diff --git a/limine/.editorconfig b/limine/.editorconfig new file mode 100644 index 0000000..5181c19 --- /dev/null +++ b/limine/.editorconfig @@ -0,0 +1,18 @@ +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true +max_line_length = 80 + +[{Makefile,GNUmakefile*}] +indent_style = tab + +[{*.toml,*.yml}] +indent_size = 2 diff --git a/limine/.forgejo/workflows/binary-release.yml b/limine/.forgejo/workflows/binary-release.yml new file mode 100644 index 0000000..822b8b0 --- /dev/null +++ b/limine/.forgejo/workflows/binary-release.yml @@ -0,0 +1,91 @@ +name: Binary release + +on: + push: + tags: + - 'v*' + +jobs: + build: + name: Build and upload binary artifacts + runs-on: codeberg-medium + container: archlinux:latest + + steps: + - name: Create resolv.conf + run: | + set -ex + echo 'nameserver 8.8.8.8' >/etc/resolv.conf + echo 'nameserver 8.8.4.4' >>/etc/resolv.conf + + - name: Install dependencies + run: pacman --noconfirm -Syu && pacman --needed --noconfirm -S nodejs base-devel gnupg git autoconf automake nasm curl mtools llvm clang lld mingw-w64-gcc + + - name: Import GPG public key + run: gpg --batch --keyserver hkps://keyserver.ubuntu.com --recv-keys 05D29860D0A0668AAEFB9D691F3C021BECA23821 + + - name: Import GPG private key + run: echo "$MINTSUKI_PRIVATE_KEY" | gpg --batch --import + env: + MINTSUKI_PRIVATE_KEY: ${{ secrets.MINTSUKI_PRIVATE_KEY }} + + - name: Checkout code + uses: https://code.forgejo.org/actions/checkout@v6 + with: + fetch-depth: '0' + + - name: Git config + run: | + set -e + git config --global --add safe.directory "$FORGEJO_WORKSPACE" + git config --global user.name 'Mintsuki' + git config --global user.email 'mintsuki@protonmail.com' + git config --global user.signingkey 05D29860D0A0668AAEFB9D691F3C021BECA23821 + + - name: Get tag name + run: echo "TAG_NAME=$(git describe --exact-match --tags $(git log -n1 --pretty='%h'))" >> $FORGEJO_ENV + + - name: Get branch name + run: echo "BRANCH_NAME=$(echo "$TAG_NAME" | grep -o 'v[0-9]\+\.')x" >> $FORGEJO_ENV + + - name: Regenerate + run: ./bootstrap + + - name: Create build dir + run: mkdir -p build + + - name: Configure + run: cd build && ../configure --enable-all + + - name: Build the bootloader + run: make -C build -j$(nproc) + + - name: Clean limine + run: rm build/bin/limine + + - name: Build limine for Windows + run: make -C build/bin CC="i686-w64-mingw32-gcc" CFLAGS="-O2 -pipe" CPPFLAGS="-D__USE_MINGW_ANSI_STDIO" limine + + - name: Strip limine for Windows + run: i686-w64-mingw32-strip build/bin/limine.exe + + - name: Copy LICENSE to bin + run: cp COPYING build/bin/LICENSE + + - name: Remove limine-bios-hdd.bin + run: rm build/bin/limine-bios-hdd.bin + + - name: Push binaries to binary branch + run: | + set -e + git remote set-url origin https://x-access-token:${{ secrets.FORGEJO_TOKEN }}@codeberg.org/Limine/Limine.git + git fetch --all + git checkout $BRANCH_NAME-binary || git checkout --orphan $BRANCH_NAME-binary + rm -rf $(ls -a | grep -v '^\.git$' | grep -v '^\.\.$' | grep -v '^\.$' | grep -v '^build$') + cp -r build/bin/. ./ + rm -rf build + git add -f . + git commit -m "Binary release $TAG_NAME" -S + git push origin $BRANCH_NAME-binary + git tag $TAG_NAME-binary -s -m $TAG_NAME-binary + git push origin $TAG_NAME-binary diff --git a/limine/.forgejo/workflows/check.yml b/limine/.forgejo/workflows/check.yml new file mode 100644 index 0000000..161eb91 --- /dev/null +++ b/limine/.forgejo/workflows/check.yml @@ -0,0 +1,51 @@ +name: Check for compilation failures + +on: [push, pull_request] + +jobs: + build: + name: Check for compilation failures + runs-on: codeberg-small + container: archlinux:latest + + steps: + - name: Create resolv.conf + run: | + set -ex + echo 'nameserver 8.8.8.8' >/etc/resolv.conf + echo 'nameserver 8.8.4.4' >>/etc/resolv.conf + + - name: Install dependencies + run: pacman --noconfirm -Syu && pacman --needed --noconfirm -S nodejs base-devel git autoconf automake nasm curl mtools llvm clang lld + + - name: Checkout code + uses: https://code.forgejo.org/actions/checkout@v6 + + - name: Build the bootloader (LLVM, default) + run: ./bootstrap && ./configure --enable-werror --enable-all && make all && make distclean + + - name: Set cross GCC version + run: echo "GCC_VERSION=15.2.0" >> $FORGEJO_ENV + + - name: Download GCC cross toolchains + run: | + set -e + for i in aarch64 loongarch64 riscv64 x86_64; do + ( curl -Lo x86_64-gcc-${{ env.GCC_VERSION }}-nolibc-$i-linux.tar.gz https://mirrors.edge.kernel.org/pub/tools/crosstool/files/bin/x86_64/${{ env.GCC_VERSION }}/x86_64-gcc-${{ env.GCC_VERSION }}-nolibc-$i-linux.tar.gz && tar -xf x86_64-gcc-${{ env.GCC_VERSION }}-nolibc-$i-linux.tar.gz ) & + WAITING_ON_PIDS="$WAITING_ON_PIDS $!" + done + for pid in $WAITING_ON_PIDS; do + wait $pid + done + + - name: Build the bootloader (GNU, x86) + run: export PATH="$(pwd -P)"/gcc-${{ env.GCC_VERSION }}-nolibc/x86_64-linux/bin:"$PATH" && ./configure TOOLCHAIN_FOR_TARGET=x86_64-linux- --enable-werror --enable-bios --enable-uefi-ia32 --enable-uefi-x86-64 && make all -j$(nproc) && make distclean + + - name: Build the bootloader (GNU, aarch64) + run: export PATH="$(pwd -P)"/gcc-${{ env.GCC_VERSION }}-nolibc/aarch64-linux/bin:"$PATH" && ./configure TOOLCHAIN_FOR_TARGET=aarch64-linux- --enable-werror --enable-uefi-aarch64 && make all -j$(nproc) && make distclean + + - name: Build the bootloader (GNU, riscv64) + run: export PATH="$(pwd -P)"/gcc-${{ env.GCC_VERSION }}-nolibc/riscv64-linux/bin:"$PATH" && ./configure TOOLCHAIN_FOR_TARGET=riscv64-linux- --enable-werror --enable-uefi-riscv64 && make all -j$(nproc) && make distclean + + - name: Build the bootloader (GNU, loongarch64) + run: export PATH="$(pwd -P)"/gcc-${{ env.GCC_VERSION }}-nolibc/loongarch64-linux/bin:"$PATH" && ./configure TOOLCHAIN_FOR_TARGET=loongarch64-linux- --enable-werror --enable-uefi-loongarch64 && make all -j$(nproc) && make distclean diff --git a/limine/.forgejo/workflows/pr_branch_check.yml b/limine/.forgejo/workflows/pr_branch_check.yml new file mode 100644 index 0000000..04645d9 --- /dev/null +++ b/limine/.forgejo/workflows/pr_branch_check.yml @@ -0,0 +1,15 @@ +name: Check that the PR is targetting trunk + +on: [ pull_request ] + +jobs: + pr_branch_check: + name: Check that the PR is targetting trunk + runs-on: codeberg-tiny + steps: + - name: Check that the PR is targetting trunk + if: ${{ forge.base_ref != 'trunk' }} + run: | + set -e + echo "The PR is not targetting the trunk branch, please fix that." + false diff --git a/limine/.forgejo/workflows/qa.yml b/limine/.forgejo/workflows/qa.yml new file mode 100644 index 0000000..4163bd5 --- /dev/null +++ b/limine/.forgejo/workflows/qa.yml @@ -0,0 +1,24 @@ +name: QA + +on: [ merge_group, push, pull_request ] + +jobs: + spellcheck: + name: Spellcheck + runs-on: codeberg-tiny + container: archlinux:latest + steps: + - name: Create resolv.conf + run: | + set -ex + echo 'nameserver 8.8.8.8' >/etc/resolv.conf + echo 'nameserver 8.8.4.4' >>/etc/resolv.conf + + - name: Install dependencies + run: pacman --noconfirm -Syu && pacman --needed --noconfirm -S nodejs git typos + + - name: Checkout code + uses: https://code.forgejo.org/actions/checkout@v6 + + - name: Run spellchecker + run: typos diff --git a/limine/.forgejo/workflows/release.yml b/limine/.forgejo/workflows/release.yml new file mode 100644 index 0000000..deeab28 --- /dev/null +++ b/limine/.forgejo/workflows/release.yml @@ -0,0 +1,100 @@ +name: Release + +on: + push: + tags: + - 'v*' + +jobs: + build: + name: Build and upload release tarball + runs-on: codeberg-small + container: archlinux:latest + + steps: + - name: Create resolv.conf + run: | + set -ex + echo 'nameserver 8.8.8.8' >/etc/resolv.conf + echo 'nameserver 8.8.4.4' >>/etc/resolv.conf + + - name: Install dependencies + run: pacman --noconfirm -Syu && pacman --needed --noconfirm -S nodejs jq curl tea base-devel gnupg gzip bzip2 xz git autoconf automake nasm curl mtools llvm clang lld github-cli + + - name: Import GPG public key + run: gpg --batch --keyserver hkps://keyserver.ubuntu.com --recv-keys 05D29860D0A0668AAEFB9D691F3C021BECA23821 + + - name: Import GPG private key + run: echo "$MINTSUKI_PRIVATE_KEY" | gpg --batch --import + env: + MINTSUKI_PRIVATE_KEY: ${{ secrets.MINTSUKI_PRIVATE_KEY }} + + - name: Checkout code + uses: https://code.forgejo.org/actions/checkout@v6 + + - name: Package release tarball + run: | + set -e + ./bootstrap + ./configure + make dist + mkdir dist-output + mv limine-*.tar.* dist-output/ + + - name: Sign release tarball + run: | + set -e + for f in dist-output/*; do + gpg --batch --default-key 05D29860D0A0668AAEFB9D691F3C021BECA23821 --detach-sign $f + done + + - name: Release + uses: https://code.forgejo.org/actions/forgejo-release@v2.7.3 + with: + direction: upload + hide-archive-link: true + release-dir: dist-output + release-notes: | + Changelog can be found [here](https://codeberg.org/Limine/Limine/src/tag/${{ forge.ref_name }}/ChangeLog). + + Binary release can be found [here](https://codeberg.org/Limine/Limine/src/tag/${{ forge.ref_name }}-binary). + + Tarballs are signed using key ID `05D29860D0A0668AAEFB9D691F3C021BECA23821` which can be obtained from a keyserver such as `keyserver.ubuntu.com`. + + Import the public key with: + ```bash + gpg --keyserver hkps://keyserver.ubuntu.com --recv-keys 05D29860D0A0668AAEFB9D691F3C021BECA23821 + ``` + + In order to verify the tarball with the given signature, do: + ```bash + gpg --verify + ``` + + - name: GitHub mirror release + env: + GITHUB_TOKEN: ${{ secrets.MINTSUKI_GH_TOKEN }} + run: | + set -e + cat >release_notes.txt <<'EOF' + Changelog can be found [here](https://github.com/limine-bootloader/limine/blob/${{ forge.ref_name }}/ChangeLog). + + Binary release can be found [here](https://github.com/limine-bootloader/limine/tree/${{ forge.ref_name }}-binary). + + Tarballs are signed using key ID `05D29860D0A0668AAEFB9D691F3C021BECA23821` which can be obtained from a keyserver such as `keyserver.ubuntu.com`. + + Import the public key with: + ```bash + gpg --keyserver hkps://keyserver.ubuntu.com --recv-keys 05D29860D0A0668AAEFB9D691F3C021BECA23821 + ``` + + In order to verify the tarball with the given signature, do: + ```bash + gpg --verify + ``` + EOF + gh release create "${{ forge.ref_name }}" \ + --repo "limine-bootloader/limine" \ + --title "${{ forge.ref_name }}" \ + --notes-file "release_notes.txt" \ + dist-output/* diff --git a/limine/.gitignore b/limine/.gitignore new file mode 100644 index 0000000..bbecc3f --- /dev/null +++ b/limine/.gitignore @@ -0,0 +1,75 @@ +# We don't want to ignore the following files +!.clang-format +!.editorconfig +!.gitattributes +!.gitignore +!.typos.toml + + +# Generated files +*.o +*.d +*.a +*.exe +*.EFI +*.bin +*.bin.gz +*.tar* +*.elf +*.hdd +*.iso +*.sys +*.dtb +*~ + +/bin +/build +/limine-protocol +/picoefi +/freestnd-c-hdrs +/flanterm +/common/lib/stb_image.h.nopatch +/common/lib/stb_image.h +/common/cc-runtime.s2.c +/cc-runtime +/decompressor/tinf +/decompressor/cc-runtime.c +/libfdt +/tinf +/edk2-ovmf +/bochsout.txt +/bx_enh_dbg.ini +/test_image +/configure +/configure.ac.save +/timestamps +/build-aux +/aclocal.m4 +/config.status +/config.log +/autom4te.cache +/man/man1/limine.1 +/GNUmakefile +/config.h +/common-bios +/common-uefi-ia32 +/common-uefi-x86-64 +/common-uefi-aarch64 +/common-uefi-riscv64 +/common-uefi-loongarch64 +/decompressor-build +/stage1.stamp + + +# For local development +/.vscode +/.gdb_history +/tags +/TAGS + +# Clang's compilation database file +compile_commands.json + +# clangd caches +/.clangd +/.cache/clangd diff --git a/limine/.typos.toml b/limine/.typos.toml new file mode 100644 index 0000000..a8a2073 --- /dev/null +++ b/limine/.typos.toml @@ -0,0 +1,24 @@ +# Configuration for https://github.com/crate-ci/typos + +[files] +extend-exclude = [ + # Typos already uses .gitignore. These excludes are additional: +] + +[default.extend-words] +hda = "hda" +Nd = "Nd" +sie = "sie" +SIE = "SIE" +ist = "ist" +rela = "rela" +RELA = "RELA" +guid = "guid" +GUID = "GUID" +pn = "pn" +hd = "hd" +htpt = "htpt" +CMO = "CMO" +extint = "extint" + +[default.extend-identifiers] diff --git a/limine/3RDPARTY.md b/limine/3RDPARTY.md new file mode 100644 index 0000000..eee4a16 --- /dev/null +++ b/limine/3RDPARTY.md @@ -0,0 +1,67 @@ +# 3rd Party Software Acknowledgments + +The Limine project depends on several other projects. + +(For readers with access to source code, know that these are pulled in by the +`./bootstrap` script, or, in the case of release tarballs, are shipped +alongside the core Limine code in the tarballs themselves, similar to +`./bootstrap` having already been run.) + +These additional projects are NOT covered by the License as contained inside +the `COPYING` file as present at the root of the source tree, or, for installed +copies, present at `${DOCDIR}/COPYING` (assuming the file has not been +otherwise removed by the packager). These are instead licensed as described by +each individual project's documentation present in each project's dedicated +subdirectory or license header(s) in the source tree. For readers without access +to the source code, one can read the following for a quick overview of licenses +that Limine is distributed under: + +A non-binding, informal summary of all projects Limine depends on, and the +licenses used by said projects, in SPDX format, is as follows: + +- [cc-runtime](https://codeberg.org/OSDev/cc-runtime) +(Apache-2.0 WITH LLVM-exception) is used to provide runtime libgcc-like +routines. + +- [0BSD Freestanding C Headers](https://codeberg.org/OSDev/freestnd-c-hdrs-0bsd) +(0BSD) provide GCC and Clang compatible freestanding C headers. + +- [Limine Boot Protocol](https://codeberg.org/Limine/limine-protocol) +(0BSD) has the C/C++ header and the specification text of the Limine Boot +Protocol. + +- [PicoEFI](https://codeberg.org/PicoEFI/PicoEFI) (multiple licenses, see list +below) provides headers and build-time support for UEFI. + - BSD-2-Clause + - BSD-2-Clause-Patent + - BSD-3-Clause + - LicenseRef-scancode-bsd-no-disclaimer-unmodified + - MIT + + For more information about the + LicenseRef-scancode-bsd-no-disclaimer-unmodified license used by parts of + PicoEFI, see + https://scancode-licensedb.aboutcode.org/bsd-no-disclaimer-unmodified.html + and the LicenseRef file + [here](LICENSES/LicenseRef-scancode-bsd-no-disclaimer-unmodified.txt), + in case of viewing this file from inside the source tree, alternatively at + `${DOCDIR}/LICENSES/LicenseRef-scancode-bsd-no-disclaimer-unmodified.txt` + in case of installed copies, assuming the file has not been otherwise + removed by the packager. + +- [tinf](https://github.com/jibsen/tinf) (Zlib) is used in early x86 BIOS +stages for GZIP decompression of stage2. + +- [Flanterm](https://codeberg.org/Mintsuki/Flanterm) (BSD-2-Clause) is used for +text related screen drawing. + +- [stb_image](https://github.com/nothings/stb/blob/master/stb_image.h) (MIT) is +used for wallpaper image loading. + +- [libfdt](https://codeberg.org/OSDev/libfdt) (BSD-2-Clause) is used for +manipulating Flat Device Trees. + +Note that some of these projects, or parts of them, are provided under +dual-licensing, in which case, in the above list, the only license mentioned is +the one chosen by the Limine developers. Refer to each individual project's +documentation for details. diff --git a/limine/CONFIG.md b/limine/CONFIG.md new file mode 100644 index 0000000..db01899 --- /dev/null +++ b/limine/CONFIG.md @@ -0,0 +1,364 @@ +# Limine configuration file + +## Location of the config file + +For EFI-booted Limine, `/limine.conf` is taken into account +first. On BIOS, or on EFI if that file is not found, Limine scans for the +config file on *the boot drive*. Every partition of the boot drive is scanned +sequentially - first partition first (or, on EFI, the partition containing the +EFI executable of the booted Limine is scanned first), last partition last - +for the presence of either a `/boot/limine/limine.conf`, `/boot/limine.conf`, +`/limine/limine.conf`, or a `/limine.conf` file, in that order. + +Once the file is located, Limine will use it as its config file. Other possible +candidates in subsequent partitions or directories are ignored. + +It is thus imperative that the intended config file is placed in a location +that will not be shadowed by another candidate config file. + +### Config via SMBIOS + +Alternatively, if present, Limine considers first and foremost configurations +supplied to it as SMBIOS OEM String entries (Type 11). Such configurations are +accepted if the first string of such an entry starts with the prefix of +`limine:config:`. The rest of the string is taken as the config file. If such a +configuration is found, no further scanning for config files is done. As such, +in this SMBIOS-provided config file scenario, the `boot():` drive is undefined +on BIOS, and set to the boot device of Limine on UEFI. + +## Structure of the config file + +The Limine configuration file is comprised of *menu entries* and *options*. +Comments begin in '#' and can only be on their own lines. + +### Menu entries and sub-entries + +*Menu entries* describe *entries* which the user can select in the *boot menu*. + +A *menu entry* is opened by a line starting with `/` followed by a +newline-terminated string, that being the title of the entry which the user +will see. +Any *local option* that comes after it, and before another *menu entry*, or +the end of the file, will be part of that *menu entry*. + +A *menu entry* can be a directory, meaning it can hold sub-entries. In order +for an entry to become a directory, it needs to have a sub-entry following +right after it. +(A `comment` option may be present between the beginning of the directory entry +and the beginning of the sub-entry). + +A *sub-entry* is a menu entry started with a number of `/` greater than 1 +prepended to it. +Each `/` represents 1 level deeper down the tree hierarchy of directories and +entries. + +Directories can be expanded (meaning they will not show up as collapsed in the +menu) by default if a `+` is put between the `/`s and the beginning of the +entry's title. + +### Options + +*Options* are simple `option_name: string...` style "assignments". +The string can have spaces and other special characters, without requiring +quotations. New lines are delimiters. Option names are not case sensitive. + +Some *options* are part of an entry (*local*), some other options are *global*. +*Global options* can appear anywhere in the file and are not part of an entry, +although usually one would put them at the beginning of the config. +Some *local options* work the same between entries using any *protocol*, while +other *local options* are specific to a given *protocol*. + +Some options take *paths* as strings; these are described in the next section. + +*Global options* are: + +Miscellaneous: + +* `timeout` - Specifies the timeout in seconds before the first *entry* is + automatically booted. If set to `no`, disable automatic boot. If set to `0`, + boots default entry instantly (see `default_entry` option). +* `quiet` - If set to `yes`, enable quiet mode, where all screen output except + panics and important warnings is suppressed. If `timeout` is not 0, the + `timeout` still occurs, and pressing any key during the timeout will reveal + the menu and disable quiet mode. +* `serial` - If set to `yes`, enable serial I/O for the bootloader. +* `serial_baudrate` - If `serial` is set to `yes`, this specifies the baudrate + to use for serial I/O. Defaults to `115200`. BIOS only, ignored with Limine + UEFI. +* `global_dtb` - If set, use this DTB instead of the firmware-provided DTB for + Limine itself, as well as for any booted entry whose protocol supports DTBs + and the DTB is not locally overridden with `dtb_path`. +* `default_entry` - 1-based entry index of the entry which will be + automatically selected at startup. If unspecified, it is `1`. +* `remember_last_entry` - If set to `yes`, remember last booted entry. + (UEFI only). +* `graphics` - If set to `no`, force CGA text mode for the boot menu, else use + a video mode. Ignored with Limine UEFI. +* `wallpaper` - Path to a file to use as a wallpaper. BMP, PNG, and JPEG + formats are supported. There can be multiple of this option, in which case + the wallpaper will be randomly selected from the provided options. +* `wallpaper_style` - The style which will be used to display the wallpaper + image: `tiled`, `centered`, or `stretched`. Default is `stretched`. +* `backdrop` - When the background style is `centered`, this specifies the + colour of the backdrop for parts of the screen not covered by the background + image, in RRGGBB format. +* `verbose` - If set to `yes`, print additional information during boot. + Defaults to not verbose. +* `randomise_memory` - If set to `yes`, randomise the contents of RAM at bootup + in order to find bugs related to non zeroed memory or for security reasons. + This option will slow down boot time significantly. For the BIOS port of + Limine, this will only randomise memory below 4GiB. +* `randomize_memory` - Alias of `randomise_memory`. +* `hash_mismatch_panic` - If set to `no`, do not panic if there is a hash + mismatch for a file, but print a warning instead. + +Limine interface control options: + +* `interface_resolution` - Specify screen resolution to be used by the Limine + interface (menu, editor, console...) in the form `x`. This + will *only* affect the Limine interface, not any booted OS. If not specified, + Limine will pick a resolution automatically. If the resolution is not + available, Limine will pick another one automatically. Ignored if using text + mode. +* `interface_rotation` - Specifies the rotation of the Limine interface. + It can be any of the following values: `0`, `90`, `180`, `270`. Default is `0`. +* `interface_branding` - A string that will be displayed on top of the Limine + interface. +* `interface_branding_colour` - A value between 0 and 7 specifying the colour + of the branding string. See below for a table of colours. Default is `6`. +* `interface_branding_color` - Alias of `interface_branding_colour`. +* `interface_help_hidden` - Hides the help text located at the top of the + screen showing the key bindings. +* `interface_help_colour` - A value between 0 and 7 specifying the colour + of the help strings. See below for a table of colours. Default is `2`. +* `interface_help_color` - Alias of `interface_help_colour`. + + | Code | Color | + |------|---------| + | 0 | Black | + | 1 | Red | + | 2 | Green | + | 3 | Yellow | + | 4 | Blue | + | 5 | Magenta | + | 6 | Cyan | + | 7 | Gray | + +Limine graphical terminal control options: + +These are ignored if using text mode. + +* `term_font` - Path to a font file to be used instead of the default one for + the menu and terminal. The font file must be a code page 437 character set + comprised of 256 consecutive glyph bitmaps. Each glyph's bitmap must be + expressed left to right (1 byte per row), and top to bottom (16 bytes per + whole glyph by default; see `term_font_size`). See e.g. the + [VGA text mode font](https://github.com/viler-int10h/vga-text-mode-fonts) + collection for fonts. +* `term_font_size` - The size of each glyph of the font in dots, which must + correspond to the font file, or display will be garbled or loading issues + will occur. Since it is assumed that all fonts are of width 8, the first + value of the pair (AKA the `8` in `8x16`) is effectively ignored. To set + horizontal spacing between glyphs on screen, see `term_font_spacing`. + Defaults to `8x16`. Ignored if `term_font` not set or if the font fails to + load. +* `term_font_scale` - Scaling for the font in the x and y directions. `2x2` + would display the font in double size, which is useful on high-DPI displays + at native resolution. `2x1` only makes the font twice as wide, similar to the + VGA 40 column mode. `4x2` might be good for a narrow font on a high + resolution display. Values over 8 are disallowed. Default is no scaling, + i.e. `1x1`. +* `term_font_spacing` - Horizontal spacing, in pixels, between glyphs on + screen. Also applies to the built-in Limine font. Defaults to 1. 0 is + allowed. +* `term_palette` - Specifies the colour palette used by the terminal (RRGGBB). + It is a `;` separated array of 8 colours: black, red, green, brown, blue, + magenta, cyan, and gray. Ignored if not using a graphical terminal. +* `term_palette_bright` - Specifies the bright colour palette used by the + terminal (RRGGBB). It is a `;` separated array of 8 bright colours: + dark gray, bright red, bright green, yellow, bright blue, bright magenta, + bright cyan, and white. Ignored if not using a graphical terminal. +* `term_background` - Terminal text background colour (TTRRGGBB). TT stands for + transparency. +* `term_foreground` - Terminal text foreground colour (RRGGBB). +* `term_background_bright` - Terminal text background bright colour (RRGGBB). +* `term_foreground_bright` - Terminal text foreground bright colour (RRGGBB). +* `term_margin` - Set the amount of margin around the terminal. +* `term_margin_gradient` - Set the thickness in pixel for the gradient around + the terminal. + +Editor control options: + +* `editor_enabled` - If set to `no`, the editor will not be accessible. + Defaults to `yes` unless a config hash is enrolled. +* `editor_highlighting` - If set to `no`, syntax highlighting in the editor + will be disabled. Defaults to `yes`. +* `editor_validation` - If set to `no`, the editor will not alert you about + invalid options or syntax errors. Defaults to `yes`. + +*Locally assignable (non protocol specific) options* are: + +* `comment` - An optional comment string that will be displayed by the + bootloader on the menu when an entry is selected. +* `protocol` - The boot protocol that will be used to boot the + kernel/executable. Valid protocols are: `linux`, `limine`, `multiboot` + (or `multiboot1`), `multiboot2`, `efi`, and `bios`. +* `cmdline` - The command line string to be passed to the kernel/executable. + Can be omitted. +* `kernel_cmdline` - Alias of `cmdline`. + +> **NOTE:** `uefi` and `efi_chainload` are aliases of the `efi` protocol +> option. `bios_chainload` is an alias of the `bios` protocol option. + +> **NOTE:** BIOS chainloading entries will be hidden when booting using UEFI +> and vice-versa. + +*Locally assignable (protocol specific) options* are: + +* Linux protocol: + * `path` - The path of the kernel. + * `kernel_path` - Alias of `path`. + * `module_path` - The path to a module (such as initramfs). This option can + be specified multiple times to specify multiple modules. + * `resolution` - The resolution to be used. This setting takes the form of + `xx`. If the resolution is not available, Limine will + pick another one automatically. Omitting `` will default to 32. + * `textmode` - If set to `yes`, prefer text mode. (BIOS only) + * `dtb_path` - A device tree blob to pass instead of the one provided by the + firmware. + +* Limine protocol: + * `path` - The path of the executable. + * `kernel_path` - Alias of `path`. + * `module_path` - The path to a module. This option can be specified multiple + times to specify multiple modules. + * `module_string` - A string to be associated with a module. This option can + also be specified multiple times. It applies to the module described by the + last module option specified. + * `module_cmdline` - Alias of `module_string`. + * `resolution` - The resolution to be used. This setting takes the form of + `xx`. If the resolution is not available, Limine will + pick another one automatically. Omitting `` will default to 32. + * `kaslr` - For relocatable executables, if set to `yes`, enable kernel + address space layout randomisation. KASLR is disabled by default. + * `randomise_hhdm_base` - If set to `yes`, randomise the base address of the + higher half direct map. If set to `no`, do not. By default it is `yes` if + KASLR is supported and enabled, else it is `no`. + * `randomize_hhdm_base` - Alias of `randomise_hhdm_base`. + * `max_paging_mode`, `min_paging_mode` - Limit the maximum and minimum paging + modes to one of the following: + - x86-64 and aarch64: `4level`, `5level`. + - riscv64: `sv39`, `sv48`, `sv57`. + - loongarch64: `4level`. + * `paging_mode` - Equivalent to setting both `max_paging_mode` and + `min_paging_mode` to the same value. + * `dtb_path` - A device tree blob to pass instead of the one provided by the + firmware. + +* multiboot1 and multiboot2 protocols: + * `path` - The path of the executable. + * `kernel_path` - Alias of `path`. + * `module_path` - The path to a module. This option can be specified multiple + times to specify multiple modules. + * `module_string` - A string to be passed to a module. This option can also + be specified multiple times. It applies to the module described by the last + module option specified. + * `resolution` - The resolution to be used should the executable request a + graphical framebuffer. This setting takes the form of + `xx` and *overrides* any resolution requested by the + executable. If the resolution is not available, Limine will pick another + one automatically. Omitting `` will default to 32. + * `textmode` - If set to `yes`, prefer text mode. (BIOS only) + +* EFI Chainload protocol: + * `path` - Path of the EFI application to chainload. + * `image_path` - Alias of `path`. + * `resolution` - The resolution to be used. This setting takes the form of + `xx`. If the resolution is not available, Limine will + pick another one automatically. Omitting `` will default to 32. + +* BIOS Chainload protocol: + * `drive` - The 1-based drive to chainload, if omitted, assume boot drive. + * `partition` - The 1-based partition to chainload, if omitted, or set to 0, + chainload drive (MBR). + * `mbr_id` - Optional. If passed, use an MBR ID (32-bit hex value) to + identify the drive containing the volume to chainload. Overrides `drive`, + if present, but does *not* override `partition`. + * `gpt_uuid` or `gpt_guid` - Optional. If passed, use the GPT GUID to + identify the drive containing the volume to chainload. Overrides `drive` + and `mbr_id`, if present, but does *not* override `partition`. + +## Paths + +A Limine path is used to locate files in the whole system. It is comprised of +a *resource*, a *resource argument*, and a *path*. It takes the form of: +``` +resource(argument):/path +``` + +The format for `argument` changes depending on the resource used. + +A resource can be one of the following: +* `boot` - If booted off PXE this is an alias of `tftp`. Else the `argument` is + the 1-based decimal value representing the partition on the boot drive + (values of 5+ for MBR logical partitions). If omitted, the partition + containing the configuration file on the boot drive is used. + For example: `boot(2):/...` will use partition 2 of the boot drive and + `boot():/...` will use the partition containing the config file on the boot + drive. +* `hdd` - Hard disk drives. The `argument` takes the form of `drive:partition`; + for example: `hdd(3:1):/...` would use hard drive 3, partition 1. Partitions + and drives are both 1-based (partition values of 5+ for MBR logical + partitions). Omitting the partition is possible; + for example: `hdd(2:):/...`. Omitting the partition will access the entire + volume instead of a specific partition (useful for unpartitioned media). +* `odd` - Optical disk drives (CDs/DVDs/...). The `argument` takes the form of + `drive:partition`; for example: `odd(3:1):/...` would use optical drive 3, + partition 1. Partitions and drives are both 1-based (partition values of 5+ + for MBR logical partitions). Omitting the partition is possible; + for example: `odd(2:):/...`. Omitting the partition will access the entire + volume instead of a specific partition (useful for unpartitioned media, which + is often the case for optical media). +* `guid` - The `argument` takes the form of a GUID/UUID, such as + `guid(736b5698-5ae1-4dff-be2c-ef8f44a61c52):/...`. The GUID is that of either + a filesystem, when available, or a GPT partition GUID, when using GPT, in a + unified namespace. +* `uuid` - Alias of `guid`. +* `fslabel` - The `argument` is the name of the filesystem label of a + partition. +* `tftp` - The `argument` is the IP address of the tftp server to load the file + from. If the argument is left empty (`tftp():/...`) the file will be loaded + from the server Limine booted from. This resource is only available when + booting off PXE. + +A path can optionally be suffixed with a blake2b hash for the referenced file, +by appending a pound character (`#`) followed by the blake2b hash. +E.g.: `boot():/somemodule.tar#ca6914d2...446b470a`. + +## Macros + +Macros are strings that can be arbitrarily assigned to represent other strings. +For example: +``` +${MY_MACRO}=Some text +``` + +Now, whenever `${MY_MACRO}` is used in the config file (except for an +assignment as above), it will be replaced by the text `Some text`. For example: +``` +CMDLINE=something before ${MY_MACRO} something after +``` + +Macros must always be placed inside `${...}` where `...` is the arbitrary macro +name. + +### Built-in macros + +Limine automatically defines these macros: + +* `ARCH` - This built-in macro expands to the architecture of the machine. + Possible values are: `x86-64`, `ia-32`, `aarch64`, `riscv64`, `loongarch64`. + In the case of IA-32, BIOS or UEFI, the macro will always expand to `x86-64` + if the 64-bit extensions are available, else `ia-32`. +* `FW_TYPE` - This built-in macro expands to `UEFI` if booted using UEFI + firmware, or `BIOS` if booted using legacy x86 BIOS. diff --git a/limine/COPYING b/limine/COPYING new file mode 100644 index 0000000..a59751f --- /dev/null +++ b/limine/COPYING @@ -0,0 +1,22 @@ +Copyright (C) 2019-2026 Mintsuki and contributors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/limine/ChangeLog b/limine/ChangeLog new file mode 100644 index 0000000..cb95cbf --- /dev/null +++ b/limine/ChangeLog @@ -0,0 +1,1882 @@ +2026-03-12 Mintsuki + + *** Release 10.8.5 *** + + Noteworthy changes compared to the previous release, 10.8.4: + + Bug fixes: + - Update the Flanterm library to 3.0.2 to fix a regression introduced + in Flanterm 3.0.1 that would also cause misrenderings of the boot + menu's text colour for selected entries. + +2026-03-11 Mintsuki + + *** Release 10.8.4 *** + + Noteworthy changes compared to the previous release, 10.8.3: + + Bug fixes: + - Update the Flanterm library to 3.0.1 to fix a regression that would + cause misrenderings of the boot menu's text colour for selected + entries. + +2026-03-07 Mintsuki + + *** Release 10.8.3 *** + + Noteworthy changes compared to the previous release, 10.8.2: + + Bug fixes: + - Preserve LAPIC register state across x2APIC-to-xAPIC transition. + - SMP: Widen bsp_lapic_id to uint32_t to avoid x2APIC ID truncation. + - Linux boot protocol: Validate RISC-V kernel header before trusting + image_size for allocation. + - Host tool: Replace GCC/Clang __builtin overflow checks with portable + C99 helpers. + - Host tool: Fix ENDSWAP width mismatch in GPT-to-MBR partition entry + conversion on big-endian hosts. + - BIOS HDD Stage 1: Fix 64-bit LBA calculation and carry propagation in + disk read loop. + - BIOS HDD Stage 1: Fix stack imbalance on int 13h/AH=48h failure. + - Add NULL terminator check in config_get_entry header line scan. + - Guard editor window_size decrement to prevent underflow on long line + wrapping. + +2026-02-26 Mintsuki + + *** Release 10.8.2 *** + + Noteworthy changes compared to the previous release, 10.8.1: + + Bug fixes: + - Revert broken/pointless legacy PIC reinitialisation logic on flush + that accidentally made it into 10.8.1. + +2026-02-26 Mintsuki + + *** Release 10.8.1 *** + + Noteworthy changes compared to the previous release, 10.8.0: + + Bug fixes: + - Fix VT-d disable order to TE, IRE, QIE. + - Fix VT-d polling loop read order and remove polling timeout. + - SMP: Add xAPIC ICR delivery status checks to INIT-SIPI-SIPI sequence. + - Amend certain APIC-related issues of Limine boot protocol and + implement the corrected behaviour. + +2026-02-22 Mintsuki + + *** Release 10.8.0 *** + + Noteworthy changes compared to the previous release, 10.7.0: + + New features: + - Limine boot protocol: Implement base revision 5. + - Limine boot protocol: Implement x86-64 Keep IOMMU feature. + + Bug fixes: + - Fix font autoscaling not resetting per display in multi-display + configurations. + - Fix undefined behaviour in memmove() due to direct pointer + comparisons. + - Skip GOP and VBE modes with invalid pitch values. + - GOP: Work around firmware reporting incorrect pitch via QueryMode() + by fetching from gop->Mode->Info after SetMode(). + - GOP: Ensure SetMode() is called at least once per handle to work + around firmware not initialising state without that. + - Fall back to EFI/BIOS console instead of panicking on small + terminals. + + Miscellaneous: + - Disable Intel VT-d and AMD-Vi IOMMUs before kernel entry by default + for the Limine, multiboot1, and multiboot2 protocols. + - Try to gracefully disable x2APIC before handoff for the Limine + (unless kernel asks for it), multiboot1, and multiboot2 protocols. + - Replace cycle-based delay() with wall time calibrated stall(). + - Prefer physical memory allocations below 4 GiB on x86. + - Align ACPI table memory map entries to page size where safe. + - Undo several overzealous limits and bounds checks added in prior + releases. + - Updated limine-protocol subproject to latest revision. + +2026-02-10 Mintsuki + + *** Release 10.7.0 *** + + Noteworthy changes compared to the previous release, 10.6.6: + + New features: + - Multiboot2: Implement relocatable preference=2 (prefer highest + address). + - Multiboot2: Accept LOAD_BASE_ADDR and NETWORK info request tags. + - Multiboot2: Accept EFI system table and image handle info requests. + + Bug fixes: + - Extensive hardening of the ELF and PE loaders with thorough bounds, + overflow, and alignment validation. + - Extensive hardening of the Multiboot and Multiboot2 protocol + implementations with thorough validation of header fields, tag sizes, + and relocation parameters. + - Linux boot protocol: Honour kernel_alignment field from the x86 boot + protocol header for kernel load address alignment, and init_size for + allocation sizing. Use image_size from the kernel header for non-x86 + allocation. + - Fix several bugs in the physical memory manager's overlap detection, + sanitiser logic, and memory info reporting. + - SMP: Fix x86 AP temp stack pointer to pass top of allocation instead + of base. Fix several bugs in the AArch64 SMP trampoline and AP + enumeration paths. Skip APs with unrecognised PSCI method instead of + panicking. + - Host tool: Fix signature search algorithm to correctly handle partial + prefix overlaps. Bounds-check GPT partition entry offset arithmetic + for overflow. Guard secondary GPT nuke against alternate_lba + underflow. + - ACPI: Fall back to RSDT when XSDT is above 4 GiB on 32-bit. + - VMM: Preserve WC/FB cache attribute in x86 page table flags during + page splits. + + Miscellaneous: + - SMP: Send two SIPIs per Intel SDM recommendation for AP startup. + - Use rdtime.d and CPUCFG for LoongArch64 performance timing. + - Use rdtime and time_base_frequency for RISC-V performance timing. + - Updated Flanterm subproject to latest revision. + - Miscellaneous bug fixes and improvements. + +2026-02-05 Mintsuki + + *** Release 10.6.6 *** + + Noteworthy changes compared to the previous release, 10.6.5: + + Bug fixes: + - Re-add a mistakenly removed check for EFI volume responsiveness. Lack + of this check would indirectly cause hangs due to EFI volumes being + indexed despite their non-responsiveness. + + Miscellaneous: + - Replace suboptimal code to handle reads close to the end of a volume. + +2026-02-04 Mintsuki + + *** Release 10.6.5 *** + + Noteworthy changes compared to the previous release, 10.6.4: + + Bug fixes: + - Work around PicoEFI structure definition issue related to padding + that would cause volume_by_device_path() to always fail for + non-optical media. + - Fix issue that would cause unique sector detection on systems with 3+ + collisions to fail and misreport unique sectors. + - Add extra layer of verification for unique sector matches to minimise + the chance of volume mismatches. + + Miscellaneous: + - RISC-V: Implement Flanterm refresh path for SoCs not exposing Zicbom + in the device tree or ACPI (often the case with U-boot). + - Use volume_by_device_path() as primary disk_volume_from_efi_handle() + detection method. + - Lazily discover unique sectors only when the fallback requiring this + procedure is actually used. + +2026-02-02 Mintsuki + + *** Release 10.6.4 *** + + Noteworthy changes compared to the previous release, 10.6.3: + + Bug fixes: + - Strip out all code relying on disk writes from the codebase. This + gets rid of the (remote) possibility of disk corruption and the (even + more remote) possibility of firmware/flash corruption. This also + sorts out an issue on Apple Silicon where m1n1/U-boot appears to + leave the NVMe controller in an inconsistent state after write + operations are attempted. + +2026-01-14 Mintsuki + + *** Release 10.6.3 *** + + Noteworthy changes compared to the previous release, 10.6.2: + + Bug fixes: + - Fix an issue with the `limine` host tool where the files read by or + created by the `bios-install` command with the + `--uninstall-data-file=` flag were prefixed by `=` due to an + off-by-one error. + - Fix an issue with the `limine` host tool where the `--quiet` and + `--reset` flags could cause the next flag to be ignored due to an + off-by-one error. + - Fix an issue with the `limine` host tool that would allow a user to + provide a GPT BIOS boot partition to the `bios-install` command that + was 1 greater than the maximum number of partitions due to an + off-by-one error. + - Fix internal module path corruption in the Limine boot protocol code, + caused by a static buffer reuse bug. + - Add timeout for A20 address line initialisation in the keyboard + controller code path. Lack of this could cause hangs on certain older + "legacy-free" PCs. + - Fix a couple of minor memory leaks. + - Undo several overzealous "bug fixes" done since 10.5.0 for issues + that were impossible or highly unlikely to trigger in practice. + - Miscellaneous bug fixes. + +2026-01-07 Mintsuki + + *** Release 10.6.2 *** + + Noteworthy changes compared to the previous release, 10.6.1: + + Bug fixes: + - Fix a bug where a buffer holding a configuration file option's value + was erroneously overwritten. This affected x86 BIOS builds only, and + it would be triggered if the `serial_baudrate` configuration file + option was used. + +2026-01-05 Mintsuki + + *** Release 10.6.1 *** + + Noteworthy changes compared to the previous release, 10.6.0: + + Bug fixes: + - Fix an issue where there were no bound checks when pressing the + delete key in the menu entry editor, sometimes causing out-of-bounds + accesses. + - Add many checks for return values of disk-related functions that + could fail. + - Fix a typo in the physical memory manager code where a pointer was + checked instead of the value it was pointing to. This could have in + theory caused 0-length usable memory entries to appear in the memory + map. + - Add checks to avoid division by 0 if the user-specified baud rate is + 0. + - Fix broken calculation in the fb_clear() function that caused severe + out-of-bounds writes when the bits-per-pixel were not 32 or 16. + - Fix potential unaligned memory accesses in the BLAKE2B code. This + could have caused issues on RISC architectures like aarch64 or + riscv64. + - Fix missing continues in the riscv ISA string initialisation logic. + This could have caused HARTs with invalid ISA strings to be used + anyways instead of being ignored. + - More minor bug fixes for bugs discovered during static analysis. + + Miscellaneous: + - Use C versions of mem*() functions instead of assembly versions for + x86 and x86-64. + +2025-12-28 Mintsuki + + *** Release 10.6.0 *** + + Noteworthy changes compared to the previous release, 10.5.1: + + New features: + - ISO9660: Add support for large, multi-extent files. + + Bug fixes: + - Unbreak BIOS booting from optical media with ISO9660 filesystems on + many platforms including QEMU. This was a regression introduced in + 10.5.1. + - Fix an issue with large (>4GiB) physical memory allocations on 32-bit + platforms. Some values were truncated to size_t (aka uint32_t) in + many instances causing bugs. + - Fix a couple of memory leaks missed in 10.5.1. + - Add safeguard against the font file being too small for the expected + size. This avoids potential panics from the filesystem driver, and it + instead just prints a warning and falls back to using the default + font. + + Miscellaneous: + - Update description of the `term_font_size` and `term_font_spacing` + configuration options in CONFIG.md. + +2025-12-26 Mintsuki + + *** Release 10.5.1 *** + + Noteworthy changes compared to the previous release, 10.5.0: + + Bug fixes: + - Many bug fixes throughout the codebase, mainly tackling hardening + (especially of filesystem drivers). Most issues were caught with + static analysis. + - Fixage of some memory leaks. + - Fixage of major issue that could have caused UEFI PXE to not work. + - Multiboot1: fix issue that would cause the information buffer to be + underallocated. + +2025-12-11 Mintsuki + + *** Release 10.5.0 *** + + Noteworthy changes compared to the previous release, 10.4.0: + + New features: + - Add new `interface_help_colour` global configuration file option to + allow changing the colour of "helper" messages in the interface. + - Add new `interface_rotation` global configuration option to allow + rotating the output interface be 0, 90, 180, or 270 degrees. + +2025-11-30 Mintsuki + + *** Release 10.4.0 *** + + Noteworthy changes compared to the previous release, 10.3.2: + + New features: + - Add new configuration file option "global_dtb" to allow overriding + the device tree used by Limine itself as well as for all boot entries + (unless overridden by entry-local "dtb_path" option). + + Bug fixes: + - Flush entire I-Cache if it is not PIPT on aarch64. + - Fix use-after-free bug in riscv64 initialisation code related to + device tree handling. + + Miscellaneous: + - Framebuffer-related caching improvements for riscv64 and aarch64. + This should fix a long standing issue where, on a lot of riscv64 + boards, the Limine interface would look mangled due to missing cache + flushing. + - Prioritise using ACPI for riscv64 initialisation code if available + and if a DTB is not manually specified. + +2025-11-23 Mintsuki + + *** Release 10.3.2 *** + + Noteworthy changes compared to the previous release, 10.3.1: + + CI-related changes, otherwise, same as 10.3.1. + +2025-11-23 Mintsuki + + *** Release 10.3.1 *** + + Noteworthy changes compared to the previous release, 10.3.0: + + Bug fixes: + - Fix wrong virtual address related constants, for the loongarch64 + port, that made Limine-protocol kernels fail to boot. + - Fix issue that caused the MBR ID in the Limine boot protocol + limine_file structure to always be 0. + +2025-11-07 Mintsuki + + *** Release 10.3.0 *** + + Noteworthy changes compared to the previous release, 10.2.1: + + **Special notes:** + Despite there being breaking changes in this release, no major version + bump is done; this is because the changes are minor enough, there are + no big new features, and version 10.x was not released long enough ago. + This is technically in violation of Semantic Versioning, but considered + to be an acceptable exception. + + Breaking changes: + - Change default paths and default path ordering for configuration file + search. Mainly, search first for a configuration file in the same + directory as the EFI application file of Limine, on EFI ports. + - Change default path ordering for stage 3 file search on BIOS ports. + - No longer install the limine.h header file on the host. + - No longer install PROTOCOL.md (Limine Boot Protocol specification) on + the host. + + Bug fixes: + - Fix an issue where code assumed that the RSDT address in the RSDP was + always non-NULL, which could cause crashes or hangs when using base + revision 4 of the Limine Boot Protocol. + +2025-10-29 Mintsuki + + *** Release 10.2.1 *** + + Noteworthy changes compared to the previous release, 10.2.0: + + Bug fixes: + - Add workaround for skipping SPI flash devices exposed as EFI volumes. + This fixes the hanging issues on many ARM laptops, without breaking + U-Boot unlike the previous workaround. + +2025-10-26 Mintsuki + + *** Release 10.2.0 *** + + Noteworthy changes compared to the previous release, 10.1.1: + + New features: + - Implement base revision 4 of the Limine boot protocol as per + documentation (read documentation for specific details). + + Bug fixes: + - "Fix" multiboot1 framebuffer structure to match GRUB instead of the + multiboot1 specification. + +2025-10-13 Mintsuki + + *** Release 10.1.1 *** + + Noteworthy changes compared to the previous release, 10.1.0: + + Bug fixes: + - DTBs: Always reallocate DTBs to bootloader-reclaimable memory. This + is to prevent pointers to EFI/reserved memory from being leaked to + Limine protocol executables, which is against said protocol, which + mandates that DTBs have to be in bootloader-reclaimable memory. + +2025-10-05 Mintsuki + + *** Release 10.1.0 *** + + Noteworthy changes compared to the previous release, 10.0.1: + + New features: + - Expand safety checks of the `limine bios-install` command and replace + the `--force-mbr` flag with just `--force`. + - Add autodetection of the BIOS boot partition if no explicit partition + index is passed to `limine bios-install`. + + Bug fixes: + - Only use ASCII characters for terminal output when graphical output + fails to initialise and the EFI standard output console is used + instead. + + Miscellaneous: + - Remove left-over logic that was meant to supplement the removed GPT + embedding feature. + - Remove workaround to allow building the RISC-V port with LLVM <=16. + - Update a bunch of output messages from the `limine` host tool. + +2025-09-21 Mintsuki + + *** Release 10.0.1 *** + + Noteworthy changes compared to the previous release, 10.0.0: + + Bug fixes: + - Fix a potential use-after-free issue when flushing interrupts on x86 + UEFI systems. + - Prefer using the 32-bit Linux protocol unless it is strictly + necessary to use the 64-bit protocol due to allocation constraints. + This is due to reports of the 32-bit protocol being more reliable + on certain rare systems where the 64-bit protocol crashes or hangs. + +2025-09-15 Mintsuki + + *** Release 10.0.0 *** + + Noteworthy changes compared to the previous release, 9.6.7: + + Breaking changes: + - GPT embedding is no longer supported as a deployment method for + GPT-with-BIOS configurations. The rationale for this being that it is + far too fragile, especially with regards to growing or shrinking disk + images, and it may cause certain machines to refuse to boot such + setups as they are technically in violation of the GPT specification. + - The default baud rate for serial I/O on BIOS systems is now 115200, + instead of 9600. + - BIOS (chainloading) boot menu entries are now hidden when booting in + UEFI mode, and EFI (chainloading) entries are hidden when booting in + BIOS mode. + - For the Limine boot protocol, for relocatable executables, KASLR now + defaults to being disabled. The rationale for this being that KASLR + is a somewhat niche and fairly bypassable security technique, which + can cause headaches for kernels not wanting to support it, and make + troubleshooting harder. + - For the Limine boot protocol, for PE format kernels, do not discard + discardable sections, as code put to a discardable section like INIT + might be intended to only be discarded after early kernel init, not + before the kernel is even loaded. + +2025-09-13 Mintsuki + + *** Release 9.6.7 *** + + Noteworthy changes compared to the previous release, 9.6.6: + + Bug fixes: + - Relax EFI handle validation before running fallback volume detection. + This further addresses issues caused by 9.5.4. + +2025-09-06 Mintsuki + + *** Release 9.6.6 *** + + Noteworthy changes compared to the previous release, 9.6.5: + + Bug fixes: + - Fix regression introduced by 9.5.4 that caused Limine to fail to boot + with U-Boot. + +2025-08-30 Mintsuki + + *** Release 9.6.3 through 9.6.5 *** + + Noteworthy changes compared to the previous release, 9.6.2: + + These are special hotfix releases attempting to address issues with the + Forgejo release workflow after the migration to Codeberg. + + Other than that, they have no differences compared to 9.6.2. + +2025-08-30 Mintsuki + + *** Release 9.6.2 *** + + Noteworthy changes compared to the previous release, 9.6.1: + + Miscellaneous: + - The Limine project has moved its home to Codeberg. + - Nyu-EFI was rebranded to PicoEFI. + - Minor build system improvements and subproject bumps. + +2025-08-16 Mintsuki + + *** Release 9.6.1 *** + + Noteworthy changes compared to the previous release, 9.6.0: + + Bug fixes: + - EFI chainloading: Make EFI device path of loaded image relative to + the loaded image device handle. This now complies with the EFI + specification, and, amongst others, it fixes chainloading Shim. + + Miscellaneous: + - Miscellaneous build system changes and improvements. + - Do not disable linker relaxations for LoongArch64. + - `limine` host tool: Add check to ensure that a provided GPT BIOS + partition is at least 32KiB in size. + +2025-08-07 Mintsuki + + *** Release 9.6.0 *** + + Noteworthy changes compared to the previous release, 9.5.4: + + Miscellaneous: + - Get rid of the fragile and messy "freestanding-toolchain" build + helper tool. This allows for a simpler and more predictable way of + selecting the target toolchain components via ./configure. + +2025-08-06 Mintsuki + + *** Release 9.5.4 *** + + Noteworthy changes compared to the previous release, 9.5.3: + + Bug fixes: + - Exclude non-PCI devices from the volume discovery process on UEFI. + This is because on (especially, but not limited to) Snapdragon X + systems, firmware/flash devices, which are non-PCI, are exposed as + block I/O volumes too. This skips them, which makes the bootloader + more reliable and avoids long hangs at boot. + - Miscellaneous changes and fixes to the fallback volume discovery + path, to increase reliability and reduce the chance of accidental + damage or data loss. See GitHub issue #525. + - Minor assembly improvement that fixed Limine not building with old + NASM. + +2025-08-04 Mintsuki + + *** Release 9.5.3 *** + + Noteworthy changes compared to the previous release, 9.5.2: + + Bug fixes: + - Miscellaneous build system fixes, mainly to sort out possible issues + introduced by 9.5.2. + +2025-08-01 Mintsuki + + *** Release 9.5.2 *** + + Noteworthy changes compared to the previous release, 9.5.1: + + Bug fixes: + - Revert change that moved to `nm` from `objdump` for generating the + symbol table, as that broke section-specific symbol dumps and caused + BIOS stage 2 to be bloated with the full symbol table. + + Miscellaneous: + - Miscellaneous build system updates and improvements. + +2025-07-29 Mintsuki + + *** Release 9.5.1 *** + + Noteworthy changes compared to the previous release, 9.5.0: + + Bug fixes: + - Unbreak build using ld.gold as linker. + + Miscellaneous: + - Miscellaneous build system updates and improvements. + +2025-07-14 Mintsuki + + *** Release 9.5.0 *** + + Noteworthy changes compared to the previous release, 9.4.0: + + New features: + - protocols/efi: Hybridise loading mechanism with old memory-device + based approach. This may work around the redundant requirement that + EFI applications be signed, and it may fix certain issues created + when the chainload mechanism was changed in 9.0.1. + + Bug fixes: + - `limine` host tool: Fix some mismatched printf format specifiers. + + Miscellaneous: + - Miscellaneous build system updates and improvements. + - Many dependencies had their commits bumped, some new dependencies + were added (limine-protocol), some were changed (libfdt moved to a + different repository that only includes BSD-2-Clause licensable + code). + +2025-07-10 Mintsuki + + *** Release 9.4.0 *** + + Noteworthy changes compared to the previous release, 9.3.4: + + New features: + - BLI: Implement `LoaderTimeInitUSec` and `LoaderTimeExecUSec`. + These are variables used by `systemd-analyze` and are useful for boot + performance metrics. + - Limine boot protocol: Add bootloader performance feature. + This feature provides the same information as the new Boot Loader + Interface variables, but encoded in a nicer way for Limine protocol + executables. + + Miscellaneous: + - Use `nm` instead of `objdump` for generating the symbol table, which + results in a better, more consistent output, especially for non-x86 + architectures. + - Miscellaneous build system updates and improvements. + +2025-06-21 Mintsuki + + *** Release 9.3.4 *** + + Noteworthy changes compared to the previous release, 9.3.3: + + Bug fixes: + - ELF: Make an optimisation related to skipping scanning of .bss + regions for finding of structures in the loaded image (for Limine + protocol structures or otherwise) less overeager as that could break + in certain cases. + - SMP: On x86, ensure that the value of IA32_APIC_BASE is the same on + the APs as it is on the BSP. + - PMM: Mark EfiLoader{Code,Data} regions as bootloader reclaimable + rather than reserved memory, as for certain protocols, like the + Limine boot protocol, reserved memory is unmapped at runtime, while + these regions may contain hot data that is still needed, like Limine + bootloader memory stacks. + +2025-05-27 Mintsuki + + *** Release 9.3.3 *** + + Noteworthy changes compared to the previous release, 9.3.2: + + Bug fixes: + - Reinstate the limit for scanned drives to 0xf0. This is due to a + report of int 13h hangs when used on drives >=0xf0. + +2025-05-19 Mintsuki + + *** Release 9.3.2 *** + + Noteworthy changes compared to the previous release, 9.3.1: + + Bug fixes: + - PMM: Mark EfiBootServices{Code,Data} memory regions as bootloader + reclaimable instead of usable free memory as apparently the ACPI + and UEFI specifications make no guarantees about useful structures + being left in these areas after leaving boot services (namely BGRT + and ESRT). + +2025-05-15 Mintsuki + + *** Release 9.3.1 *** + + Noteworthy changes compared to the previous release, 9.3.0: + + Bug fixes: + - Remove .git directory mistakenly left over inside release tarballs. + - Fix bug that would lead to panics when booting Linux protocol kernels + without modules on x86. + +2025-05-07 Mintsuki + + *** Release 9.3.0 *** + + Noteworthy changes compared to the previous release, 9.2.3: + + New features: + - Add optional support for the 64-bit x86-64 boot protocol for Linux. + This is supported on the UEFI/x86-64 port of Limine, and it is + preferred over the 32-bit boot protocol if supported by the given + kernel. This helps with loading larger modules on certain systems + with low amounts of low memory which would otherwise panic. + + Bug fixes: + - Fix away some ungraceful handling of out-of-memory situations when + loading files using the Linux boot protocol. Make into panics instead + of crashes or hangs. + + Miscellaneous: + - Improve performance and memory usage of loading modules for the Linux + boot protocol. + +2025-04-12 Mintsuki + + *** Release 9.2.3 *** + + Noteworthy changes compared to the previous release, 9.2.2: + + Bug fixes: + - Fix issues introduced by the stb_image fix introduced in 9.2.1 which + could cause hangs or crashes when certain wallpaper images are used. + + Miscellaneous: + - Update cc-runtime to new, integer-only, packed version. + +2025-03-31 Mintsuki + + *** Release 9.2.2 *** + + Noteworthy changes compared to the previous release, 9.2.1: + + Bug fixes: + - Two quick bug fixes and workarounds in order to make multiboot2 more + resilient when booting specific kernels such as Xen. + - Avoid mistakenly allocating more memory than necessary when creating + the volume index. + +2025-03-25 Mintsuki + + *** Release 9.2.1 *** + + Noteworthy changes compared to the previous release, 9.2.0: + + Bug fixes: + - Fix potential buffer overflow bug with our stb_image support code. + + Miscellaneous: + - Dynamically allocate volume index instead of relying on a hard coded + limit and a fixed allocation. This fixes potential panics or failure + to access volumes on systems with a lot of drives/partitions. + - Limine boot protocol/ELF: Print the name of unresolved symbols on + panic. + +2025-03-16 Mintsuki + + *** Release 9.2.0 *** + + Noteworthy changes compared to the previous release, 9.1.3: + + New features: + - Introduce initial support for the Boot Loader Interface + specification. + - Add /EFI/limine/limine.conf to the paths tried for config file when + using EFI. + + Miscellaneous: + - Do not try config path /EFI/BOOT/limine.conf unless booted using EFI. + +2025-03-13 Mintsuki + + *** Release 9.1.3 *** + + Noteworthy changes compared to the previous release, 9.1.2: + + Bug fixes: + - Loongarch64: Fix alignment of TLB-refill handler. + + Miscellaneous: + - Many fixes and improvements to documentation, installed or otherwise, + including further licensing clarifications. + +2025-03-11 Mintsuki + + *** Release 9.1.2 *** + + Noteworthy changes compared to the previous release, 9.1.1: + + This release does not change anything code-wise, but rather it focuses on + sorting out the licensing mess of Nyu-EFI, by updating it to the latest + revision which tackles this problem, and furthermore it improves the + COPYING file to be more explicit about submodules and their licensing. + +2025-03-10 Mintsuki + + *** Release 9.1.1 *** + + Noteworthy changes compared to the previous release, 9.1.0: + + Bug fixes: + - Do not use ConOut for serial output exclusively unless serial is + verified to be present to the best of our abilities. This fixes an + issue where if `serial=yes` is used on some machines (including + several Apple Mac models) a black screen will be shown instead of the + Limine menu. + + Miscellaneous: + - Begin signing tarballs and (most) commits. + +2025-03-07 Mintsuki + + *** Release 9.1.0 *** + + Noteworthy changes compared to the previous release, 9.0.1: + + New features: + - `limine` host tool: When using the `bios-install` command on an + ISOHYBRID with a GPT, automatically convert it to MBR, if possible + and unless explicitly disabled, in order to enhance compatibility of + said ISOHYBRIDs with many systems (of note, Lenovo Thinkpads). + +2025-02-25 Mintsuki + + *** Release 9.0.1 *** + + Noteworthy changes compared to the previous release, 9.0.0: + + Miscellaneous: + - EFI chainload: Replace old chainload method with one that is more + friendly to other EFI bootloaders such as rEFInd. + - Limine boot protocol/PE: Map previously unmapped PE image headers. + +2025-02-17 Mintsuki + + *** Release 9.0.0 *** + + Noteworthy changes compared to the previous release, 8.7.0: + + Breaking changes: + - Support for the ext2/3/4 filesystems has been once again dropped due + to lack of maintenance and to avoid extra potential attack surface + during Secure Boot Limine usage. + - Drop support for the legacy config format (limine.cfg) and only keep + around the new one (limine.conf) introduced with Limine 8.x. + - Drop support for the (barely worked and seldom used) `chainload_next` + protocol. + + New features: + - Limine boot protocol: Introduce API revision 3, for the `limine.h` + file with several renamed identifiers for better clarity. + - Limine boot protocol: Specify and implement a new, convenience, + "Executable Command Line feature". + - Limine boot protocol: Use monitor/mwait CPU instructions, when + available, to reduce CPU usage and increase performance during + multiprocessor initialisation. + - Add preferred aliases for BIOS and EFI chainload (namely `bios` and + `efi`) protocol names for the `protocol` config option. + + Bug fixes: + - EFI (chainload) boot protocol: Fix `path` option alias not working. + + Miscellaneous: + - Update Flanterm submodule to latest. + +2025-01-10 Mintsuki + + *** Release 8.7.0 *** + + Noteworthy changes compared to the previous release, 8.6.1: + + New features: + - Limine boot protocol: Add support for PE executables. + - Linux boot protocol: Enable support for Loongarch64. + - Add support for selecting a random wallpaper every boot if multiple + are provided. + - Add `path` aliases for `kernel_path` and `image_path` for the Linux + and EFI Chainloading protocols respectively. + + Miscellaneous: + - Submodule version/commit bumps. + +2024-12-26 Mintsuki + + *** Release 8.6.1 *** + + Noteworthy changes compared to the previous release, 8.6.0: + + Bug fixes: + - Propagate EFI PXE device handle properly when chainloading. + - Ensure that, when specifying a `dtb_path`, extra space is properly + allocated just as is done for EFI-provided DTBs. + + Miscellaneous: + - Linux on RISCs: General code clean up; pass EFI memory map and initrd + information as EFI configuration tables alongside already passed DTB + parameters for the same, in preparation for loongarch64 support. + +2024-12-05 Mintsuki + + *** Release 8.6.0 *** + + Noteworthy changes compared to the previous release, 8.5.0: + + New features: + - Limine boot protocol: Specify and implement new "RISC-V BSP Hart ID" + feature. + - Limine boot protocol: Add a new "SBI" firmware type to the firmware + type feature + - Limine boot protocol: Add a new API revision 2 to the limine.h header + file. This replaces many mentions of 'kernel' with a more neutral + 'executable' instead. + - Config: add 'path' alias for 'kernel_path' for the Limine and + multiboot1 and 2 protocols. + +2024-11-30 Mintsuki + + *** Release 8.5.0 *** + + Noteworthy changes compared to the previous release, 8.4.1: + + New features: + - Limine boot protocol: Introduce limine.h API revisions to avoid API + breaks. + + Bug fixes: + - Limine boot protocol: Amend base revision 3 to keep EFI memmap + pointer as virtual. Making it physical made no sense and is + henceforth considered a bug. + + Miscellaneous: + - Limine boot protocol: Rename SMP feature to MP feature for limine.h + API revision 1+. + - Limine boot protocol: Make some types that were void pointers into + uint64_t's to better represent their physical nature; limine.h API + revision 1+. + +2024-11-25 Mintsuki + + *** Release 8.4.1 *** + + Noteworthy changes compared to the previous release, 8.4.0: + + Miscellaneous: + - Limine boot protocol: Ensure machines with pre-enabled x2APIC are + properly supported. + - Reuse a viable framebuffer for UEFI fallback terminal output (like on + panics) if boot services have been exited instead of bailing out. + - Miscellaneous build system updates. + +2024-11-08 Mintsuki + + *** Release 8.4.0 *** + + Noteworthy changes compared to the previous release, 8.3.2: + + New features: + - Support passing Limine its configuration via SMBIOS tables. + + Miscellaneous: + - Update cc-runtime to latest; use non-packed version. + +2024-10-31 Mintsuki + + *** Release 8.3.2 *** + + Noteworthy changes compared to the previous release, 8.3.1: + + Miscellaneous: + - Limine boot protocol: Amend base revision 3 to mandate reporting base + revision used to kernels. + +2024-10-28 Mintsuki + + *** Release 8.3.1 *** + + Noteworthy changes compared to the previous release, 8.3.0: + + Bug fixes: + - Limine boot protocol: Revert broken optimisation. + +2024-10-28 Mintsuki + + *** Release 8.3.0 *** + + Noteworthy changes compared to the previous release, 8.2.0: + + New features: + - Limine boot protocol: Specify and implement base revision 3. See the + specification for further info. + - Add a new `FW_TYPE` built-in config macro. + + Miscellaneous: + - Bump Flanterm revision to a6f6edd6631c01caab932a59dce97bbd5f0c72c8. + - Limine boot protocol: Misc optimisations to page tables usage. + +2024-10-23 Mintsuki + + *** Release 8.2.0 *** + + Noteworthy changes compared to the previous release, 8.1.2: + + New features: + - Add device tree blob override configuration options for the Limine + and Linux boot protocols. + - Limine boot protocol: Filter `memory@...` nodes out of device trees. + + Miscellaneous: + - Documentation: USAGE.md: Update `xorriso` command to produce + ISOHYBRIDs with Joliet info and an HFS+ filesystem for broader + compatibility. + - Increase the size of the `limine-cd-uefi.bin` image to 5760 sectors; + this allows more and/or larger EFI executables to fit (like for + example those generated when compiling with -O0). + - Bump cc-runtime revision to d5425655388977fa12ff9b903e554a20b20c426e. + +2024-10-15 Mintsuki + + *** Release 8.1.2 *** + + Noteworthy changes compared to the previous release, 8.1.1: + + Bug fixes: + - Add missing register saves and restores around call from assembly to + C code for the x86-64 UEFI port. This bug could have caused crashes + with certain compiler optimisation levels (like -O0). + - Adjust size of reserved area of VBE mode info struct to be the right + size. This bug could have caused crashes with certain compiler + optimisation levels (like -O0). + - Ignore UEFI memory map entries of length 0. This fixes crashes that + could have happened on quirky UEFI firmwares passing such entries. + +2024-10-12 Mintsuki + + *** Release 8.1.1 *** + + Noteworthy changes compared to the previous release, 8.1.0: + + Bug fixes: + - Limine boot protocol: Do not hard depend on the CPU supporting the + Page Attribute Table (PAT) *for SMP processors as well*. + +2024-10-12 Mintsuki + + *** Release 8.1.0 *** + + Noteworthy changes compared to the previous release, 8.0.14: + + Bug fixes: + - Limine boot protocol: Do not hard depend on the CPU supporting the + Page Attribute Table (PAT). + + New features: + - Limine boot protocol: Add a `randomise_hhdm_base` config file option. + + Miscellaneous: + - Update libfdt (from dtc) to 1.7.1. + +2024-09-29 Mintsuki + + *** Release 8.0.14 *** + + Noteworthy changes compared to the previous release, 8.0.13: + + Miscellaneous: + - Limine boot protocol: Increase maximum memory map entry count from + 256 to 1024 as it would otherwise cause issues on certain systems. + +2024-09-18 Mintsuki + + *** Release 8.0.13 *** + + Noteworthy changes compared to the previous release, 8.0.12: + + Bug fixes: + - multiboot2: Fix handling of relocatable kernels. + +2024-09-15 Mintsuki + + *** Release 8.0.12 *** + + Noteworthy changes compared to the previous release, 8.0.11: + + Bug fixes: + - Fix a build system problem that could cause configure failures if + STRIP variable set to an absolute path. + + Miscellaneous: + - Updates for "freestanding-headers" being rebranded to + "freestnd-c-hdrs-0bsd". + - Updates for "limine-efi" being rebranded to "nyu-efi". + +2024-09-10 Mintsuki + + *** Release 8.0.11 *** + + Noteworthy changes compared to the previous release, 8.0.10: + + Miscellaneous: + - Replace "freestnd-c-hdrs" back with "freestanding-headers" due to + GPLv3 with Runtime Exceptions licensing concerns and size. + +2024-09-09 Mintsuki + + *** Release 8.0.10 *** + + Noteworthy changes compared to the previous release, 8.0.9: + + Bug fixes: + - Fix bug where contents of loaded EFI image size variable could be off + significantly compared to actual size when compiled with GCC, causing + many issues. + - Fix memory manager bug introduced in 2021 during optimisation work. + + Miscellaneous: + - Replace "freestanding-headers" with GCC-provided "freestnd-c-hdrs". + +2024-09-04 Mintsuki + + *** Release 8.0.9 *** + + Noteworthy changes compared to the previous release, 8.0.8: + + Bug fixes: + - Fix an issue that could cause Limine to not boot on UEFI systems with + too many memory map entries. + +2024-09-03 Mintsuki + + *** Release 8.0.8 *** + + Noteworthy changes compared to the previous release, 8.0.7: + + Bug fixes: + - Work around makefile issues that caused GNU make versions 4.0 and 4.1 + as well as version 3.80 to fail to build Limine. + +2024-09-02 Mintsuki + + *** Release 8.0.7 *** + + Noteworthy changes compared to the previous release, 8.0.6: + + Miscellaneous: + - Remove GNU make version check as we now support building on older + make versions. + - Miscellaneous build system improvements. + +2024-08-25 Mintsuki + + *** Release 8.0.6 *** + + Noteworthy changes compared to the previous release, 8.0.5: + + Bug fixes: + - Fix issue with makefiles that would cause, among others, issues with + job control on older GNU make versions. + + Miscellaneous: + - Update limine-efi dependency to latest. + - Minor build system improvements. + +2024-08-12 Mintsuki + + *** Release 8.0.5 *** + + Noteworthy changes compared to the previous release, 8.0.4: + + Bug fixes: + - Limine boot protocol: Obtain LAPIC ID from LAPIC instead of CPUID. + This fixes crashes on some odd machines whose firmware remaps the + BSP's LAPIC ID from the default one. + +2024-08-10 Mintsuki + + *** Release 8.0.4 *** + + Noteworthy changes compared to the previous release, 8.0.3: + + Bug fixes: + - Limine boot protocol: Fix off-by-1 error in HHDM MAXPHYADDR bounds + check. + + Miscellaneous: + - Updated cc-runtime dependency to latest, packed version. + +2024-08-09 Mintsuki + + *** Release 8.0.3 *** + + Noteworthy changes compared to the previous release, 8.0.2: + + Bug fixes: + - AArch64, riscv64: SMP: Fix BSP potentially timing out while waiting + for APs due to missing delay. + +2024-08-08 Mintsuki + + *** Release 8.0.2 *** + + Noteworthy changes compared to the previous release, 8.0.1: + + Bug fixes: + - Menu: Miscellaneous cosmetic fixes. + - Editor: Fix input and displaying of tab characters. + + Miscellaneous: + - Config: Accept tab characters as whitespace after option colon. + +2024-08-05 Mintsuki + + *** Release 8.0.1 *** + + Noteworthy changes compared to the previous release, 8.0.0: + + Bug fixes: + - Assortment of menu fixes to issues that could cause things not to + render as they should. + + Miscellaneous: + - Documentation fixes for 8.x. + +2024-08-02 Mintsuki + + *** Release 8.0.0 *** + + Noteworthy changes compared to the previous release, 7.13.3: + + New features: + - Add loongarch64 support. + + Bug fixes: + - Limine boot protocol: Ensure higher half direct map size does not + exceed MAXPHYADDR on x86. + + Miscellaneous: + - Extensive changes to the config syntax. (See CONFIG.md). + - Rename term_wallpaper, term_wallpaper_style, and term_backdrop config + options to remove the term_ prefix. + - Removed support for GZ-compressed files (and internal Limine + protocol modules). + +2024-08-02 Mintsuki + + *** Release 7.13.3 *** + + Noteworthy changes compared to the previous release, 7.13.2: + + Bug fixes: + - Limine boot protocol: Fix broken comparison that could cause an HHDM + too large to not be properly detected. + - Limine boot protocol: aarch64: Fix incorrect macro value that would + cause the size of the higher half to be incorrectly detected as half. + + Miscellaneous: + - Remove Nix flake stuff from repository/tarball. + +2024-07-30 Mintsuki + + *** Release 7.13.2 *** + + Noteworthy changes compared to the previous release, 7.13.1: + + Bug fixes: + - Limine boot protocol: Fix a bug introduced in 7.13.0 that made + compressed modules not work on IA-32 ports. + +2024-07-29 Mintsuki + + *** Release 7.13.1 *** + + Noteworthy changes compared to the previous release, 7.13.0: + + Bug fixes: + - Limine boot protocol: Fix a bug introduced in 7.13.0 that made the + protocol unusable with the BIOS port when loading any modules, when + Limine was linked using ld.bfd. + +2024-07-29 Mintsuki + + *** Release 7.13.0 *** + + Noteworthy changes compared to the previous release, 7.12.1: + + New features: + - Limine boot protocol: Allow above-4GiB load of modules on IA-32 + ports. + + Miscellaneous: + - Build system improvements. + +2024-07-27 Mintsuki + + *** Release 7.12.1 *** + + Noteworthy changes compared to the previous release, 7.12.0: + + Miscellaneous: + - Limine boot protocol: limit the HHDM size to half of the higher half + in all conditions; limit HHDM KASLR wiggle room to a quarter rather + than half of the higher half. + - Build system improvements. + +2024-07-21 Mintsuki + + *** Release 7.12.0 *** + + Noteworthy changes compared to the previous release, 7.11.0: + + New features: + - Reinstate support for ext2/3/4, albeit in an unsupported and + unmaintained state, at least for the time being. + - Menu: Add vertical bars around entry title in entry editor. + - Menu: Add clearer indications of what is wrong when config invalid or + missing. + + Bug fixes: + - Terminal: Fix long standing wallpaper drawing related bug that under + certain circumstances could cause memory corruption. + - Miscellaneous VBE related bug fixes. + - Miscellaneous A20 line related improvements/fixes. + + Miscellaneous: + - Limine boot protocol: perform appropriate checks to ensure that the + higher half direct map fits in the higher half; enforce paging modes + as needed. + - Limine boot protocol: aarch64: Specify that the granule size for both + TTBR0_EL1 and TTBR1_EL1 is 4KiB. + - Limine boot protocol: aarch64: Specify the values of TCR_EL1.T0SZ and + TCR_EL1.T1SZ depending on the paging mode used. + - Limine boot protocol: aarch64: Specify the condition of TTBR0_EL1 and + TTBR1_EL1 at entry. + +2024-07-18 Mintsuki + + *** Release 7.11.0 *** + + Noteworthy changes compared to the previous release, 7.10.3: + + New features: + - Limine boot protocol: Specify and implement "firmware type" feature. + - Limine boot protocol: Specify and implement revision 1 "paging mode" + request extension. + +2024-07-16 Mintsuki + + *** Release 7.10.3 *** + + Noteworthy changes compared to the previous release, 7.10.2: + + Bug fixes: + - Menu: Fix high menu entry count support that was previously broken in + a regression. + - Linux boot protocol: Install "memory reservation" EFI configuration + table on aarch64/riscv64. + + Miscellaneous: + - Miscellaneous improvements and optimisations of code introduced in + version 7.10.2. + +2024-07-14 Mintsuki + + *** Release 7.10.2 *** + + Noteworthy changes compared to the previous release, 7.10.1: + + Bug fixes: + - Fix a long standing issue with volume detection on UEFI. + - Fix issue returning to editor after a panic when coming from a blank + entry without a valid config. + - Add missing "loading" prints when booting Linux on non-x86. + +2024-07-12 Mintsuki + + *** Release 7.10.1 *** + + Noteworthy changes compared to the previous release, 7.10.0: + + Bug fixes: + - DTB: Never pass UEFI DTB buffer directly; fix misuse of libfdt API. + +2024-07-12 Mintsuki + + *** Release 7.10.0 *** + + Noteworthy changes compared to the previous release, 7.9.2: + + New features: + - SMP: Support SMP on AArch64 without ACPI. + + Bug fixes: + - SMP: Do not allocate a useless stack for the BSP. + - `limine`: Protect against overwriting MBR partitions beginning at + exceedingly low sector values (outside de facto specification). + + Miscellaneous: + - Limine boot protocol: Do not pass responses if RSDP or SMBIOS not + available instead of returning responses with NULL pointers. + - Limine boot protocol: Do not pass a framebuffer response if no + framebuffers are available. + +2024-07-06 Mintsuki + + *** Release 7.9.2 *** + + Noteworthy changes compared to the previous release, 7.9.1: + + Bug fixes: + - Limine boot protocol: Return NULL response instead of a response with + erroneous CPU count of 0 for SMP on x86-64, if x2APIC support not + enabled and missing xAPIC fallback. + - Limine boot protocol: Do not ignore MAX_PAGING_MODE config setting if + paging mode request missing from loaded kernel. + + Miscellaneous: + - Limine boot protocol/ELF: Apply a slide of 0xffffffff80000000 minus + the ELF-reported base load address for relocatable kernels that have + lower half load addresses, instead of always applying a slide of + 0xffffffff80000000. + +2024-06-29 Mintsuki + + *** Release 7.9.1 *** + + Noteworthy changes compared to the previous release, 7.9.0: + + Bug fixes: + - `limine`: Revert change that marked protective MBR wrapper partition + as active on GPT. + +2024-06-28 Mintsuki + + *** Release 7.9.0 *** + + Noteworthy changes compared to the previous release, 7.8.0: + + New features: + - Linux boot protocol: Add support on aarch64 and riscv64. + + Bug fixes: + - multiboot1/2: Relax ELF file type requirements (mainly for multiboot2 + relocatable kernels). + - UEFI: Fix bug that caused delete key to behave as if it was backspace + even when the SERIAL config option was not "yes". + + Miscellaneous: + - Limine boot protocol/ELF: Do not consider ELFs relocatable unless the + ELF file type is ET_DYN. + - Some dead code removal. + +2024-06-24 Mintsuki + + *** Release 7.8.0 *** + + Noteworthy changes compared to the previous release, 7.7.2: + + New features: + - Return to booted edited entry on panic. + + Bug fixes: + - `limine`: Mark protective MBR wrapper partition as active on GPT. + + Miscellaneous: + - UEFI: Allocate most memory as EfiLoaderCode instead of EfiLoaderData. + - Smaller fixes and improvements. + +2024-06-09 Mintsuki + + *** Release 7.7.2 *** + + Noteworthy changes compared to the previous release, 7.7.1: + + Bug fixes: + - Limine boot protocol/ELF: Ignore PT_LOAD segments with 0 memory size. + - Limine boot protocol/ELF: Panic on non-weak unresolved symbols. + - Limine boot protocol/ELF: Do not sanity check DT_RELAENT size for non + DT_RELA relocations. This is because DT_RELAENT may not be present. + - multiboot 1 and 2 protocols: Reject relocatable ELFs. + +2024-06-06 Mintsuki + + *** Release 7.7.1 *** + + Noteworthy changes compared to the previous release, 7.7.0: + + Bug fixes: + - Limine boot protocol/ELF: Stop parsing PT_DYNAMIC segment once + DT_NULL is reached. + +2024-06-04 Mintsuki + + *** Release 7.7.0 *** + + Noteworthy changes compared to the previous release, 7.6.0: + + New features: + - Limine boot protocol/ELF: Add support for GLOB_DAT, JUMP_SLOT, 64, + and NONE relocation types. + - Limine boot protocol/ELF: Add RELR (packed relative relocations) + support. + + Bug fixes: + - Limine boot protocol/ELF: Reject ELFs that depend on external + dynamically linked libraries. + + Miscellaneous: + - Bring back .xz compressed release tarball. + +2024-06-01 Mintsuki + + *** Release 7.6.0 *** + + Noteworthy changes compared to the previous release, 7.5.3: + + New features: + - RISC-V: Add support for RISC-V systems without ACPI (using DTBs). + +2024-05-24 Mintsuki + + *** Release 7.5.3 *** + + Noteworthy changes compared to the previous release, 7.5.2: + + Bug fixes: + - Multiboot 2 protocol: Fix an issue where the 32-bit EFI Image Handle + tag would be generated under 64-bit EFI and vice versa. + - Do not set VirtualStart to PhysicalStart when generating the EFI + memory map; set it to 0 instead. + - Do not perform additional checks when loading relocatable ELF files + after verifying that they are of type ET_DYN and have a PT_DYNAMIC + segment. This ensures that certain relocatable ELF files that were + previously misidentified as non-relocatable are recognised as such. + +2024-05-22 Mintsuki + + *** Release 7.5.2 *** + + Noteworthy changes compared to the previous release, 7.5.1: + + Bug fixes: + - Fix missing checks in the FAT filesystem driver that could cause + divide-by-0 exceptions when parsing malformed/corrupted filesystems. + - Limine boot protocol: Do not reject relocatable kernels with ELF PHDR + load addresses at or around 0, instead slide them all the way to the + topmost 2GiB (0xffffffff80000000). This allows setting a base load + address of 0 in LD BFD/LLD linker scripts which makes LD BFD (the + default GNU LD) actually emit an ELF with ET_DYN type. + +2024-05-08 Mintsuki + + *** Release 7.5.1 *** + + Noteworthy changes compared to the previous release, 7.5.0: + + Bug fixes: + - Workarounds for issues related to drive iteration on BIOS. Discovered + on an HP Pavilion dv6-3152er. + +2024-05-03 Mintsuki + + *** Release 7.5.0 *** + + Noteworthy changes compared to the previous release, 7.4.1: + + New features: + - Limine boot protocol: Add support for requests start marker, to + complement the previously called delimiter, now renamed to "end + marker". + - Limine boot protocol: Add base revision 2. The sole difference + compared to base revision 1 being that it mandates bootloaders to + support request delimiters. + + Bug fixes: + - Fix an issue regarding commit date parsing in the bootstrap script. + + Miscellaneous: + - General documentation updates and improvements; split USAGE.md from + README.md and install the former only. + +2024-04-29 Mintsuki + + *** Release 7.4.1 *** + + Noteworthy changes compared to the previous release, 7.4.0: + + Miscellaneous: + - Bump max volume limit for UEFI from 64 to 256. + - Improve how ISA name and firmware type is reported as part of the + default branding. + +2024-04-20 Mintsuki + + *** Release 7.4.0 *** + + Noteworthy changes compared to the previous release, 7.3.1: + + New features: + - ISA name and firmware type is now printed as part of the default + bootloader branding. + - Add 'B' key shortcut to menu to open up a blank entry. + + Bug fixes: + - Fix missing NULL pointer check in configuration related function. + + Miscellaneous: + - Console has been removed. + +2024-04-11 Mintsuki + + *** Release 7.3.1 *** + + Noteworthy changes compared to the previous release, 7.3.0: + + Bug fixes: + - Fix a minor issue with scrolling when using the EFI console fallback. + + Miscellaneous: + - Bump the Flanterm revision to current latest. + - Some dead code removal. + +2024-03-31 Mintsuki + + *** Release 7.3.0 *** + + Noteworthy changes compared to the previous release, 7.2.1: + + New features: + - Limine boot protocol: Add a `MAX_PAGING_MODE` configuration option + to override kernel/bootloader selected paging mode. + + Bug fixes: + - Fix a couple of minor bugs with the 4GiB workaround introduced in + version 7.2.1. + + Miscellaneous: + - No longer produce xz compressed release tarballs. Add bzip2, lzip, + and zstd as alternatives in its place. + +2024-03-28 Mintsuki + + *** Release 7.2.1 *** + + Noteworthy changes compared to the previous release, 7.2.0: + + Bug fixes: + - Implement workaround to allow Limine loaded above 4GiB by UEFI on + x86-64 to work in some configurations, instead of panicking. + +2024-03-24 Mintsuki + + *** Release 7.2.0 *** + + Noteworthy changes compared to the previous release, 7.1.0: + + New features: + - Add support for remembering the last booted entry on UEFI by means + of the `REMEMBER_LAST_ENTRY` config option. + +2024-03-19 Mintsuki + + *** Release 7.1.0 *** + + Noteworthy changes compared to the previous release, 7.0.5: + + New features: + - Limine boot protocol: Add support for requests delimiter. + + Bug fixes: + - Fixed a potential security vulnerability in the ISO9660 driver. + +2024-02-15 Mintsuki + + *** Release 7.0.5 *** + + Noteworthy changes compared to the previous release, 7.0.4: + + Bug fixes: + - Build system related fixes and improvements. This fixes an issue that + caused Limine 7.0.4 to fail building on Busybox systems. + +2024-02-14 Mintsuki + + *** Release 7.0.4 *** + + Noteworthy changes compared to the previous release, 7.0.3: + + Bug fixes: + - Minor build system related fixes and improvements. + + Miscellaneous: + - Significantly improve reproducibility of builds. + +2024-02-08 Mintsuki + + *** Release 7.0.3 *** + + Noteworthy changes compared to the previous release, 7.0.2: + + Bug fixes: + - multiboot2 protocol: Do not panic if there is a ENTRY_ADDRESS_EFI64 + tag, but there is a valid alternative entry point. This further aligns + our behaviour with GRUB2. + + Miscellaneous: + - Terminal: Significantly improve serial and fallback UEFI console's + rendition (especially colour palettes). + - Serial: Add a baud rate config setting for Limine's BIOS version. + +2024-01-26 Mintsuki + + *** Release 7.0.2 *** + + Noteworthy changes compared to the previous release, 7.0.1: + + Bug fixes: + - Fix a cosmetic bug in the menu where, when the editor was disabled, + the controls showed "ENTER Expand" even for boot entries instead of + only for menu directories. + + Miscellaneous: + - Exclusively use git for obtaining dependencies in "bootstrap" script. + +2024-01-22 Mintsuki + + *** Release 7.0.1 *** + + Noteworthy changes compared to the previous release, 7.0.0: + + Bug fixes: + - Fix ARM64/aarch64 spinup code to properly support Apple Silicon + chips. + - Prevent users from building Limine with a GNU Make version older than + 4.2, as that is not supported, by gracefully erroring out with a + useful error message. + - Avoid usage of reserved C identifiers in certain cases. + + Miscellaneous: + - Drop usage of "libgcc-binaries" for "cc-runtime". This finally makes + us able to ship Limine tarballs without included binary blobs, and + should improve compatibility with more obscure toolchain set ups. + +2024-01-14 Mintsuki + + *** Release 7.0.0 *** + + Noteworthy changes compared to the previous release, 6.20240107.0: + + Bug fixes: + - Minor fix of newline handling when reading user input lines. + + Miscellaneous: + - Bump major version to 7. Note: There is no breaking change in 7.x + compared to 6.x; the major version bump is done due to a change in + the versioning scheme used by Limine (move to proper Semantic + Versioning). + - Fix versions of external dependencies in "bootstrap" script. + +2024-01-07 Mintsuki + + *** Release 6.20240107.0 *** + + Noteworthy changes compared to the previous release, 6.20231227.0: + + New features: + - Limine boot protocol: Add support for compressed internal modules. + + Miscellaneous: + - Improve error reporting from host "limine" executable. + - Track "master" branch of stb_image upstream. + +2023-12-27 Mintsuki + + *** Release 6.20231227.0 *** + + Noteworthy changes compared to the previous release, 6.20231226.0: + + Bug fixes: + - Actually regenerate build system with GNU Autoconf 2.72 instead of + GNU Autoconf 2.71. + +2023-12-26 Mintsuki + + *** Release 6.20231226.0 *** + + Noteworthy changes compared to the previous release, 6.20231216.0: + + New features: + - Ignore trailing whitespace in Limine config file. + + Miscellaneous: + - Regenerate build system with GNU Autoconf 2.72. + - Minor build system adjustments. + +2023-12-16 Mintsuki + + *** Release 6.20231216.0 *** + + Noteworthy changes compared to the previous release, 6.20231210.0: + + New features: + - Added a `--print-datadir` switch to the `limine` program in order + to print the directory where Limine files are stored when installed + on a host distro. + + Miscellaneous: + - General improvements to the `--help` texts for the `limine` program. + +2023-12-10 Mintsuki + + *** Release 6.20231210.0 *** + + This is the first release in the 6.x series. + + Breaking changes compared to the 5.x series: + + - Support for the unmaintained ext2/3/4 filesystems dropped in compliance + with the Limine bootloader design philosophy. + - Chainloading protocol split into 2 protocols: EFI chainloading and BIOS + chainloading. + +2023-12-07 Mintsuki + + *** Release 5.20231207.1 *** + + Noteworthy changes compared to the previous release, 5.20231207.0: + + Bug fixes: + - multiboot2/elf: Fix issue where ELF vaddrs were taken into account + instead of paddrs when loading executables. + - Fix implementation of a function that detects whether a given memory + range is physical RAM. + +2023-12-07 Mintsuki + + *** Release 5.20231207.0 *** + + Noteworthy changes compared to the previous release, 5.20231124.0: + + New features: + - multiboot2 protocol: Add support for relocatable header tag. + - multiboot2 protocol: Add support for console flags header tag. + - Limine protocol: Add support for EFI memory map feature. + + Miscellaneous: + - Improve error reporting from host "limine" executable. + - Do not ignore errors from mtools when building limine-uefi-cd.bin. + +2023-11-24 Mintsuki + + *** Release 5.20231124.0 *** + + Compared to previous release, 5.20231121.0: + + New features: + - Add support for rebooting to UEFI firmware setup. + + Bug fixes: + - Fix SMP (multiprocessor) initialisation regression on aarch64. + + Miscellaneous: + - Move to new, handwritten ChangeLog from autogenerated one. diff --git a/limine/FAQ.md b/limine/FAQ.md new file mode 100644 index 0000000..1516078 --- /dev/null +++ b/limine/FAQ.md @@ -0,0 +1,35 @@ +# Frequently Asked Questions + +### Why not support filesystem X or feature Y? (eg: LUKS, LVM) + +The idea with Limine is to remove the responsibility of parsing filesystems and +formats, aside from the bare minimum necessities (eg: FAT*, ISO9660), from the +bootloader itself. It is a needless duplication of efforts to have bootloaders +support all possible filesystems and formats, and it leads to massive, bloated +bootloaders as a result (eg: GRUB2). + +What is needed is to simply make sure the bootloader is capable of reading its +own files, configuration, and be able to load kernel/module files from disk. +The kernel should be responsible for parsing everything else as it sees fit. + +### What about LUKS? What about security? Encrypt the kernel! + +Simply put, this is unnecessary. Putting the kernel/modules in a readable FAT32 +partition and letting Limine know about their BLAKE2B checksums in the config +file provides as much security as encrypting the kernel does. + +### What if a malicious actor modifies the config file? + +While this is a pointless effort on legacy x86 BIOS, it is a reasonable +expectation to secure the boot sequence on UEFI systems with Secure Boot. +Limine provides a way to modify its own EFI executable to bake in the BLAKE2B +checksum of the config file itself. The EFI executable can then get signed with +a key added to the firmware's keychain. This prevents modifications to the +config file (and in turn the checksums contained there) from going unnoticed. + +### I do not want to have a separate FAT boot partition! What can I do? + +It is `$year_following_2012` now and most PCs are equipped with UEFI and simply +won't boot without a FAT EFI system partition anyways. +It is not unreasonable to share the EFI system partition with the OS's /boot +and store kernels, initramfses, and any other files needed for boot there. diff --git a/limine/GNUmakefile.in b/limine/GNUmakefile.in new file mode 100644 index 0000000..065d0ef --- /dev/null +++ b/limine/GNUmakefile.in @@ -0,0 +1,401 @@ +.SUFFIXES: + +override SOURCE_DATE_EPOCH := @SOURCE_DATE_EPOCH@ +export SOURCE_DATE_EPOCH + +override SOURCE_DATE_EPOCH_TOUCH := @SOURCE_DATE_EPOCH_TOUCH@ + +override PACKAGE_TARNAME := @PACKAGE_TARNAME@ +override PACKAGE_VERSION := @PACKAGE_VERSION@ +override DIST_OUTPUT := $(PACKAGE_TARNAME)-$(PACKAGE_VERSION) + +override prefix := @prefix@ +override exec_prefix := @exec_prefix@ + +override bindir := @bindir@ +override datarootdir := @datarootdir@ +override mandir := @mandir@ +override docdir := @docdir@ + +override BUILDDIR := @BUILDDIR@ +override BINDIR := $(BUILDDIR)/bin + +override SRCDIR := @SRCDIR@ + +override SPACE := $(subst ,, ) +override COMMA := , + +override MKESCAPE = $(subst $(SPACE),\ ,$(1)) +override SHESCAPE = $(subst ','\'',$(1)) +override NASMESCAPE = $(subst ','"'$(COMMA) \"'\"$(COMMA) '"',$(1)) + +override BUILD_BIOS := @BUILD_BIOS@ +override BUILD_UEFI_X86_64 := @BUILD_UEFI_X86_64@ +override BUILD_UEFI_IA32 := @BUILD_UEFI_IA32@ +override BUILD_UEFI_AARCH64 := @BUILD_UEFI_AARCH64@ +override BUILD_UEFI_RISCV64 := @BUILD_UEFI_RISCV64@ +override BUILD_UEFI_LOONGARCH64 := @BUILD_UEFI_LOONGARCH64@ +override BUILD_UEFI_CD := @BUILD_UEFI_CD@ +override BUILD_BIOS_PXE := @BUILD_BIOS_PXE@ +override BUILD_BIOS_CD := @BUILD_BIOS_CD@ + +INSTALL := @INSTALL@ +INSTALL_PROGRAM := @INSTALL_PROGRAM@ +INSTALL_DATA := @INSTALL_DATA@ +STRIP := @STRIP@ + +MKDIR_P := @MKDIR_P@ +export MKDIR_P +GREP := @GREP@ +export GREP +SED := @SED@ +export SED +AWK := @AWK@ +export AWK + +CC := @CC@ + +CPPFLAGS := @CPPFLAGS@ +CFLAGS := @CFLAGS@ +LDFLAGS := @LDFLAGS@ +LIBS := @LIBS@ + +CC_FOR_TARGET := @CC_FOR_TARGET@ +export CC_FOR_TARGET +LD_FOR_TARGET := @LD_FOR_TARGET@ +export LD_FOR_TARGET +OBJCOPY_FOR_TARGET := @OBJCOPY_FOR_TARGET@ +export OBJCOPY_FOR_TARGET +OBJDUMP_FOR_TARGET := @OBJDUMP_FOR_TARGET@ +export OBJDUMP_FOR_TARGET +READELF_FOR_TARGET := @READELF_FOR_TARGET@ +export READELF_FOR_TARGET + +override WERROR_FLAG := @WERROR_FLAG@ +export WERROR_FLAG + +CFLAGS_FOR_TARGET := @CFLAGS_FOR_TARGET@ +export CFLAGS_FOR_TARGET +CPPFLAGS_FOR_TARGET := @CPPFLAGS_FOR_TARGET@ +export CPPFLAGS_FOR_TARGET +LDFLAGS_FOR_TARGET := @LDFLAGS_FOR_TARGET@ +export LDFLAGS_FOR_TARGET +NASMFLAGS_FOR_TARGET := @NASMFLAGS_FOR_TARGET@ +export NASMFLAGS_FOR_TARGET + +override STAGE1_FILES := $(shell find '$(call SHESCAPE,$(SRCDIR))/stage1' -type f -name '*.asm' | LC_ALL=C sort) + +.PHONY: all +all: $(call MKESCAPE,$(BINDIR))/Makefile + $(MAKE) all1 + +.PHONY: all1 +all1: $(BUILD_UEFI_X86_64) $(BUILD_UEFI_IA32) $(BUILD_UEFI_AARCH64) $(BUILD_UEFI_RISCV64) $(BUILD_UEFI_LOONGARCH64) $(BUILD_BIOS) + $(MAKE) '$(call SHESCAPE,$(BINDIR))/limine' + $(MAKE) '$(call SHESCAPE,$(BINDIR))/limine-uefi-cd.bin' + +$(call MKESCAPE,$(BINDIR))/limine-bios-hdd.h: $(call MKESCAPE,$(BINDIR))/limine-bios-hdd.bin + $(MKDIR_P) '$(call SHESCAPE,$(BINDIR))' + cd '$(call SHESCAPE,$(BINDIR))' && '$(call SHESCAPE,$(SRCDIR))/host/hgen.sh' >limine-bios-hdd.h + +override LIMINE_NO_BIOS := +ifneq ($(BUILD_BIOS),limine-bios) +override LIMINE_NO_BIOS := -DLIMINE_NO_BIOS +endif + +$(call MKESCAPE,$(BINDIR))/limine: $(call MKESCAPE,$(BINDIR))/Makefile $(call MKESCAPE,$(SRCDIR))/host/limine.c $(if $(filter $(BUILD_BIOS),limine-bios),$(call MKESCAPE,$(BINDIR))/limine-bios-hdd.h) + $(SED) 's/%VERSION%/@PACKAGE_VERSION@/g;s/%COPYRIGHT%/@LIMINE_COPYRIGHT@/g' <'$(call SHESCAPE,$(SRCDIR))/host/limine.c' >'$(call SHESCAPE,$(BINDIR))/limine.c' + $(MAKE) -C '$(call SHESCAPE,$(BINDIR))' limine \ + CC="$(CC)" \ + CFLAGS="$(CFLAGS) -Wall -Wextra $(WERROR_FLAG)" \ + CPPFLAGS='$(CPPFLAGS) $(LIMINE_NO_BIOS) -DLIMINE_DATADIR=\"$(call SHESCAPE,$(datarootdir))/limine\"' \ + LDFLAGS="$(LDFLAGS)" \ + LIBS="$(LIBS)" + +$(call MKESCAPE,$(BINDIR))/Makefile: $(call MKESCAPE,$(SRCDIR))/host/host.mk $(call MKESCAPE,$(SRCDIR))/host/.gitignore + mkdir -p '$(call SHESCAPE,$(BINDIR))' + cp '$(call SHESCAPE,$(SRCDIR))/host/host.mk' '$(call SHESCAPE,$(BINDIR))/Makefile' + cp '$(call SHESCAPE,$(SRCDIR))/host/.gitignore' '$(call SHESCAPE,$(BINDIR))/' + +.PHONY: limine +limine: + $(MAKE) '$(call SHESCAPE,$(BINDIR))/limine' + +.PHONY: clean +clean: limine-bios-clean limine-uefi-ia32-clean limine-uefi-x86-64-clean limine-uefi-aarch64-clean limine-uefi-riscv64-clean limine-uefi-loongarch64-clean + rm -rf '$(call SHESCAPE,$(BINDIR))' '$(call SHESCAPE,$(BUILDDIR))/stage1.stamp' + +.PHONY: install +install: all + $(INSTALL) -d '$(call SHESCAPE,$(DESTDIR)$(docdir))' + $(INSTALL_DATA) '$(call SHESCAPE,$(SRCDIR))/COPYING' '$(call SHESCAPE,$(DESTDIR)$(docdir))/' + $(INSTALL) -d '$(call SHESCAPE,$(DESTDIR)$(docdir))/LICENSES' + $(INSTALL_DATA) '$(call SHESCAPE,$(SRCDIR))/LICENSES/LicenseRef-scancode-bsd-no-disclaimer-unmodified.txt' '$(call SHESCAPE,$(DESTDIR)$(docdir))/LICENSES/' + $(INSTALL_DATA) '$(call SHESCAPE,$(SRCDIR))/3RDPARTY.md' '$(call SHESCAPE,$(DESTDIR)$(docdir))/' + $(INSTALL_DATA) '$(call SHESCAPE,$(SRCDIR))/CONFIG.md' '$(call SHESCAPE,$(DESTDIR)$(docdir))/' + $(INSTALL_DATA) '$(call SHESCAPE,$(SRCDIR))/FAQ.md' '$(call SHESCAPE,$(DESTDIR)$(docdir))/' + $(INSTALL_DATA) '$(call SHESCAPE,$(SRCDIR))/USAGE.md' '$(call SHESCAPE,$(DESTDIR)$(docdir))/' + $(INSTALL) -d '$(call SHESCAPE,$(DESTDIR)$(mandir))/man1' + $(INSTALL_DATA) '$(call SHESCAPE,$(BUILDDIR))/man/man1/limine.1' '$(call SHESCAPE,$(DESTDIR)$(mandir))/man1/' + $(INSTALL) -d '$(call SHESCAPE,$(DESTDIR)$(datarootdir))' + $(INSTALL) -d '$(call SHESCAPE,$(DESTDIR)$(datarootdir))/limine' +ifeq ($(BUILD_BIOS),limine-bios) + $(INSTALL_DATA) '$(call SHESCAPE,$(BINDIR))/limine-bios.sys' '$(call SHESCAPE,$(DESTDIR)$(datarootdir))/limine/' +endif +ifneq ($(BUILD_BIOS_CD),no) + $(INSTALL_DATA) '$(call SHESCAPE,$(BINDIR))/limine-bios-cd.bin' '$(call SHESCAPE,$(DESTDIR)$(datarootdir))/limine/' +endif +ifneq ($(BUILD_UEFI_CD),no) + $(INSTALL_DATA) '$(call SHESCAPE,$(BINDIR))/limine-uefi-cd.bin' '$(call SHESCAPE,$(DESTDIR)$(datarootdir))/limine/' +endif +ifneq ($(BUILD_BIOS_PXE),no) + $(INSTALL_DATA) '$(call SHESCAPE,$(BINDIR))/limine-bios-pxe.bin' '$(call SHESCAPE,$(DESTDIR)$(datarootdir))/limine/' +endif +ifeq ($(BUILD_UEFI_AARCH64),limine-uefi-aarch64) + $(INSTALL_DATA) '$(call SHESCAPE,$(BINDIR))/BOOTAA64.EFI' '$(call SHESCAPE,$(DESTDIR)$(datarootdir))/limine/' +endif +ifeq ($(BUILD_UEFI_RISCV64),limine-uefi-riscv64) + $(INSTALL_DATA) '$(call SHESCAPE,$(BINDIR))/BOOTRISCV64.EFI' '$(call SHESCAPE,$(DESTDIR)$(datarootdir))/limine/' +endif +ifeq ($(BUILD_UEFI_LOONGARCH64),limine-uefi-loongarch64) + $(INSTALL_DATA) '$(call SHESCAPE,$(BINDIR))/BOOTLOONGARCH64.EFI' '$(call SHESCAPE,$(DESTDIR)$(datarootdir))/limine/' +endif +ifeq ($(BUILD_UEFI_X86_64),limine-uefi-x86-64) + $(INSTALL_DATA) '$(call SHESCAPE,$(BINDIR))/BOOTX64.EFI' '$(call SHESCAPE,$(DESTDIR)$(datarootdir))/limine/' +endif +ifeq ($(BUILD_UEFI_IA32),limine-uefi-ia32) + $(INSTALL_DATA) '$(call SHESCAPE,$(BINDIR))/BOOTIA32.EFI' '$(call SHESCAPE,$(DESTDIR)$(datarootdir))/limine/' +endif + $(INSTALL) -d '$(call SHESCAPE,$(DESTDIR)$(bindir))' + $(INSTALL_PROGRAM) '$(call SHESCAPE,$(BINDIR))/limine' '$(call SHESCAPE,$(DESTDIR)$(bindir))/' + +.PHONY: install-strip +install-strip: install + $(STRIP) '$(call SHESCAPE,$(DESTDIR)$(bindir))/limine' + +.PHONY: uninstall +uninstall: + rm -f '$(call SHESCAPE,$(DESTDIR)$(docdir))/COPYING' + rm -rf '$(call SHESCAPE,$(DESTDIR)$(docdir))/LICENSES' + rm -f '$(call SHESCAPE,$(DESTDIR)$(docdir))/3RDPARTY.md' + rm -f '$(call SHESCAPE,$(DESTDIR)$(docdir))/CONFIG.md' + rm -f '$(call SHESCAPE,$(DESTDIR)$(docdir))/FAQ.md' + rm -f '$(call SHESCAPE,$(DESTDIR)$(docdir))/USAGE.md' + rm -f '$(call SHESCAPE,$(DESTDIR)$(mandir))/man1/limine.1' + rm -f '$(call SHESCAPE,$(DESTDIR)$(bindir))/limine' + rm -rf '$(call SHESCAPE,$(DESTDIR)$(datarootdir))/limine' + +$(call MKESCAPE,$(BUILDDIR))/stage1.stamp: $(STAGE1_FILES) $(call MKESCAPE,$(BUILDDIR))/decompressor-build/decompressor.bin $(call MKESCAPE,$(BUILDDIR))/common-bios/stage2.bin.gz + $(MKDIR_P) '$(call SHESCAPE,$(BINDIR))' + cd '$(call SHESCAPE,$(SRCDIR))/stage1/hdd' && nasm bootsect.asm -Wall -w-unknown-warning -w-reloc $(WERROR_FLAG) -fbin -DBUILDDIR="'"'$(call NASMESCAPE,$(BUILDDIR))'"'" -o '$(call SHESCAPE,$(BINDIR))/limine-bios-hdd.bin' +ifneq ($(BUILD_BIOS_CD),no) + cd '$(call SHESCAPE,$(SRCDIR))/stage1/cd' && nasm bootsect.asm -Wall -w-unknown-warning -w-reloc $(WERROR_FLAG) -fbin -DBUILDDIR="'"'$(call NASMESCAPE,$(BUILDDIR))'"'" -o '$(call SHESCAPE,$(BINDIR))/limine-bios-cd.bin' +endif +ifneq ($(BUILD_BIOS_PXE),no) + cd '$(call SHESCAPE,$(SRCDIR))/stage1/pxe' && nasm bootsect.asm -Wall -w-unknown-warning -w-reloc $(WERROR_FLAG) -fbin -DBUILDDIR="'"'$(call NASMESCAPE,$(BUILDDIR))'"'" -o '$(call SHESCAPE,$(BINDIR))/limine-bios-pxe.bin' +endif + cp '$(call SHESCAPE,$(BUILDDIR))/common-bios/limine-bios.sys' '$(call SHESCAPE,$(BINDIR))/' + touch '$(call SHESCAPE,$(BUILDDIR))/stage1.stamp' + +.PHONY: limine-bios +limine-bios: common-bios decompressor + $(MAKE) '$(call SHESCAPE,$(BUILDDIR))/stage1.stamp' + +$(call MKESCAPE,$(BINDIR))/limine-uefi-cd.bin: $(if $(BUILD_UEFI_IA32),$(call MKESCAPE,$(BUILDDIR))/common-uefi-ia32/BOOTIA32.EFI) $(if $(BUILD_UEFI_X86_64),$(call MKESCAPE,$(BUILDDIR))/common-uefi-x86-64/BOOTX64.EFI) $(if $(BUILD_UEFI_AARCH64),$(call MKESCAPE,$(BUILDDIR))/common-uefi-aarch64/BOOTAA64.EFI) $(if $(BUILD_UEFI_RISCV64),$(call MKESCAPE,$(BUILDDIR))/common-uefi-riscv64/BOOTRISCV64.EFI) $(if $(BUILD_UEFI_LOONGARCH64),$(call MKESCAPE,$(BUILDDIR))/common-uefi-loongarch64/BOOTLOONGARCH64.EFI) +ifneq ($(BUILD_UEFI_CD),no) + $(MKDIR_P) '$(call SHESCAPE,$(BINDIR))' + rm -f '$(call SHESCAPE,$(BINDIR))/limine-uefi-cd.bin' + dd if=/dev/zero of='$(call SHESCAPE,$(BINDIR))/limine-uefi-cd.bin' bs=512 count=5760 2>/dev/null + mformat -i '$(call SHESCAPE,$(BINDIR))/limine-uefi-cd.bin' -f 2880 -N 12345678 :: + LIMINE_UEFI_CD_TMP="$$(mktemp -d)"; \ + mkdir -p "$$LIMINE_UEFI_CD_TMP"/EFI/BOOT; \ + cp '$(call SHESCAPE,$(BUILDDIR))/common-uefi-aarch64/BOOTAA64.EFI' "$$LIMINE_UEFI_CD_TMP"/EFI/BOOT/ 2>/dev/null; \ + cp '$(call SHESCAPE,$(BUILDDIR))/common-uefi-riscv64/BOOTRISCV64.EFI' "$$LIMINE_UEFI_CD_TMP"/EFI/BOOT/ 2>/dev/null; \ + cp '$(call SHESCAPE,$(BUILDDIR))/common-uefi-loongarch64/BOOTLOONGARCH64.EFI' "$$LIMINE_UEFI_CD_TMP"/EFI/BOOT/ 2>/dev/null; \ + cp '$(call SHESCAPE,$(BUILDDIR))/common-uefi-x86-64/BOOTX64.EFI' "$$LIMINE_UEFI_CD_TMP"/EFI/BOOT/ 2>/dev/null; \ + cp '$(call SHESCAPE,$(BUILDDIR))/common-uefi-ia32/BOOTIA32.EFI' "$$LIMINE_UEFI_CD_TMP"/EFI/BOOT/ 2>/dev/null; \ + find "$$LIMINE_UEFI_CD_TMP" -exec touch -t $(SOURCE_DATE_EPOCH_TOUCH) '{}' + && \ + mcopy -D o -s -m -i '$(call SHESCAPE,$(BINDIR))/limine-uefi-cd.bin' "$$LIMINE_UEFI_CD_TMP"/EFI :: && \ + rm -rf "$$LIMINE_UEFI_CD_TMP" +endif + +.PHONY: limine-uefi-cd +limine-uefi-cd: + $(MAKE) '$(call SHESCAPE,$(BINDIR))/limine-uefi-cd.bin' + +$(call MKESCAPE,$(BINDIR))/BOOTX64.EFI: $(call MKESCAPE,$(BUILDDIR))/common-uefi-x86-64/BOOTX64.EFI + $(MKDIR_P) '$(call SHESCAPE,$(BINDIR))' + cp '$(call SHESCAPE,$(BUILDDIR))/common-uefi-x86-64/BOOTX64.EFI' '$(call SHESCAPE,$(BINDIR))/' + +.PHONY: limine-uefi-x86-64 +limine-uefi-x86-64: + $(MAKE) common-uefi-x86-64 + $(MAKE) '$(call SHESCAPE,$(BINDIR))/BOOTX64.EFI' + +$(call MKESCAPE,$(BINDIR))/BOOTIA32.EFI: $(call MKESCAPE,$(BUILDDIR))/common-uefi-ia32/BOOTIA32.EFI + $(MKDIR_P) '$(call SHESCAPE,$(BINDIR))' + cp '$(call SHESCAPE,$(BUILDDIR))/common-uefi-ia32/BOOTIA32.EFI' '$(call SHESCAPE,$(BINDIR))/' + +.PHONY: limine-uefi-ia32 +limine-uefi-ia32: + $(MAKE) common-uefi-ia32 + $(MAKE) '$(call SHESCAPE,$(BINDIR))/BOOTIA32.EFI' + +$(call MKESCAPE,$(BINDIR))/BOOTAA64.EFI: $(call MKESCAPE,$(BUILDDIR))/common-uefi-aarch64/BOOTAA64.EFI + $(MKDIR_P) '$(call SHESCAPE,$(BINDIR))' + cp '$(call SHESCAPE,$(BUILDDIR))/common-uefi-aarch64/BOOTAA64.EFI' '$(call SHESCAPE,$(BINDIR))/' + +.PHONY: limine-uefi-aarch64 +limine-uefi-aarch64: + $(MAKE) common-uefi-aarch64 + $(MAKE) '$(call SHESCAPE,$(BINDIR))/BOOTAA64.EFI' + +$(call MKESCAPE,$(BINDIR))/BOOTRISCV64.EFI: $(call MKESCAPE,$(BUILDDIR))/common-uefi-riscv64/BOOTRISCV64.EFI + $(MKDIR_P) '$(call SHESCAPE,$(BINDIR))' + cp '$(call SHESCAPE,$(BUILDDIR))/common-uefi-riscv64/BOOTRISCV64.EFI' '$(call SHESCAPE,$(BINDIR))/' + +.PHONY: limine-uefi-riscv64 +limine-uefi-riscv64: + $(MAKE) common-uefi-riscv64 + $(MAKE) '$(call SHESCAPE,$(BINDIR))/BOOTRISCV64.EFI' + +$(call MKESCAPE,$(BINDIR))/BOOTLOONGARCH64.EFI: $(call MKESCAPE,$(BUILDDIR))/common-uefi-loongarch64/BOOTLOONGARCH64.EFI + $(MKDIR_P) '$(call SHESCAPE,$(BINDIR))' + cp '$(call SHESCAPE,$(BUILDDIR))/common-uefi-loongarch64/BOOTLOONGARCH64.EFI' '$(call SHESCAPE,$(BINDIR))/' + +.PHONY: limine-uefi-loongarch64 +limine-uefi-loongarch64: + $(MAKE) common-uefi-loongarch64 + $(MAKE) '$(call SHESCAPE,$(BINDIR))/BOOTLOONGARCH64.EFI' + +.PHONY: limine-bios-clean +limine-bios-clean: common-bios-clean decompressor-clean + +.PHONY: limine-uefi-x86-64-clean +limine-uefi-x86-64-clean: common-uefi-x86-64-clean + +.PHONY: limine-uefi-ia32-clean +limine-uefi-ia32-clean: common-uefi-ia32-clean + +.PHONY: limine-uefi-aarch64-clean +limine-uefi-aarch64-clean: common-uefi-aarch64-clean + +.PHONY: limine-uefi-riscv64-clean +limine-uefi-riscv64-clean: common-uefi-riscv64-clean + +.PHONY: limine-uefi-loongarch64-clean +limine-uefi-loongarch64-clean: common-uefi-loongarch64-clean + +.PHONY: dist +dist: + rm -rf '$(call SHESCAPE,$(BUILDDIR))'/"$(DIST_OUTPUT)" + $(MKDIR_P) '$(call SHESCAPE,$(BUILDDIR))'/"$(DIST_OUTPUT)" + cp -r '$(call SHESCAPE,$(SRCDIR))'/.git '$(call SHESCAPE,$(BUILDDIR))'/"$(DIST_OUTPUT)"/ + cd '$(call SHESCAPE,$(BUILDDIR))'/"$(DIST_OUTPUT)" && git checkout . + cd '$(call SHESCAPE,$(BUILDDIR))'/"$(DIST_OUTPUT)" && ./bootstrap + rm -rf '$(call SHESCAPE,$(BUILDDIR))'/"$(DIST_OUTPUT)/flanterm/.git" + rm -rf '$(call SHESCAPE,$(BUILDDIR))'/"$(DIST_OUTPUT)/flanterm/.gitignore" + rm -rf '$(call SHESCAPE,$(BUILDDIR))'/"$(DIST_OUTPUT)/freestnd-c-hdrs/.git" + rm -rf '$(call SHESCAPE,$(BUILDDIR))'/"$(DIST_OUTPUT)/limine-protocol/.git" + rm -rf '$(call SHESCAPE,$(BUILDDIR))'/"$(DIST_OUTPUT)/picoefi/.git" + rm -rf '$(call SHESCAPE,$(BUILDDIR))'/"$(DIST_OUTPUT)/picoefi/.gitignore" + rm -rf '$(call SHESCAPE,$(BUILDDIR))'/"$(DIST_OUTPUT)/cc-runtime" + rm -rf '$(call SHESCAPE,$(BUILDDIR))'/"$(DIST_OUTPUT)/libfdt/.git" + rm -rf '$(call SHESCAPE,$(BUILDDIR))'/"$(DIST_OUTPUT)/tinf" + rm -rf '$(call SHESCAPE,$(BUILDDIR))'/"$(DIST_OUTPUT)/common/lib/stb_image.h.nopatch" + rm -rf '$(call SHESCAPE,$(BUILDDIR))'/"$(DIST_OUTPUT)/.git" + rm -rf '$(call SHESCAPE,$(BUILDDIR))'/"$(DIST_OUTPUT)/.gitignore" + rm -rf '$(call SHESCAPE,$(BUILDDIR))'/"$(DIST_OUTPUT)/.forgejo" + rm -rf '$(call SHESCAPE,$(BUILDDIR))'/"$(DIST_OUTPUT)/README.md" + rm -rf '$(call SHESCAPE,$(BUILDDIR))'/"$(DIST_OUTPUT)/autom4te.cache" + rm -rf '$(call SHESCAPE,$(BUILDDIR))'/"$(DIST_OUTPUT)/test" + rm -rf '$(call SHESCAPE,$(BUILDDIR))'/"$(DIST_OUTPUT)/test.mk" + rm -rf '$(call SHESCAPE,$(BUILDDIR))'/"$(DIST_OUTPUT)/logo.png" + rm -rf '$(call SHESCAPE,$(BUILDDIR))'/"$(DIST_OUTPUT)/screenshot.png" + rm -rf '$(call SHESCAPE,$(BUILDDIR))'/"$(DIST_OUTPUT)/bochsrc" + echo "$(PACKAGE_VERSION)" > '$(call SHESCAPE,$(BUILDDIR))'/"$(DIST_OUTPUT)/version" + cd '$(call SHESCAPE,$(BUILDDIR))' && tar -cf "$(DIST_OUTPUT).tar" "$(DIST_OUTPUT)" + cd '$(call SHESCAPE,$(BUILDDIR))' && gzip < "$(DIST_OUTPUT).tar" > "$(DIST_OUTPUT).tar.gz" + cd '$(call SHESCAPE,$(BUILDDIR))' && ( bzip2 < "$(DIST_OUTPUT).tar" > "$(DIST_OUTPUT).tar.bz2" || rm -f "$(DIST_OUTPUT).tar.bz2" ) + cd '$(call SHESCAPE,$(BUILDDIR))' && ( xz < "$(DIST_OUTPUT).tar" > "$(DIST_OUTPUT).tar.xz" || rm -f "$(DIST_OUTPUT).tar.xz" ) + cd '$(call SHESCAPE,$(BUILDDIR))' && rm "$(DIST_OUTPUT).tar" + rm -rf '$(call SHESCAPE,$(BUILDDIR))'/"$(DIST_OUTPUT)" + +.PHONY: distclean +distclean: clean + rm -rf edk2-ovmf config.log config.status GNUmakefile config.h man/man1/limine.1 + +.PHONY: maintainer-clean +maintainer-clean: distclean + cd '$(call SHESCAPE,$(SRCDIR))' && rm -rf flanterm common/lib/stb_image.h.nopatch common/lib/stb_image.h decompressor/tinf tinf libfdt freestnd-c-hdrs cc-runtime common/cc-runtime.s2.c decompressor/cc-runtime.c limine-protocol picoefi configure timestamps build-aux *'~' autom4te.cache aclocal.m4 *.tar* + +.PHONY: common-uefi-x86-64 +common-uefi-x86-64: + $(MAKE) -C '$(call SHESCAPE,$(SRCDIR))/common' -f common.mk \ + TARGET=uefi-x86-64 \ + BUILDDIR='$(call SHESCAPE,$(BUILDDIR))/common-uefi-x86-64' + +.PHONY: common-uefi-x86-64-clean +common-uefi-x86-64-clean: + rm -rf '$(call SHESCAPE,$(BUILDDIR))/common-uefi-x86-64' + +.PHONY: common-uefi-aarch64 +common-uefi-aarch64: + $(MAKE) -C '$(call SHESCAPE,$(SRCDIR))/common' -f common.mk \ + TARGET=uefi-aarch64 \ + BUILDDIR='$(call SHESCAPE,$(BUILDDIR))/common-uefi-aarch64' + +.PHONY: common-uefi-aarch64-clean +common-uefi-aarch64-clean: + rm -rf '$(call SHESCAPE,$(BUILDDIR))/common-uefi-aarch64' + +.PHONY: common-uefi-riscv64 +common-uefi-riscv64: + $(MAKE) -C '$(call SHESCAPE,$(SRCDIR))/common' -f common.mk \ + TARGET=uefi-riscv64 \ + BUILDDIR='$(call SHESCAPE,$(BUILDDIR))/common-uefi-riscv64' + +.PHONY: common-uefi-riscv64-clean +common-uefi-riscv64-clean: + rm -rf '$(call SHESCAPE,$(BUILDDIR))/common-uefi-riscv64' + +.PHONY: common-uefi-loongarch64 +common-uefi-loongarch64: + $(MAKE) -C '$(call SHESCAPE,$(SRCDIR))/common' -f common.mk \ + TARGET=uefi-loongarch64 \ + BUILDDIR='$(call SHESCAPE,$(BUILDDIR))/common-uefi-loongarch64' + +.PHONY: common-uefi-loongarch64-clean +common-uefi-loongarch64-clean: + rm -rf '$(call SHESCAPE,$(BUILDDIR))/common-uefi-loongarch64' + +.PHONY: common-uefi-ia32 +common-uefi-ia32: + $(MAKE) -C '$(call SHESCAPE,$(SRCDIR))/common' -f common.mk \ + TARGET=uefi-ia32 \ + BUILDDIR='$(call SHESCAPE,$(BUILDDIR))/common-uefi-ia32' + +.PHONY: common-uefi-ia32-clean +common-uefi-ia32-clean: + rm -rf '$(call SHESCAPE,$(BUILDDIR))/common-uefi-ia32' + +.PHONY: common-bios +common-bios: + $(MAKE) -C '$(call SHESCAPE,$(SRCDIR))/common' -f common.mk \ + TARGET=bios \ + BUILDDIR='$(call SHESCAPE,$(BUILDDIR))/common-bios' + +.PHONY: common-bios-clean +common-bios-clean: + rm -rf '$(call SHESCAPE,$(BUILDDIR))/common-bios' + +.PHONY: decompressor +decompressor: + $(MAKE) -C '$(call SHESCAPE,$(SRCDIR))/decompressor' -f decompressor.mk \ + BUILDDIR='$(call SHESCAPE,$(BUILDDIR))/decompressor-build' + +.PHONY: decompressor-clean +decompressor-clean: + rm -rf '$(call SHESCAPE,$(BUILDDIR))/decompressor-build' + +-include test.mk diff --git a/limine/INSTALL.md b/limine/INSTALL.md new file mode 100644 index 0000000..764b0a6 --- /dev/null +++ b/limine/INSTALL.md @@ -0,0 +1,50 @@ +# Build and Install Instructions + +> **NOTE:** This document is about building and installing Limine. +> For information about deployment for usage, see [USAGE.md](USAGE.md). + +## Prerequisites + +In order to build Limine, the following programs have to be installed: +common UNIX tools (also known as `coreutils`), +`GNU make`, `grep`, `sed`, `find`, `awk`, `gzip`, `nasm`, `mtools` +(optional, necessary to build `limine-uefi-cd.bin`). +Furthermore, `gcc` or `llvm/clang` must also be installed, alongside +the respective binutils. + +## Configure + +If using a release tarball (recommended, see +https://codeberg.org/Limine/Limine/releases), run `./configure` directly. + +If checking out from the repository, run `./bootstrap` first in order to +download the necessary [dependencies](3RDPARTY.md) and generate the configure +script (`GNU autoconf` required). + +`./configure` takes arguments and environment variables; for more information +on these, run `./configure --help`. + +> **NOTE:** `./configure` by default does not build any Limine port. Make sure +> to read the output of `./configure --help` and enable any or all ports! + +Limine supports both in-tree and out-of-tree builds. Simply run the `configure` +script from the directory you wish to execute the build in. The following +`make` commands are supposed to be run inside the build directory. + +## Building + +To build Limine, run: +```bash +make # (or gmake where applicable) +``` + +## Installing + +This step will install Limine files to `share`, `include`, and `bin` +directories in the specified prefix (default is `/usr/local`, see +`./configure --help`. + +To install Limine, run: +```bash +make install # (or gmake where applicable) +``` diff --git a/limine/LICENSES/LicenseRef-scancode-bsd-no-disclaimer-unmodified.txt b/limine/LICENSES/LicenseRef-scancode-bsd-no-disclaimer-unmodified.txt new file mode 100644 index 0000000..ff0021a --- /dev/null +++ b/limine/LICENSES/LicenseRef-scancode-bsd-no-disclaimer-unmodified.txt @@ -0,0 +1,7 @@ + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice and this list of conditions, without modification. + * 2. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. diff --git a/limine/README.md b/limine/README.md new file mode 100644 index 0000000..5eb644b --- /dev/null +++ b/limine/README.md @@ -0,0 +1,102 @@ +# Limine [![Matrix Server](https://img.shields.io/matrix/limine:matrix.org?color=000000&label=Matrix&logo=matrix)](https://matrix.to/#/#limine:matrix.org) [![Fluxer](https://img.shields.io/badge/Fluxer-Chat-4f3cc9?logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0Ij4KICA8cGF0aCBkPSJNMyA3LjVjMi41LTMgNS0zIDcuNSAwczUgMyA3LjUgMCIgc3Ryb2tlPSJ3aGl0ZSIgc3Ryb2tlLXdpZHRoPSIzIiBzdHJva2UtbGluZWNhcD0icm91bmQiIGZpbGw9Im5vbmUiLz4KICA8cGF0aCBkPSJNMyAxNi41YzIuNS0zIDUtMyA3LjUgMHM1IDMgNy41IDAiIHN0cm9rZT0id2hpdGUiIHN0cm9rZS13aWR0aD0iMyIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBmaWxsPSJub25lIi8+Cjwvc3ZnPgo=&style=flat)](https://fluxer.gg/ZRviNMvT) + +

+ Limine's logo +

+ +### What is Limine? + +Limine (pronounced as demonstrated [here](https://www.merriam-webster.com/dictionary/in%20limine)) +is a modern, advanced, portable, multiprotocol bootloader and boot manager, also used +as the reference implementation for the [Limine boot protocol](https://codeberg.org/Limine/limine-protocol/src/branch/trunk/PROTOCOL.md). + +### Community, Support, and Donations + +#### Donate +If you want to support the work I ([@mintsuki](https://codeberg.org/Mintsuki)) do on Limine, feel free to donate to me on Liberapay: +

Donate using Liberapay

+ +Donations welcome, but absolutely not mandatory! + +#### Community +We have a Matrix room at [`#limine:matrix.org`](https://matrix.to/#/#limine:matrix.org) +and a [Fluxer community](https://fluxer.gg/ZRviNMvT) if you need support, info, or you just want to hang out with us. + +### Limine's boot menu + +![Reference screenshot](screenshot.png?raw=true "Reference screenshot") + +[Photo by Pixabay](https://www.pexels.com/photo/brown-leafed-tree-on-open-field-under-white-clouds-and-blue-sky-35857/) + +### Supported architectures +* IA-32 (32-bit x86) +* x86-64 +* aarch64 (arm64) +* riscv64 +* loongarch64 (experimental support!) + +### Supported boot protocols +* Linux +* [Limine](https://codeberg.org/Limine/limine-protocol/src/branch/trunk/PROTOCOL.md) +* Multiboot 1 +* Multiboot 2 +* Chainloading + +### Supported partitioning schemes +* MBR +* GPT +* Unpartitioned media + +### Supported filesystems +* FAT12/16/32 +* ISO9660 (CDs/DVDs) + +If your filesystem isn't listed here, please read [the FAQ](FAQ.md) first, especially before +opening issues or pull requests related to this. + +### Minimum system requirements +For 32-bit x86 systems, support is only ensured starting with those with +Pentium Pro (i686) class CPUs. + +All x86-64, aarch64, riscv64 and loongarch64 (UEFI) systems are supported. + +## Packaging status + +All Limine releases since 7.x use [Semantic Versioning](https://semver.org/spec/v2.0.0.html) for their naming. + +[![Packaging status](https://repology.org/badge/vertical-allrepos/limine.svg?columns=3)](https://repology.org/project/limine/versions) + +## Binary releases + +For convenience, for point releases, binaries are distributed. These binaries +are shipped in the `-binary` branches and tags of this repository +(see [branches](https://codeberg.org/Limine/Limine/branches) and +[tags](https://codeberg.org/Limine/Limine/tags)). + +For example, to clone the latest binary release of the `10.x` branch, one can do: +```bash +git clone https://codeberg.org/Limine/Limine.git --branch=v10.x-binary --depth=1 +``` +or, to clone a specific binary point release (for example `10.8.5`): +```bash +git clone https://codeberg.org/Limine/Limine.git --branch=v10.8.5-binary --depth=1 +``` + +In order to rebuild host utilities like `limine`, simply run `make` in the binary +release directory. + +Host utility binaries are provided for Windows. + +## Build and Install Instructions + +*The following steps are not necessary if cloning a binary release.* + +See [INSTALL.md](INSTALL.md). + +## Usage + +See [USAGE.md](USAGE.md). + +## 3rd Party Software Acknowledgments + +See [3RDPARTY.md](3RDPARTY.md). diff --git a/limine/USAGE.md b/limine/USAGE.md new file mode 100644 index 0000000..f791775 --- /dev/null +++ b/limine/USAGE.md @@ -0,0 +1,107 @@ +# Usage + +> **NOTE:** The Limine files referred to here are those contained inside +> ${PREFIX}/share/, installed there as a product of the steps described in +> [INSTALL.md](INSTALL.md). + +## UEFI +The `BOOT*.EFI` files are valid EFI applications that can be simply copied to +the `/EFI/BOOT` directory of a FAT formatted EFI system partition. These files +can be installed there and coexist with a BIOS installation of Limine +(see below) so that the disk will be bootable on both BIOS and UEFI systems. + +A valid config file should also be provided as described in +[CONFIG.md](CONFIG.md). + +## Secure Boot +Limine can be booted with secure boot if the executable is signed and the key +used to sign it is added to the firmware's keychain. This should be done in +combination with enrolling the BLAKE2B hash of the Limine config file into the +Limine EFI executable image itself for verification purposes. +For more information see the `limine enroll-config` program and +[the FAQ](FAQ.md). + +## BIOS/MBR +In order to install Limine on a MBR device (which can just be a raw image +file), run `limine bios-install` as such: + +```bash +limine bios-install +``` + +The boot device must contain the `limine-bios.sys` and `limine.conf` files in +either a `boot/limine`, `boot`, `limine`, or root directory of one of the +partitions, formatted with a supported file system. See [CONFIG.md](CONFIG.md). + +## BIOS/GPT +If using a GPT formatted device, create a partition on the GPT device (usually +of "BIOS boot" type) of at least 32KiB in size, and pass the 1-based number +of the partition to `limine bios-install` as a second argument; such as: + +```bash +limine bios-install <1-based stage 2 partition number> +``` + +The boot device must contain the `limine-bios.sys` and `limine.conf` files in +either a `boot/limine`, `boot`, `limine`, or root directory of one of the +partitions, formatted with a supported file system. See [CONFIG.md](CONFIG.md). + +## BIOS/UEFI hybrid ISO creation +In order to create a hybrid ISO with Limine, place the +`limine-uefi-cd.bin`, `limine-bios-cd.bin`, `limine-bios.sys`, and +`limine.conf` files into a directory which will serve as the root of the +created ISO. +(`limine-bios.sys` and `limine.conf` must either be in the root, `limine`, +`boot`, or `boot/limine` directory; `limine-uefi-cd.bin` and +`limine-bios-cd.bin` can reside anywhere). + +After that, create a `/EFI/BOOT` directory and copy the +relevant Limine EFI executables over (such as `BOOTX64.EFI`). + +Place any other file you want to be on the final ISO in said directory, then +run: +``` +xorriso -as mkisofs -R -r -J -b \ + -no-emul-boot -boot-load-size 4 -boot-info-table -hfsplus \ + -apm-block-size 2048 --efi-boot \ + -efi-boot-part --efi-boot-image --protective-msdos-label \ + -o image.iso +``` + +*Note: `xorriso` is required.* + +And do not forget to also run `limine bios-install` on the generated image: +``` +limine bios-install image.iso +``` + +`` is the relative path of +`limine-bios-cd.bin` inside the root directory. +For example, if it was copied in `/boot/limine-bios-cd.bin`, +it would be `boot/limine-bios-cd.bin`. + +`` is the relative path of +`limine-uefi-cd.bin` inside the root directory. +For example, if it was copied in +`/boot/limine-uefi-cd.bin`, it would be +`boot/limine-uefi-cd.bin`. + +## BIOS/PXE boot +The `limine-bios-pxe.bin` binary is a valid PXE boot image. +In order to boot Limine from PXE it is necessary to setup a DHCP server with +support for PXE booting. This can either be accomplished using a single DHCP +server or your existing DHCP server and a proxy DHCP server such as dnsmasq. + +`limine.conf` and `limine-bios.sys` are expected to be on the server used for +boot. + +## UEFI/PXE boot +The `BOOT*.EFI` files are compatible with UEFI PXE. +The steps needed to boot Limine are the same as with BIOS PXE, +except that the `limine-bios.sys` file is not needed on the server. + +## Configuration +The `limine.conf` file contains Limine's configuration. + +More info on the format of `limine.conf` can be found in +[`CONFIG.md`](CONFIG.md). diff --git a/limine/bochsrc b/limine/bochsrc new file mode 100644 index 0000000..8d7e277 --- /dev/null +++ b/limine/bochsrc @@ -0,0 +1,7 @@ +cpu: count=2, reset_on_triple_fault=0 +memory: guest=1024, host=1024 +ata0-master: type=disk, path="test.hdd", mode=flat +boot: c +clock: sync=realtime, rtc_sync=1, time0=utc +port_e9_hack: enabled=1 +magic_break: enabled=1 diff --git a/limine/bootstrap b/limine/bootstrap new file mode 100755 index 0000000..c1a9679 --- /dev/null +++ b/limine/bootstrap @@ -0,0 +1,170 @@ +#! /bin/sh + +set -ex + +srcdir="$(dirname "$0")" +test -z "$srcdir" && srcdir=. + +: "${AUTORECONF:=autoreconf}" +: "${AUTOMAKE:=automake}" + +cd "$srcdir" + +AUXFILES="config.guess config.sub install-sh" + +clone_repo_commit() { + if test -d "$2/.git"; then + git -C "$2" reset --hard + git -C "$2" clean -fd + if ! git -C "$2" -c advice.detachedHead=false checkout $3; then + rm -rf "$2" + fi + else + if test -d "$2"; then + set +x + echo "error: '$2' is not a Git repository" 1>&2 + exit 1 + fi + fi + if ! test -d "$2"; then + git clone $1 "$2" + if ! git -C "$2" -c advice.detachedHead=false checkout $3; then + rm -rf "$2" + exit 1 + fi + fi +} + +download_by_hash() { + DOWNLOAD_COMMAND="curl -Lo" + if ! command -v "${DOWNLOAD_COMMAND%% *}" >/dev/null 2>&1; then + DOWNLOAD_COMMAND="wget -O" + if ! command -v "${DOWNLOAD_COMMAND%% *}" >/dev/null 2>&1; then + set +x + echo "error: Neither curl nor wget found" 1>&2 + exit 1 + fi + fi + SHA256_COMMAND="sha256sum" + if ! command -v "${SHA256_COMMAND%% *}" >/dev/null 2>&1; then + SHA256_COMMAND="sha256" + if ! command -v "${SHA256_COMMAND%% *}" >/dev/null 2>&1; then + set +x + echo "error: Cannot find sha256(sum) command" 1>&2 + exit 1 + fi + fi + if ! test -f "$2" || ! $SHA256_COMMAND "$2" | grep $3 >/dev/null 2>&1; then + rm -f "$2" + mkdir -p "$2" && rm -rf "$2" + $DOWNLOAD_COMMAND "$2" $1 + if ! $SHA256_COMMAND "$2" | grep $3 >/dev/null 2>&1; then + set +x + echo "error: Cannot download file '$2' by hash" 1>&2 + echo "incorrect hash:" 1>&2 + $SHA256_COMMAND "$2" 1>&2 + rm -f "$2" + exit 1 + fi + fi +} + +if ! test -f version; then + clone_repo_commit \ + https://codeberg.org/OSDev/freestnd-c-hdrs-0bsd.git \ + freestnd-c-hdrs \ + 097259a899d30f0a4b7a694de2de5fdda942e923 + + clone_repo_commit \ + https://codeberg.org/OSDev/cc-runtime.git \ + cc-runtime \ + dae79833b57a01b9fd3e359ee31def69f5ae899b + cp cc-runtime/src/cc-runtime.c common/cc-runtime.s2.c + cp cc-runtime/src/cc-runtime.c decompressor/cc-runtime.c + + clone_repo_commit \ + https://codeberg.org/Limine/limine-protocol.git \ + limine-protocol \ + e42d010a761e4e9ac6bad1b578110ba72c61e1d9 + + clone_repo_commit \ + https://codeberg.org/PicoEFI/PicoEFI.git \ + picoefi \ + 8b79fdaa72ee548a8ea24e3dc4d87bf281312865 + + clone_repo_commit \ + https://github.com/jibsen/tinf.git \ + tinf \ + 57ffa1f1d5e3dde19011b2127bd26d01689b694b + mkdir -p decompressor/tinf + cp tinf/src/tinf.h tinf/src/tinflate.c tinf/src/tinfgzip.c decompressor/tinf/ + patch -p0 < decompressor/tinf.patch + rm -f decompressor/tinf/*.orig + + clone_repo_commit \ + https://codeberg.org/Mintsuki/Flanterm.git \ + flanterm \ + 5d93c648bee81ce7d19f7a6671ec2bc3aaa834f2 + + download_by_hash \ + https://github.com/nothings/stb/raw/5c205738c191bcb0abc65c4febfa9bd25ff35234/stb_image.h \ + common/lib/stb_image.h.nopatch \ + 594c2fe35d49488b4382dbfaec8f98366defca819d916ac95becf3e75f4200b3 + cp common/lib/stb_image.h.nopatch common/lib/stb_image.h + patch -p0 < common/stb_image.patch + rm -f common/lib/stb_image.h.orig + + clone_repo_commit \ + https://codeberg.org/OSDev/libfdt.git \ + libfdt \ + 7bf94e6347129d17eca263112296ad170dec28a9 +fi + +# Create timestamps file +if test -d .git && git log -1 >/dev/null 2>&1; then + cat >timestamps <timestamps </dev/null 2>&1; then + set +x + echo "error: Broken autoreconf detected, but missing or broken automake." 1>&2 + echo " Please make sure automake is installed and working." 1>&2 + exit 1 + fi + AUTOMAKE_LIBDIR="$($AUTOMAKE --print-libdir)" + if test -z "$AUTOMAKE_LIBDIR"; then + # Assume `true` was passed as $AUTOMAKE + continue + fi + mkdir -p build-aux + cp -v "$AUTOMAKE_LIBDIR/$auxfile" build-aux/ + chmod +x build-aux/$auxfile + fi +done + +set +x + +printf "\nSource tree bootstrapped successfully!\n" diff --git a/limine/common/common.mk b/limine/common/common.mk new file mode 100644 index 0000000..0e2dc5b --- /dev/null +++ b/limine/common/common.mk @@ -0,0 +1,675 @@ +.SUFFIXES: + +override SRCDIR := $(shell pwd -P) + +override SPACE := $(subst ,, ) + +override MKESCAPE = $(subst $(SPACE),\ ,$(1)) +override SHESCAPE = $(subst ','\'',$(1)) +override OBJESCAPE = $(subst .a ,.a' ',$(subst .o ,.o' ',$(call SHESCAPE,$(1)))) + +override CC_FOR_TARGET_IS_CLANG := $(shell ! $(CC_FOR_TARGET) --version 2>/dev/null | $(GREP) -q '^Target: '; echo $$?) + +COM_OUTPUT := false +E9_OUTPUT := false + +override S2CFLAGS := -Os + +override BASE_CFLAGS := $(CFLAGS_FOR_TARGET) + +override CFLAGS_FOR_TARGET += \ + -g \ + -Wall \ + -Wextra \ + -Wshadow \ + -Wvla \ + $(WERROR_FLAG) \ + -std=gnu11 \ + -nostdinc \ + -ffreestanding \ + -ffunction-sections \ + -fdata-sections \ + -fno-stack-protector \ + -fno-stack-check \ + -fno-omit-frame-pointer \ + -fno-strict-aliasing \ + -fno-lto + +override CPPFLAGS_FOR_TARGET := \ + -I . \ + -I libc-compat \ + -I ../limine-protocol/include \ + -I ../flanterm/src \ + -I ../libfdt/src \ + -I '$(call SHESCAPE,$(BUILDDIR))/..' \ + -isystem ../freestnd-c-hdrs/include \ + $(CPPFLAGS_FOR_TARGET) \ + -DCOM_OUTPUT=$(COM_OUTPUT) \ + -DE9_OUTPUT=$(E9_OUTPUT) \ + -DFLANTERM_IN_FLANTERM \ + -MMD \ + -MP + +$(call MKESCAPE,$(BUILDDIR))/libfdt/src/fdt_overlay.o: override CFLAGS_FOR_TARGET += \ + -Wno-unused-parameter + +$(call MKESCAPE,$(BUILDDIR))/flanterm/src/flanterm_backends/fb.o: override CPPFLAGS_FOR_TARGET += \ + -DFLANTERM_FB_DISABLE_BUMP_ALLOC + +override NASMFLAGS_FOR_TARGET += \ + -g \ + -Wall \ + -w-unknown-warning \ + -w-reloc \ + $(WERROR_FLAG) + +override NASMFLAGS_FOR_TARGET := \ + $(patsubst -g,-g -F dwarf,$(NASMFLAGS_FOR_TARGET)) + +ifeq ($(TARGET),bios) + ifeq ($(CC_FOR_TARGET_IS_CLANG),1) + override CC_FOR_TARGET += \ + -target i686-unknown-none-elf + endif + override CFLAGS_FOR_TARGET += \ + -fno-PIC \ + -m32 \ + -march=i686 \ + -mabi=sysv \ + -mno-80387 \ + -mno-mmx + override CPPFLAGS_FOR_TARGET := \ + $(CPPFLAGS_FOR_TARGET) \ + -DBIOS + override NASMFLAGS_FOR_TARGET := \ + -f elf32 \ + $(NASMFLAGS_FOR_TARGET) \ + -DIA32_TARGET \ + -DBIOS +endif + +ifeq ($(TARGET),uefi-x86-64) + ifeq ($(CC_FOR_TARGET_IS_CLANG),1) + override CC_FOR_TARGET += \ + -target x86_64-unknown-none-elf + endif + override CFLAGS_FOR_TARGET += \ + -fPIE \ + -fshort-wchar \ + -m64 \ + -march=x86-64 \ + -mabi=sysv \ + -mno-80387 \ + -mno-mmx \ + -mno-sse \ + -mno-sse2 \ + -mno-red-zone + override CPPFLAGS_FOR_TARGET := \ + -I ../picoefi/inc \ + $(CPPFLAGS_FOR_TARGET) \ + -DUEFI + override NASMFLAGS_FOR_TARGET := \ + -f elf64 \ + $(NASMFLAGS_FOR_TARGET) \ + -DX86_64_TARGET \ + -DUEFI +endif + +ifeq ($(TARGET),uefi-ia32) + ifeq ($(CC_FOR_TARGET_IS_CLANG),1) + override CC_FOR_TARGET += \ + -target i686-unknown-none-elf + endif + override CFLAGS_FOR_TARGET += \ + -fPIE \ + -fshort-wchar \ + -m32 \ + -march=i686 \ + -mabi=sysv \ + -mno-80387 \ + -mno-mmx + override CPPFLAGS_FOR_TARGET := \ + -I ../picoefi/inc \ + $(CPPFLAGS_FOR_TARGET) \ + -DUEFI + override NASMFLAGS_FOR_TARGET := \ + -f elf32 \ + $(NASMFLAGS_FOR_TARGET) \ + -DIA32_TARGET \ + -DUEFI +endif + +ifeq ($(TARGET),uefi-aarch64) + ifeq ($(CC_FOR_TARGET_IS_CLANG),1) + override CC_FOR_TARGET += \ + -target aarch64-unknown-none-elf + endif + override CFLAGS_FOR_TARGET += \ + -fPIE \ + -fshort-wchar \ + -mcpu=generic \ + -march=armv8-a+nofp+nosimd \ + -mgeneral-regs-only + override CPPFLAGS_FOR_TARGET := \ + -I ../picoefi/inc \ + $(CPPFLAGS_FOR_TARGET) \ + -DUEFI +endif + +ifeq ($(TARGET),uefi-riscv64) + ifeq ($(CC_FOR_TARGET_IS_CLANG),1) + override CC_FOR_TARGET += \ + -target riscv64-unknown-none-elf + endif + override CFLAGS_FOR_TARGET += \ + -fPIE \ + -fshort-wchar \ + -march=rv64imac_zicsr_zifencei \ + -mabi=lp64 \ + -mno-relax + override CPPFLAGS_FOR_TARGET := \ + -I ../picoefi/inc \ + $(CPPFLAGS_FOR_TARGET) \ + -DUEFI +endif + +ifeq ($(TARGET),uefi-loongarch64) + ifeq ($(CC_FOR_TARGET_IS_CLANG),1) + override CC_FOR_TARGET += \ + -target loongarch64-unknown-none-elf + endif + override CFLAGS_FOR_TARGET += \ + -fPIE \ + -fshort-wchar \ + -march=loongarch64 \ + -mabi=lp64s \ + -mfpu=none \ + -msimd=none + override CPPFLAGS_FOR_TARGET := \ + -I ../picoefi/inc \ + $(CPPFLAGS_FOR_TARGET) \ + -DUEFI +endif + +override LDFLAGS_FOR_TARGET += \ + -nostdlib \ + -z max-page-size=0x1000 \ + --gc-sections + +ifeq ($(TARGET),bios) + override LDFLAGS_FOR_TARGET += \ + -m elf_i386 \ + -static \ + --build-id=sha1 +endif + +ifeq ($(TARGET),uefi-x86-64) + override LDFLAGS_FOR_TARGET += \ + -m elf_x86_64 \ + -pie \ + -z text +endif + +ifeq ($(TARGET),uefi-ia32) + override LDFLAGS_FOR_TARGET += \ + -m elf_i386 \ + -pie \ + -z text +endif + +ifeq ($(TARGET),uefi-aarch64) + override LDFLAGS_FOR_TARGET += \ + -m aarch64elf \ + -pie \ + -z text +endif + +ifeq ($(TARGET),uefi-riscv64) + override LDFLAGS_FOR_TARGET += \ + -m elf64lriscv \ + --no-relax \ + -pie \ + -z text +endif + +ifeq ($(TARGET),uefi-loongarch64) + override LDFLAGS_FOR_TARGET += \ + -m elf64loongarch \ + -pie \ + -z text +endif + +ifeq ($(TARGET),bios) + override C_FILES := $(shell cd .. && find common flanterm/src libfdt/src -type f -name '*.c' | LC_ALL=C sort) + override S_FILES := $(shell cd .. && find common -type f -name '*.S' | LC_ALL=C sort) + + override ASMX86_FILES := $(shell cd .. && find common -type f -name '*.asm_x86' | LC_ALL=C sort) + override ASM32_FILES := $(shell cd .. && find common -type f -name '*.asm_ia32' | LC_ALL=C sort) + override ASMB_FILES := $(shell cd .. && find common -type f -name '*.asm_bios_ia32' | LC_ALL=C sort) + + override OBJ := $(addprefix $(call MKESCAPE,$(BUILDDIR))/, $(C_FILES:.c=.o) $(S_FILES:.S=.o) $(ASM32_FILES:.asm_ia32=.o) $(ASMB_FILES:.asm_bios_ia32=.o) $(ASMX86_FILES:.asm_x86=.o)) + override OBJ_S2 := $(filter %.s2.o,$(OBJ)) +endif +ifeq ($(TARGET),uefi-x86-64) + override C_FILES := $(shell cd .. && find common picoefi/x86_64 flanterm/src libfdt/src -type f -name '*.c' | LC_ALL=C sort) + override S_FILES := $(shell cd .. && find common picoefi/x86_64 -type f -name '*.S' | LC_ALL=C sort) + + override ASMX86_FILES := $(shell cd .. && find common -type f -name '*.asm_x86' | LC_ALL=C sort) + override ASM64_FILES := $(shell cd .. && find common -type f -name '*.asm_x86_64' | LC_ALL=C sort) + override ASM64U_FILES := $(shell cd .. && find common -type f -name '*.asm_uefi_x86_64' | LC_ALL=C sort) + + override OBJ := $(addprefix $(call MKESCAPE,$(BUILDDIR))/, $(C_FILES:.c=.o) $(S_FILES:.S=.o) $(ASM64_FILES:.asm_x86_64=.o) $(ASM64U_FILES:.asm_uefi_x86_64=.o) $(ASMX86_FILES:.asm_x86=.o)) +endif +ifeq ($(TARGET),uefi-ia32) + override C_FILES := $(shell cd .. && find common picoefi/ia32 flanterm/src libfdt/src -type f -name '*.c' | LC_ALL=C sort) + override S_FILES := $(shell cd .. && find common picoefi/ia32 -type f -name '*.S' | LC_ALL=C sort) + + override ASMX86_FILES := $(shell cd .. && find common -type f -name '*.asm_x86' | LC_ALL=C sort) + override ASM32_FILES := $(shell cd .. && find common -type f -name '*.asm_ia32' | LC_ALL=C sort) + override ASM32U_FILES := $(shell cd .. && find common -type f -name '*.asm_uefi_ia32' | LC_ALL=C sort) + + override OBJ := $(addprefix $(call MKESCAPE,$(BUILDDIR))/, $(C_FILES:.c=.o) $(S_FILES:.S=.o) $(ASM32_FILES:.asm_ia32=.o) $(ASM32U_FILES:.asm_uefi_ia32=.o) $(ASMX86_FILES:.asm_x86=.o)) +endif +ifeq ($(TARGET),uefi-aarch64) + override C_FILES := $(shell cd .. && find common picoefi/aarch64 flanterm/src libfdt/src -type f -name '*.c' | LC_ALL=C sort) + override S_FILES := $(shell cd .. && find common picoefi/aarch64 -type f -name '*.S' | LC_ALL=C sort) + + override ASM64_FILES := $(shell cd .. && find common -type f -name '*.asm_aarch64' | LC_ALL=C sort) + override ASM64U_FILES := $(shell cd .. && find common -type f -name '*.asm_uefi_aarch64' | LC_ALL=C sort) + + override OBJ := $(addprefix $(call MKESCAPE,$(BUILDDIR))/, $(C_FILES:.c=.o) $(S_FILES:.S=.o) $(ASM64_FILES:.asm_aarch64=.o) $(ASM64U_FILES:.asm_uefi_aarch64=.o)) +endif +ifeq ($(TARGET),uefi-riscv64) + override C_FILES := $(shell cd .. && find common picoefi/riscv64 flanterm/src libfdt/src -type f -name '*.c' | LC_ALL=C sort) + override S_FILES := $(shell cd .. && find common picoefi/riscv64 -type f -name '*.S' | LC_ALL=C sort) + + override ASM64_FILES := $(shell cd .. && find common -type f -name '*.asm_riscv64' | LC_ALL=C sort) + override ASM64U_FILES := $(shell cd .. && find common -type f -name '*.asm_uefi_riscv64' | LC_ALL=C sort) + + override OBJ := $(addprefix $(call MKESCAPE,$(BUILDDIR))/, $(C_FILES:.c=.o) $(S_FILES:.S=.o) $(ASM64_FILES:.asm_riscv64=.o) $(ASM64U_FILES:.asm_uefi_riscv64=.o)) +endif +ifeq ($(TARGET),uefi-loongarch64) + override C_FILES := $(shell cd .. && find common picoefi/loongarch64 flanterm/src libfdt/src -type f -name '*.c' | LC_ALL=C sort) + override S_FILES := $(shell cd .. && find common picoefi/loongarch64 -type f -name '*.S' | LC_ALL=C sort) + + override ASM64_FILES := $(shell cd .. && find common -type f -name '*.asm_loongarch64' | LC_ALL=C sort) + override ASM64U_FILES := $(shell cd .. && find common -type f -name '*.asm_uefi_loongarch64' | LC_ALL=C sort) + + override OBJ := $(addprefix $(call MKESCAPE,$(BUILDDIR))/, $(C_FILES:.c=.o) $(S_FILES:.S=.o) $(ASM64_FILES:.asm_loongarch64=.o) $(ASM64U_FILES:.asm_uefi_loongarch64=.o)) +endif + +override HEADER_DEPS := $(addprefix $(call MKESCAPE,$(BUILDDIR))/, $(C_FILES:.c=.d) $(C_FILES:.S=.d)) + +.PHONY: all + +ifeq ($(TARGET),bios) +all: $(call MKESCAPE,$(BUILDDIR))/limine-bios.sys $(call MKESCAPE,$(BUILDDIR))/stage2.bin.gz +endif +ifeq ($(TARGET),uefi-x86-64) +all: $(call MKESCAPE,$(BUILDDIR))/BOOTX64.EFI +endif +ifeq ($(TARGET),uefi-ia32) +all: $(call MKESCAPE,$(BUILDDIR))/BOOTIA32.EFI +endif +ifeq ($(TARGET),uefi-aarch64) +all: $(call MKESCAPE,$(BUILDDIR))/BOOTAA64.EFI +endif +ifeq ($(TARGET),uefi-riscv64) +all: $(call MKESCAPE,$(BUILDDIR))/BOOTRISCV64.EFI +endif +ifeq ($(TARGET),uefi-loongarch64) +all: $(call MKESCAPE,$(BUILDDIR))/BOOTLOONGARCH64.EFI +endif + +ifeq ($(TARGET),bios) + +$(call MKESCAPE,$(BUILDDIR))/stage2.bin.gz: $(call MKESCAPE,$(BUILDDIR))/stage2.bin + gzip -n -9 < '$(call SHESCAPE,$<)' > '$(call SHESCAPE,$@)' + +$(call MKESCAPE,$(BUILDDIR))/stage2.bin: $(call MKESCAPE,$(BUILDDIR))/limine-bios.sys + dd if='$(call SHESCAPE,$<)' bs=$$(( 0x$$("$(READELF_FOR_TARGET)" -S '$(call SHESCAPE,$(BUILDDIR))/limine.elf' | $(GREP) '\.text\.stage3' | $(SED) 's/^.*] //' | $(AWK) '{print $$3}' | $(SED) 's/^0*//') - 0xf000 )) count=1 of='$(call SHESCAPE,$@)' 2>/dev/null + +$(call MKESCAPE,$(BUILDDIR))/stage2.map.o: $(call MKESCAPE,$(BUILDDIR))/limine_nomap.elf + cd '$(call SHESCAPE,$(BUILDDIR))' && \ + '$(call SHESCAPE,$(SRCDIR))/gensyms.sh' '$(call SHESCAPE,$<)' stage2 32 '\.text\.stage2' + $(CC_FOR_TARGET) $(CFLAGS_FOR_TARGET) $(CPPFLAGS_FOR_TARGET) -c '$(call SHESCAPE,$(BUILDDIR))/stage2.map.S' -o '$(call SHESCAPE,$@)' + rm -f '$(call SHESCAPE,$(BUILDDIR))/stage2.map.S' '$(call SHESCAPE,$(BUILDDIR))/stage2.map.d' + +$(call MKESCAPE,$(BUILDDIR))/full.map.o: $(call MKESCAPE,$(BUILDDIR))/limine_nos3map.elf + cd '$(call SHESCAPE,$(BUILDDIR))' && \ + '$(call SHESCAPE,$(SRCDIR))/gensyms.sh' '$(call SHESCAPE,$<)' full 32 '\.text' + $(CC_FOR_TARGET) $(CFLAGS_FOR_TARGET) $(CPPFLAGS_FOR_TARGET) -c '$(call SHESCAPE,$(BUILDDIR))/full.map.S' -o '$(call SHESCAPE,$@)' + rm -f '$(call SHESCAPE,$(BUILDDIR))/full.map.S' '$(call SHESCAPE,$(BUILDDIR))/full.map.d' + +$(call MKESCAPE,$(BUILDDIR))/limine-bios.sys: $(call MKESCAPE,$(BUILDDIR))/limine_stage2only.elf $(call MKESCAPE,$(BUILDDIR))/limine.elf + $(OBJCOPY_FOR_TARGET) -O binary '$(call SHESCAPE,$(BUILDDIR))/limine.elf' '$(call SHESCAPE,$@)' + chmod -x '$(call SHESCAPE,$@)' + +$(call MKESCAPE,$(BUILDDIR))/linker_stage2only.ld: linker_bios.ld.in + $(MKDIR_P) '$(call SHESCAPE,$(BUILDDIR))' + $(CC_FOR_TARGET) -x c -E -P -undef -DLINKER_STAGE2ONLY linker_bios.ld.in -o '$(call SHESCAPE,$(BUILDDIR))/linker_stage2only.ld' + +$(call MKESCAPE,$(BUILDDIR))/limine_stage2only.elf: $(OBJ_S2) + $(MAKE) -f common.mk '$(call SHESCAPE,$(BUILDDIR))/linker_stage2only.ld' + $(LD_FOR_TARGET) $(LDFLAGS_FOR_TARGET) '$(call OBJESCAPE,$^)' -T'$(call SHESCAPE,$(BUILDDIR))/linker_stage2only.ld' -o '$(call SHESCAPE,$@)' || \ + ( echo "This error may mean that stage 2 was trying to use stage 3 symbols before loading stage 3" && \ + false ) + +$(call MKESCAPE,$(BUILDDIR))/linker_nos2map.ld: linker_bios.ld.in + $(MKDIR_P) '$(call SHESCAPE,$(BUILDDIR))' + $(CC_FOR_TARGET) -x c -E -P -undef -DLINKER_NOMAP -DLINKER_NOS2MAP linker_bios.ld.in -o '$(call SHESCAPE,$(BUILDDIR))/linker_nos2map.ld' + +$(call MKESCAPE,$(BUILDDIR))/empty: + touch '$(call SHESCAPE,$@)' + +$(call MKESCAPE,$(BUILDDIR))/limine_nomap.elf: $(OBJ) + $(MAKE) -f common.mk '$(call SHESCAPE,$(BUILDDIR))/empty' + $(MAKE) -f common.mk '$(call SHESCAPE,$(BUILDDIR))/linker_nos2map.ld' + $(LD_FOR_TARGET) $(LDFLAGS_FOR_TARGET) '$(call OBJESCAPE,$^)' -T'$(call SHESCAPE,$(BUILDDIR))/linker_nos2map.ld' -o '$(call SHESCAPE,$@)' + $(OBJCOPY_FOR_TARGET) -O binary --only-section=.note.gnu.build-id '$(call SHESCAPE,$@)' '$(call SHESCAPE,$(BUILDDIR))/build-id.s2.bin' + cd '$(call SHESCAPE,$(BUILDDIR))' && \ + $(OBJCOPY_FOR_TARGET) -I binary -B i386 -O elf32-i386 build-id.s2.bin build-id.s2.o && \ + $(OBJCOPY_FOR_TARGET) --add-section .note.GNU-stack='$(call SHESCAPE,$(BUILDDIR))/empty' --set-section-flags .note.GNU-stack=noload,readonly build-id.s2.o + $(OBJCOPY_FOR_TARGET) -O binary --only-section=.note.gnu.build-id '$(call SHESCAPE,$@)' '$(call SHESCAPE,$(BUILDDIR))/build-id.s3.bin' + cd '$(call SHESCAPE,$(BUILDDIR))' && \ + $(OBJCOPY_FOR_TARGET) -I binary -B i386 -O elf32-i386 build-id.s3.bin build-id.s3.o && \ + $(OBJCOPY_FOR_TARGET) --add-section .note.GNU-stack='$(call SHESCAPE,$(BUILDDIR))/empty' --set-section-flags .note.GNU-stack=noload,readonly build-id.s3.o + $(LD_FOR_TARGET) $(LDFLAGS_FOR_TARGET) '$(call OBJESCAPE,$^)' '$(call SHESCAPE,$(BUILDDIR))/build-id.s2.o' '$(call SHESCAPE,$(BUILDDIR))/build-id.s3.o' -T'$(call SHESCAPE,$(BUILDDIR))/linker_nos2map.ld' -o '$(call SHESCAPE,$@)' + +$(call MKESCAPE,$(BUILDDIR))/linker_nomap.ld: linker_bios.ld.in + $(MKDIR_P) '$(call SHESCAPE,$(BUILDDIR))' + $(CC_FOR_TARGET) -x c -E -P -undef -DLINKER_NOMAP linker_bios.ld.in -o '$(call SHESCAPE,$(BUILDDIR))/linker_nomap.ld' + +$(call MKESCAPE,$(BUILDDIR))/limine_nos3map.elf: $(OBJ) $(call MKESCAPE,$(BUILDDIR))/stage2.map.o + $(MAKE) -f common.mk '$(call SHESCAPE,$(BUILDDIR))/empty' + $(MAKE) -f common.mk '$(call SHESCAPE,$(BUILDDIR))/linker_nomap.ld' + $(LD_FOR_TARGET) $(LDFLAGS_FOR_TARGET) '$(call OBJESCAPE,$^)' -T'$(call SHESCAPE,$(BUILDDIR))/linker_nomap.ld' -o '$(call SHESCAPE,$@)' + $(OBJCOPY_FOR_TARGET) -O binary --only-section=.note.gnu.build-id '$(call SHESCAPE,$@)' '$(call SHESCAPE,$(BUILDDIR))/build-id.s2.bin' + cd '$(call SHESCAPE,$(BUILDDIR))' && \ + $(OBJCOPY_FOR_TARGET) -I binary -B i386 -O elf32-i386 build-id.s2.bin build-id.s2.o && \ + $(OBJCOPY_FOR_TARGET) --add-section .note.GNU-stack='$(call SHESCAPE,$(BUILDDIR))/empty' --set-section-flags .note.GNU-stack=noload,readonly build-id.s2.o + $(OBJCOPY_FOR_TARGET) -O binary --only-section=.note.gnu.build-id '$(call SHESCAPE,$@)' '$(call SHESCAPE,$(BUILDDIR))/build-id.s3.bin' + cd '$(call SHESCAPE,$(BUILDDIR))' && \ + $(OBJCOPY_FOR_TARGET) -I binary -B i386 -O elf32-i386 build-id.s3.bin build-id.s3.o && \ + $(OBJCOPY_FOR_TARGET) --add-section .note.GNU-stack='$(call SHESCAPE,$(BUILDDIR))/empty' --set-section-flags .note.GNU-stack=noload,readonly build-id.s3.o + $(LD_FOR_TARGET) $(LDFLAGS_FOR_TARGET) '$(call OBJESCAPE,$^)' '$(call SHESCAPE,$(BUILDDIR))/build-id.s2.o' '$(call SHESCAPE,$(BUILDDIR))/build-id.s3.o' -T'$(call SHESCAPE,$(BUILDDIR))/linker_nomap.ld' -o '$(call SHESCAPE,$@)' + +$(call MKESCAPE,$(BUILDDIR))/linker.ld: linker_bios.ld.in + $(MKDIR_P) '$(call SHESCAPE,$(BUILDDIR))' + $(CC_FOR_TARGET) -x c -E -P -undef linker_bios.ld.in -o '$(call SHESCAPE,$(BUILDDIR))/linker.ld' + +$(call MKESCAPE,$(BUILDDIR))/limine.elf: $(OBJ) $(call MKESCAPE,$(BUILDDIR))/stage2.map.o $(call MKESCAPE,$(BUILDDIR))/full.map.o + $(MAKE) -f common.mk '$(call SHESCAPE,$(BUILDDIR))/empty' + $(MAKE) -f common.mk '$(call SHESCAPE,$(BUILDDIR))/linker.ld' + $(LD_FOR_TARGET) $(LDFLAGS_FOR_TARGET) '$(call OBJESCAPE,$^)' -T'$(call SHESCAPE,$(BUILDDIR))/linker.ld' -o '$(call SHESCAPE,$@)' + $(OBJCOPY_FOR_TARGET) -O binary --only-section=.note.gnu.build-id '$(call SHESCAPE,$@)' '$(call SHESCAPE,$(BUILDDIR))/build-id.s2.bin' + cd '$(call SHESCAPE,$(BUILDDIR))' && \ + $(OBJCOPY_FOR_TARGET) -I binary -B i386 -O elf32-i386 build-id.s2.bin build-id.s2.o && \ + $(OBJCOPY_FOR_TARGET) --add-section .note.GNU-stack='$(call SHESCAPE,$(BUILDDIR))/empty' --set-section-flags .note.GNU-stack=noload,readonly build-id.s2.o + $(OBJCOPY_FOR_TARGET) -O binary --only-section=.note.gnu.build-id '$(call SHESCAPE,$@)' '$(call SHESCAPE,$(BUILDDIR))/build-id.s3.bin' + cd '$(call SHESCAPE,$(BUILDDIR))' && \ + $(OBJCOPY_FOR_TARGET) -I binary -B i386 -O elf32-i386 build-id.s3.bin build-id.s3.o && \ + $(OBJCOPY_FOR_TARGET) --add-section .note.GNU-stack='$(call SHESCAPE,$(BUILDDIR))/empty' --set-section-flags .note.GNU-stack=noload,readonly build-id.s3.o + $(LD_FOR_TARGET) $(LDFLAGS_FOR_TARGET) '$(call OBJESCAPE,$^)' '$(call SHESCAPE,$(BUILDDIR))/build-id.s2.o' '$(call SHESCAPE,$(BUILDDIR))/build-id.s3.o' -T'$(call SHESCAPE,$(BUILDDIR))/linker.ld' -o '$(call SHESCAPE,$@)' + +endif + +ifeq ($(TARGET),uefi-x86-64) + +$(call MKESCAPE,$(BUILDDIR))/full.map.o: $(call MKESCAPE,$(BUILDDIR))/limine_nomap.elf + cd '$(call SHESCAPE,$(BUILDDIR))' && \ + '$(call SHESCAPE,$(SRCDIR))/gensyms.sh' '$(call SHESCAPE,$<)' full 64 '\.text' + $(CC_FOR_TARGET) $(CFLAGS_FOR_TARGET) $(CPPFLAGS_FOR_TARGET) -c '$(call SHESCAPE,$(BUILDDIR))/full.map.S' -o '$(call SHESCAPE,$@)' + rm -f '$(call SHESCAPE,$(BUILDDIR))/full.map.S' '$(call SHESCAPE,$(BUILDDIR))/full.map.d' + +$(call MKESCAPE,$(BUILDDIR))/BOOTX64.EFI: $(call MKESCAPE,$(BUILDDIR))/limine.elf + $(OBJCOPY_FOR_TARGET) -O binary '$(call SHESCAPE,$<)' '$(call SHESCAPE,$@)' + chmod -x '$(call SHESCAPE,$@)' + dd if=/dev/zero of='$(call SHESCAPE,$@)' bs=4096 count=0 seek=$$(( ($$(wc -c < '$(call SHESCAPE,$@)') + 4095) / 4096 )) 2>/dev/null + +$(call MKESCAPE,$(BUILDDIR))/linker_nomap.ld: linker_uefi_x86_64.ld.in + $(MKDIR_P) '$(call SHESCAPE,$(BUILDDIR))' + $(CC_FOR_TARGET) -x c -E -P -undef -DLINKER_NOMAP linker_uefi_x86_64.ld.in -o '$(call SHESCAPE,$(BUILDDIR))/linker_nomap.ld' + +$(call MKESCAPE,$(BUILDDIR))/limine_nomap.elf: $(OBJ) + $(MAKE) -f common.mk '$(call SHESCAPE,$(BUILDDIR))/linker_nomap.ld' + $(LD_FOR_TARGET) \ + -T'$(call SHESCAPE,$(BUILDDIR))/linker_nomap.ld' \ + $(LDFLAGS_FOR_TARGET) '$(call OBJESCAPE,$^)' -o '$(call SHESCAPE,$@)' + +$(call MKESCAPE,$(BUILDDIR))/linker.ld: linker_uefi_x86_64.ld.in + $(MKDIR_P) '$(call SHESCAPE,$(BUILDDIR))' + $(CC_FOR_TARGET) -x c -E -P -undef linker_uefi_x86_64.ld.in -o '$(call SHESCAPE,$(BUILDDIR))/linker.ld' + +$(call MKESCAPE,$(BUILDDIR))/limine.elf: $(OBJ) $(call MKESCAPE,$(BUILDDIR))/full.map.o + $(MAKE) -f common.mk '$(call SHESCAPE,$(BUILDDIR))/linker.ld' + $(LD_FOR_TARGET) \ + -T'$(call SHESCAPE,$(BUILDDIR))/linker.ld' \ + $(LDFLAGS_FOR_TARGET) '$(call OBJESCAPE,$^)' -o '$(call SHESCAPE,$@)' + +endif + +ifeq ($(TARGET),uefi-aarch64) + +$(call MKESCAPE,$(BUILDDIR))/full.map.o: $(call MKESCAPE,$(BUILDDIR))/limine_nomap.elf + cd '$(call SHESCAPE,$(BUILDDIR))' && \ + '$(call SHESCAPE,$(SRCDIR))/gensyms.sh' '$(call SHESCAPE,$<)' full 64 '\.text' + $(CC_FOR_TARGET) $(CFLAGS_FOR_TARGET) $(CPPFLAGS_FOR_TARGET) -c '$(call SHESCAPE,$(BUILDDIR))/full.map.S' -o '$(call SHESCAPE,$@)' + rm -f '$(call SHESCAPE,$(BUILDDIR))/full.map.S' '$(call SHESCAPE,$(BUILDDIR))/full.map.d' + +$(call MKESCAPE,$(BUILDDIR))/BOOTAA64.EFI: $(call MKESCAPE,$(BUILDDIR))/limine.elf + $(OBJCOPY_FOR_TARGET) -O binary '$(call SHESCAPE,$<)' '$(call SHESCAPE,$@)' + chmod -x '$(call SHESCAPE,$@)' + dd if=/dev/zero of='$(call SHESCAPE,$@)' bs=4096 count=0 seek=$$(( ($$(wc -c < '$(call SHESCAPE,$@)') + 4095) / 4096 )) 2>/dev/null + +$(call MKESCAPE,$(BUILDDIR))/linker_nomap.ld: linker_uefi_aarch64.ld.in + $(MKDIR_P) '$(call SHESCAPE,$(BUILDDIR))' + $(CC_FOR_TARGET) -x c -E -P -undef -DLINKER_NOMAP linker_uefi_aarch64.ld.in -o '$(call SHESCAPE,$(BUILDDIR))/linker_nomap.ld' + +$(call MKESCAPE,$(BUILDDIR))/limine_nomap.elf: $(OBJ) + $(MAKE) -f common.mk '$(call SHESCAPE,$(BUILDDIR))/linker_nomap.ld' + $(LD_FOR_TARGET) \ + -T'$(call SHESCAPE,$(BUILDDIR))/linker_nomap.ld' \ + $(LDFLAGS_FOR_TARGET) '$(call OBJESCAPE,$^)' -o '$(call SHESCAPE,$@)' + +$(call MKESCAPE,$(BUILDDIR))/linker.ld: linker_uefi_aarch64.ld.in + $(MKDIR_P) '$(call SHESCAPE,$(BUILDDIR))' + $(CC_FOR_TARGET) -x c -E -P -undef linker_uefi_aarch64.ld.in -o '$(call SHESCAPE,$(BUILDDIR))/linker.ld' + +$(call MKESCAPE,$(BUILDDIR))/limine.elf: $(OBJ) $(call MKESCAPE,$(BUILDDIR))/full.map.o + $(MAKE) -f common.mk '$(call SHESCAPE,$(BUILDDIR))/linker.ld' + $(LD_FOR_TARGET) \ + -T'$(call SHESCAPE,$(BUILDDIR))/linker.ld' \ + $(LDFLAGS_FOR_TARGET) '$(call OBJESCAPE,$^)' -o '$(call SHESCAPE,$@)' +endif + +ifeq ($(TARGET),uefi-riscv64) + +$(call MKESCAPE,$(BUILDDIR))/full.map.o: $(call MKESCAPE,$(BUILDDIR))/limine_nomap.elf + cd '$(call SHESCAPE,$(BUILDDIR))' && \ + '$(call SHESCAPE,$(SRCDIR))/gensyms.sh' '$(call SHESCAPE,$<)' full 64 '\.text' + $(CC_FOR_TARGET) $(CFLAGS_FOR_TARGET) $(CPPFLAGS_FOR_TARGET) -c '$(call SHESCAPE,$(BUILDDIR))/full.map.S' -o '$(call SHESCAPE,$@)' + rm -f '$(call SHESCAPE,$(BUILDDIR))/full.map.S' '$(call SHESCAPE,$(BUILDDIR))/full.map.d' + +$(call MKESCAPE,$(BUILDDIR))/BOOTRISCV64.EFI: $(call MKESCAPE,$(BUILDDIR))/limine.elf + $(OBJCOPY_FOR_TARGET) -O binary '$(call SHESCAPE,$<)' '$(call SHESCAPE,$@)' + chmod -x '$(call SHESCAPE,$@)' + dd if=/dev/zero of='$(call SHESCAPE,$@)' bs=4096 count=0 seek=$$(( ($$(wc -c < '$(call SHESCAPE,$@)') + 4095) / 4096 )) 2>/dev/null + +$(call MKESCAPE,$(BUILDDIR))/linker_nomap.ld: linker_uefi_riscv64.ld.in + $(MKDIR_P) '$(call SHESCAPE,$(BUILDDIR))' + $(CC_FOR_TARGET) -x c -E -P -undef -DLINKER_NOMAP linker_uefi_riscv64.ld.in -o '$(call SHESCAPE,$(BUILDDIR))/linker_nomap.ld' + +$(call MKESCAPE,$(BUILDDIR))/limine_nomap.elf: $(OBJ) + $(MAKE) -f common.mk '$(call SHESCAPE,$(BUILDDIR))/linker_nomap.ld' + $(LD_FOR_TARGET) \ + -T'$(call SHESCAPE,$(BUILDDIR))/linker_nomap.ld' \ + $(LDFLAGS_FOR_TARGET) '$(call OBJESCAPE,$^)' -o '$(call SHESCAPE,$@)' + +$(call MKESCAPE,$(BUILDDIR))/linker.ld: linker_uefi_riscv64.ld.in + $(MKDIR_P) '$(call SHESCAPE,$(BUILDDIR))' + $(CC_FOR_TARGET) -x c -E -P -undef linker_uefi_riscv64.ld.in -o '$(call SHESCAPE,$(BUILDDIR))/linker.ld' + +$(call MKESCAPE,$(BUILDDIR))/limine.elf: $(OBJ) $(call MKESCAPE,$(BUILDDIR))/full.map.o + $(MAKE) -f common.mk '$(call SHESCAPE,$(BUILDDIR))/linker.ld' + $(LD_FOR_TARGET) \ + -T'$(call SHESCAPE,$(BUILDDIR))/linker.ld' \ + $(LDFLAGS_FOR_TARGET) '$(call OBJESCAPE,$^)' -o '$(call SHESCAPE,$@)' +endif + +ifeq ($(TARGET),uefi-loongarch64) + +$(call MKESCAPE,$(BUILDDIR))/full.map.o: $(call MKESCAPE,$(BUILDDIR))/limine_nomap.elf + cd '$(call SHESCAPE,$(BUILDDIR))' && \ + '$(call SHESCAPE,$(SRCDIR))/gensyms.sh' '$(call SHESCAPE,$<)' full 64 '\.text' + $(CC_FOR_TARGET) $(CFLAGS_FOR_TARGET) $(CPPFLAGS_FOR_TARGET) -c '$(call SHESCAPE,$(BUILDDIR))/full.map.S' -o '$(call SHESCAPE,$@)' + rm -f '$(call SHESCAPE,$(BUILDDIR))/full.map.S' '$(call SHESCAPE,$(BUILDDIR))/full.map.d' + +$(call MKESCAPE,$(BUILDDIR))/BOOTLOONGARCH64.EFI: $(call MKESCAPE,$(BUILDDIR))/limine.elf + $(OBJCOPY_FOR_TARGET) -O binary '$(call SHESCAPE,$<)' '$(call SHESCAPE,$@)' + chmod -x '$(call SHESCAPE,$@)' + dd if=/dev/zero of='$(call SHESCAPE,$@)' bs=4096 count=0 seek=$$(( ($$(wc -c < '$(call SHESCAPE,$@)') + 4095) / 4096 )) 2>/dev/null + +$(call MKESCAPE,$(BUILDDIR))/linker_nomap.ld: linker_uefi_loongarch64.ld.in + $(MKDIR_P) '$(call SHESCAPE,$(BUILDDIR))' + $(CC_FOR_TARGET) -x c -E -P -undef -DLINKER_NOMAP linker_uefi_loongarch64.ld.in -o '$(call SHESCAPE,$(BUILDDIR))/linker_nomap.ld' + +$(call MKESCAPE,$(BUILDDIR))/limine_nomap.elf: $(OBJ) + $(MAKE) -f common.mk '$(call SHESCAPE,$(BUILDDIR))/linker_nomap.ld' + $(LD_FOR_TARGET) \ + -T'$(call SHESCAPE,$(BUILDDIR))/linker_nomap.ld' \ + $(LDFLAGS_FOR_TARGET) '$(call OBJESCAPE,$^)' -o '$(call SHESCAPE,$@)' + +$(call MKESCAPE,$(BUILDDIR))/linker.ld: linker_uefi_loongarch64.ld.in + $(MKDIR_P) '$(call SHESCAPE,$(BUILDDIR))' + $(CC_FOR_TARGET) -x c -E -P -undef linker_uefi_loongarch64.ld.in -o '$(call SHESCAPE,$(BUILDDIR))/linker.ld' + +$(call MKESCAPE,$(BUILDDIR))/limine.elf: $(OBJ) $(call MKESCAPE,$(BUILDDIR))/full.map.o + $(MAKE) -f common.mk '$(call SHESCAPE,$(BUILDDIR))/linker.ld' + $(LD_FOR_TARGET) \ + -T'$(call SHESCAPE,$(BUILDDIR))/linker.ld' \ + $(LDFLAGS_FOR_TARGET) '$(call OBJESCAPE,$^)' -o '$(call SHESCAPE,$@)' +endif + +ifeq ($(TARGET),uefi-ia32) + +$(call MKESCAPE,$(BUILDDIR))/full.map.o: $(call MKESCAPE,$(BUILDDIR))/limine_nomap.elf + cd '$(call SHESCAPE,$(BUILDDIR))' && \ + '$(call SHESCAPE,$(SRCDIR))/gensyms.sh' '$(call SHESCAPE,$<)' full 32 '\.text' + $(CC_FOR_TARGET) $(CFLAGS_FOR_TARGET) $(CPPFLAGS_FOR_TARGET) -c '$(call SHESCAPE,$(BUILDDIR))/full.map.S' -o '$(call SHESCAPE,$@)' + rm -f '$(call SHESCAPE,$(BUILDDIR))/full.map.S' '$(call SHESCAPE,$(BUILDDIR))/full.map.d' + +$(call MKESCAPE,$(BUILDDIR))/BOOTIA32.EFI: $(call MKESCAPE,$(BUILDDIR))/limine.elf + $(OBJCOPY_FOR_TARGET) -O binary '$(call SHESCAPE,$<)' '$(call SHESCAPE,$@)' + chmod -x '$(call SHESCAPE,$@)' + dd if=/dev/zero of='$(call SHESCAPE,$@)' bs=4096 count=0 seek=$$(( ($$(wc -c < '$(call SHESCAPE,$@)') + 4095) / 4096 )) 2>/dev/null + +$(call MKESCAPE,$(BUILDDIR))/linker_nomap.ld: linker_uefi_ia32.ld.in + $(MKDIR_P) '$(call SHESCAPE,$(BUILDDIR))' + $(CC_FOR_TARGET) -x c -E -P -undef -DLINKER_NOMAP linker_uefi_ia32.ld.in -o '$(call SHESCAPE,$(BUILDDIR))/linker_nomap.ld' + +$(call MKESCAPE,$(BUILDDIR))/limine_nomap.elf: $(OBJ) + $(MAKE) -f common.mk '$(call SHESCAPE,$(BUILDDIR))/linker_nomap.ld' + $(LD_FOR_TARGET) \ + -T'$(call SHESCAPE,$(BUILDDIR))/linker_nomap.ld' \ + $(LDFLAGS_FOR_TARGET) '$(call OBJESCAPE,$^)' -o '$(call SHESCAPE,$@)' + +$(call MKESCAPE,$(BUILDDIR))/linker.ld: linker_uefi_ia32.ld.in + $(MKDIR_P) '$(call SHESCAPE,$(BUILDDIR))' + $(CC_FOR_TARGET) -x c -E -P -undef linker_uefi_ia32.ld.in -o '$(call SHESCAPE,$(BUILDDIR))/linker.ld' + +$(call MKESCAPE,$(BUILDDIR))/limine.elf: $(OBJ) $(call MKESCAPE,$(BUILDDIR))/full.map.o + $(MAKE) -f common.mk '$(call SHESCAPE,$(BUILDDIR))/linker.ld' + $(LD_FOR_TARGET) \ + -T'$(call SHESCAPE,$(BUILDDIR))/linker.ld' \ + $(LDFLAGS_FOR_TARGET) '$(call OBJESCAPE,$^)' -o '$(call SHESCAPE,$@)' + +endif + +-include $(HEADER_DEPS) + +$(call MKESCAPE,$(BUILDDIR))/%.o: ../%.c + $(MKDIR_P) "$$(dirname '$(call SHESCAPE,$@)')" + $(CC_FOR_TARGET) $(CFLAGS_FOR_TARGET) $(CPPFLAGS_FOR_TARGET) -c '$(call SHESCAPE,$<)' -o '$(call SHESCAPE,$@)' + +$(call MKESCAPE,$(BUILDDIR))/%.o: ../%.S + $(MKDIR_P) "$$(dirname '$(call SHESCAPE,$@)')" + $(CC_FOR_TARGET) $(CFLAGS_FOR_TARGET) $(CPPFLAGS_FOR_TARGET) -c '$(call SHESCAPE,$<)' -o '$(call SHESCAPE,$@)' + +ifeq ($(TARGET),bios) +$(call MKESCAPE,$(BUILDDIR))/%.s2.o: ../%.s2.c + $(MKDIR_P) "$$(dirname '$(call SHESCAPE,$@)')" + $(CC_FOR_TARGET) $(CFLAGS_FOR_TARGET) $(S2CFLAGS) $(CPPFLAGS_FOR_TARGET) -c '$(call SHESCAPE,$<)' -o '$(call SHESCAPE,$@)' +endif + +ifeq ($(TARGET),bios) +$(call MKESCAPE,$(BUILDDIR))/%.o: ../%.asm_ia32 + $(MKDIR_P) "$$(dirname '$(call SHESCAPE,$@)')" + nasm '$(call SHESCAPE,$<)' $(NASMFLAGS_FOR_TARGET) -o '$(call SHESCAPE,$@)' + +$(call MKESCAPE,$(BUILDDIR))/%.o: ../%.asm_bios_ia32 + $(MKDIR_P) "$$(dirname '$(call SHESCAPE,$@)')" + nasm '$(call SHESCAPE,$<)' $(NASMFLAGS_FOR_TARGET) -o '$(call SHESCAPE,$@)' + +$(call MKESCAPE,$(BUILDDIR))/%.o: ../%.asm_x86 + $(MKDIR_P) "$$(dirname '$(call SHESCAPE,$@)')" + nasm '$(call SHESCAPE,$<)' $(NASMFLAGS_FOR_TARGET) -o '$(call SHESCAPE,$@)' +endif + +ifeq ($(TARGET),uefi-x86-64) +$(call MKESCAPE,$(BUILDDIR))/%.o: ../%.asm_x86_64 + $(MKDIR_P) "$$(dirname '$(call SHESCAPE,$@)')" + nasm '$(call SHESCAPE,$<)' $(NASMFLAGS_FOR_TARGET) -o '$(call SHESCAPE,$@)' + +$(call MKESCAPE,$(BUILDDIR))/%.o: ../%.asm_uefi_x86_64 + $(MKDIR_P) "$$(dirname '$(call SHESCAPE,$@)')" + nasm '$(call SHESCAPE,$<)' $(NASMFLAGS_FOR_TARGET) -o '$(call SHESCAPE,$@)' + +$(call MKESCAPE,$(BUILDDIR))/%.o: ../%.asm_x86 + $(MKDIR_P) "$$(dirname '$(call SHESCAPE,$@)')" + nasm '$(call SHESCAPE,$<)' $(NASMFLAGS_FOR_TARGET) -o '$(call SHESCAPE,$@)' +endif + +ifeq ($(TARGET),uefi-aarch64) +$(call MKESCAPE,$(BUILDDIR))/%.o: ../%.asm_aarch64 + $(MKDIR_P) "$$(dirname '$(call SHESCAPE,$@)')" + $(CC_FOR_TARGET) $(CFLAGS_FOR_TARGET) $(CPPFLAGS_FOR_TARGET) -x assembler-with-cpp -c '$(call SHESCAPE,$<)' -o '$(call SHESCAPE,$@)' + +$(call MKESCAPE,$(BUILDDIR))/%.o: ../%.asm_uefi_aarch64 + $(MKDIR_P) "$$(dirname '$(call SHESCAPE,$@)')" + $(CC_FOR_TARGET) $(CFLAGS_FOR_TARGET) $(CPPFLAGS_FOR_TARGET) -x assembler-with-cpp -c '$(call SHESCAPE,$<)' -o '$(call SHESCAPE,$@)' +endif + +ifeq ($(TARGET),uefi-riscv64) +$(call MKESCAPE,$(BUILDDIR))/%.o: ../%.asm_riscv64 + $(MKDIR_P) "$$(dirname '$(call SHESCAPE,$@)')" + $(CC_FOR_TARGET) $(CFLAGS_FOR_TARGET) $(CPPFLAGS_FOR_TARGET) -x assembler-with-cpp -c '$(call SHESCAPE,$<)' -o '$(call SHESCAPE,$@)' + +$(call MKESCAPE,$(BUILDDIR))/%.o: ../%.asm_uefi_riscv64 + $(MKDIR_P) "$$(dirname '$(call SHESCAPE,$@)')" + $(CC_FOR_TARGET) $(CFLAGS_FOR_TARGET) $(CPPFLAGS_FOR_TARGET) -x assembler-with-cpp -c '$(call SHESCAPE,$<)' -o '$(call SHESCAPE,$@)' +endif + +ifeq ($(TARGET),uefi-loongarch64) +$(call MKESCAPE,$(BUILDDIR))/%.o: ../%.asm_loongarch64 + $(MKDIR_P) "$$(dirname '$(call SHESCAPE,$@)')" + $(CC_FOR_TARGET) $(CFLAGS_FOR_TARGET) $(CPPFLAGS_FOR_TARGET) -x assembler-with-cpp -c '$(call SHESCAPE,$<)' -o '$(call SHESCAPE,$@)' + +$(call MKESCAPE,$(BUILDDIR))/%.o: ../%.asm_uefi_loongarch64 + $(MKDIR_P) "$$(dirname '$(call SHESCAPE,$@)')" + $(CC_FOR_TARGET) $(CFLAGS_FOR_TARGET) $(CPPFLAGS_FOR_TARGET) -x assembler-with-cpp -c '$(call SHESCAPE,$<)' -o '$(call SHESCAPE,$@)' +endif + +ifeq ($(TARGET),uefi-ia32) +$(call MKESCAPE,$(BUILDDIR))/%.o: ../%.asm_ia32 + $(MKDIR_P) "$$(dirname '$(call SHESCAPE,$@)')" + nasm '$(call SHESCAPE,$<)' $(NASMFLAGS_FOR_TARGET) -o '$(call SHESCAPE,$@)' + +$(call MKESCAPE,$(BUILDDIR))/%.o: ../%.asm_uefi_ia32 + $(MKDIR_P) "$$(dirname '$(call SHESCAPE,$@)')" + nasm '$(call SHESCAPE,$<)' $(NASMFLAGS_FOR_TARGET) -o '$(call SHESCAPE,$@)' + +$(call MKESCAPE,$(BUILDDIR))/%.o: ../%.asm_x86 + $(MKDIR_P) "$$(dirname '$(call SHESCAPE,$@)')" + nasm '$(call SHESCAPE,$<)' $(NASMFLAGS_FOR_TARGET) -o '$(call SHESCAPE,$@)' +endif diff --git a/limine/common/crypt/blake2b.c b/limine/common/crypt/blake2b.c new file mode 100644 index 0000000..b62183f --- /dev/null +++ b/limine/common/crypt/blake2b.c @@ -0,0 +1,220 @@ +// This blake2b implementation comes from the GNU coreutils project. +// https://github.com/coreutils/coreutils/blob/master/src/blake2/blake2b-ref.c + +#include +#include +#include +#include +#include + +#define BLAKE2B_BLOCK_BYTES 128 +#define BLAKE2B_KEY_BYTES 64 +#define BLAKE2B_SALT_BYTES 16 +#define BLAKE2B_PERSONAL_BYTES 16 + +static const uint64_t blake2b_iv[8] = { + 0x6a09e667f3bcc908, + 0xbb67ae8584caa73b, + 0x3c6ef372fe94f82b, + 0xa54ff53a5f1d36f1, + 0x510e527fade682d1, + 0x9b05688c2b3e6c1f, + 0x1f83d9abfb41bd6b, + 0x5be0cd19137e2179, +}; + +static const uint8_t blake2b_sigma[12][16] = { + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }, + { 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 }, + { 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4 }, + { 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8 }, + { 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13 }, + { 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9 }, + { 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11 }, + { 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10 }, + { 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5 }, + { 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0 }, + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }, + { 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 }, +}; + +struct blake2b_state { + uint64_t h[8]; + uint64_t t[2]; + uint64_t f[2]; + uint8_t buf[BLAKE2B_BLOCK_BYTES]; + size_t buf_len; + uint8_t last_node; +}; + +struct blake2b_param { + uint8_t digest_length; + uint8_t key_length; + uint8_t fan_out; + uint8_t depth; + uint32_t leaf_length; + uint32_t node_offset; + uint32_t xof_length; + uint8_t node_depth; + uint8_t inner_length; + uint8_t reserved[14]; + uint8_t salt[BLAKE2B_SALT_BYTES]; + uint8_t personal[BLAKE2B_PERSONAL_BYTES]; +} __attribute__((packed)); + +// Safe unaligned load/store helpers for architectures with strict alignment (ARM64, RISC-V) +static inline uint64_t load64_le(const void *src) { + uint64_t val; + memcpy(&val, src, sizeof(val)); + return val; +} + +static inline void store64_le(void *dst, uint64_t val) { + memcpy(dst, &val, sizeof(val)); +} + +static void blake2b_increment_counter(struct blake2b_state *state, uint64_t inc) { + state->t[0] += inc; + state->t[1] += state->t[0] < inc; +} + +static inline uint64_t rotr64(uint64_t w, unsigned c) { + return (w >> c) | (w << (64 - c)); +} + +#define G(r, i, a, b, c, d) do { \ + a = a + b + m[blake2b_sigma[r][2 * i + 0]]; \ + d = rotr64(d ^ a, 32); \ + c = c + d; \ + b = rotr64(b ^ c, 24); \ + a = a + b + m[blake2b_sigma[r][2 * i + 1]]; \ + d = rotr64(d ^ a, 16); \ + c = c + d; \ + b = rotr64(b ^ c, 63); \ + } while (0) + +#define ROUND(r) do { \ + G(r, 0, v[0], v[4], v[8], v[12]); \ + G(r, 1, v[1], v[5], v[9], v[13]); \ + G(r, 2, v[2], v[6], v[10], v[14]); \ + G(r, 3, v[3], v[7], v[11], v[15]); \ + G(r, 4, v[0], v[5], v[10], v[15]); \ + G(r, 5, v[1], v[6], v[11], v[12]); \ + G(r, 6, v[2], v[7], v[8], v[13]); \ + G(r, 7, v[3], v[4], v[9], v[14]); \ + } while (0) + +static void blake2b_compress(struct blake2b_state *state, const uint8_t block[static BLAKE2B_BLOCK_BYTES]) { + uint64_t m[16]; + uint64_t v[16]; + + for (int i = 0; i < 16; i++) { + m[i] = load64_le(block + i * sizeof(m[i])); + } + + for (int i = 0; i < 8; i++) { + v[i] = state->h[i]; + } + + v[8] = blake2b_iv[0]; + v[9] = blake2b_iv[1]; + v[10] = blake2b_iv[2]; + v[11] = blake2b_iv[3]; + v[12] = blake2b_iv[4] ^ state->t[0]; + v[13] = blake2b_iv[5] ^ state->t[1]; + v[14] = blake2b_iv[6] ^ state->f[0]; + v[15] = blake2b_iv[7] ^ state->f[1]; + + ROUND(0); + ROUND(1); + ROUND(2); + ROUND(3); + ROUND(4); + ROUND(5); + ROUND(6); + ROUND(7); + ROUND(8); + ROUND(9); + ROUND(10); + ROUND(11); + + for (int i = 0; i < 8; i++) { + state->h[i] = state->h[i] ^ v[i] ^ v[i + 8]; + } +} + +#undef G +#undef ROUND + +static void blake2b_init(struct blake2b_state *state) { + struct blake2b_param param = {0}; + + param.digest_length = BLAKE2B_OUT_BYTES; + param.fan_out = 1; + param.depth = 1; + + memset(state, 0, sizeof(struct blake2b_state)); + + for (int i = 0; i < 8; i++) { + state->h[i] = blake2b_iv[i]; + } + + for (int i = 0; i < 8; i++) { + state->h[i] ^= load64_le((uint8_t *)¶m + sizeof(state->h[i]) * i); + } +} + +static void blake2b_update(struct blake2b_state *state, const void *in, size_t in_len) { + if (in_len == 0) { + return; + } + + size_t left = state->buf_len; + size_t fill = BLAKE2B_BLOCK_BYTES - left; + + if (in_len > fill) { + state->buf_len = 0; + + memcpy(state->buf + left, in, fill); + blake2b_increment_counter(state, BLAKE2B_BLOCK_BYTES); + blake2b_compress(state, state->buf); + + in += fill; + in_len -= fill; + + while (in_len > BLAKE2B_BLOCK_BYTES) { + blake2b_increment_counter(state, BLAKE2B_BLOCK_BYTES); + blake2b_compress(state, in); + + in += BLAKE2B_BLOCK_BYTES; + in_len -= BLAKE2B_BLOCK_BYTES; + } + } + + memcpy(state->buf + state->buf_len, in, in_len); + state->buf_len += in_len; +} + +static void blake2b_final(struct blake2b_state *state, void *out) { + uint8_t buffer[BLAKE2B_OUT_BYTES] = {0}; + + blake2b_increment_counter(state, state->buf_len); + state->f[0] = (uint64_t)-1; + memset(state->buf + state->buf_len, 0, BLAKE2B_BLOCK_BYTES - state->buf_len); + blake2b_compress(state, state->buf); + + for (int i = 0; i < 8; i++) { + store64_le(buffer + sizeof(state->h[i]) * i, state->h[i]); + } + + memcpy(out, buffer, BLAKE2B_OUT_BYTES); + memset(buffer, 0, sizeof(buffer)); +} + +void blake2b(void *out, const void *in, size_t in_len) { + struct blake2b_state state = {0}; + + blake2b_init(&state); + blake2b_update(&state, in, in_len); + blake2b_final(&state, out); +} diff --git a/limine/common/crypt/blake2b.h b/limine/common/crypt/blake2b.h new file mode 100644 index 0000000..313a7c6 --- /dev/null +++ b/limine/common/crypt/blake2b.h @@ -0,0 +1,10 @@ +#ifndef CRYPT__BLAKE2B_H__ +#define CRYPT__BLAKE2B_H__ + +#include + +#define BLAKE2B_OUT_BYTES 64 + +void blake2b(void *out, const void *in, size_t in_len); + +#endif diff --git a/limine/common/drivers/disk.h b/limine/common/drivers/disk.h new file mode 100644 index 0000000..bb3e83e --- /dev/null +++ b/limine/common/drivers/disk.h @@ -0,0 +1,26 @@ +#ifndef DRIVERS__DISK_H__ +#define DRIVERS__DISK_H__ + +#include +#include +#include +#include + +#if defined (UEFI) + +#include + +struct volume *disk_volume_from_efi_handle(EFI_HANDLE efi_handle); + +#endif + +enum { + DISK_SUCCESS, + DISK_NO_MEDIA, + DISK_FAILURE +}; + +void disk_create_index(void); +int disk_read_sectors(struct volume *volume, void *buf, uint64_t block, size_t count); + +#endif diff --git a/limine/common/drivers/disk.s2.c b/limine/common/drivers/disk.s2.c new file mode 100644 index 0000000..eba8e24 --- /dev/null +++ b/limine/common/drivers/disk.s2.c @@ -0,0 +1,846 @@ +#include +#include +#include +#include +#include +#if defined (BIOS) +# include +#elif defined (UEFI) +# include +# include +#endif +#include +#include +#include +#include +#include +#include + +#define DEFAULT_FASTEST_XFER_SIZE 64 +#define MAX_FASTEST_XFER_SIZE 512 + +#if defined (BIOS) + +struct dpte { + uint16_t io_port; + uint16_t control_port; + uint8_t head_reg_upper; + uint8_t bios_vendor_specific; + uint8_t irq_info; + uint8_t block_count_multiple; + uint8_t dma_info; + uint8_t pio_info; + uint16_t flags; + uint16_t reserved; + uint8_t revision; + uint8_t checksum; +} __attribute__((packed)); + +struct bios_drive_params { + uint16_t buf_size; + uint16_t info_flags; + uint32_t cyl; + uint32_t heads; + uint32_t sects; + uint64_t lba_count; + uint16_t bytes_per_sect; + uint16_t dpte_off; + uint16_t dpte_seg; +} __attribute__((packed)); + +struct dap { + uint16_t size; + uint16_t count; + uint16_t offset; + uint16_t segment; + uint64_t lba; +}; + +#define XFER_BUF_SIZE (xfer_sizes[SIZEOF_ARRAY(xfer_sizes) - 1] * 512) +static const size_t xfer_sizes[] = { 1, 2, 4, 8, 16, 24, 32, 48, 64 }; +static uint8_t *xfer_buf = NULL; + +static size_t fastest_xfer_size(struct volume *volume) { + struct dap dap = {0}; + + if (xfer_buf == NULL) + xfer_buf = conv_mem_alloc(XFER_BUF_SIZE); + + size_t fastest_size = 1; + uint64_t last_speed = (uint64_t)-1; + + for (size_t i = 0; i < SIZEOF_ARRAY(xfer_sizes); i++) { + if (xfer_sizes[i] * volume->sector_size > XFER_BUF_SIZE) { + break; + } + + dap.size = 16; + dap.count = xfer_sizes[i]; + dap.segment = rm_seg(xfer_buf); + dap.offset = rm_off(xfer_buf); + dap.lba = 0; + + uint64_t start_timestamp = rdtsc(); + for (size_t j = 0; j < XFER_BUF_SIZE / 512; j += xfer_sizes[i]) { + struct rm_regs r = {0}; + r.eax = 0x4200; + r.edx = volume->drive; + r.esi = (uint32_t)rm_off(&dap); + r.ds = rm_seg(&dap); + rm_int(0x13, &r, &r); + if (r.eflags & EFLAGS_CF) { + int ah = (r.eax >> 8) & 0xff; + print("Disk error %x. Drive %x", ah, volume->drive); + return 8; + } + dap.lba += xfer_sizes[i]; + } + uint64_t end_timestamp = rdtsc(); + + uint64_t speed = end_timestamp - start_timestamp; + + if (speed < last_speed) { + last_speed = speed; + fastest_size = xfer_sizes[i]; + } + } + + return fastest_size; +} + +int disk_read_sectors(struct volume *volume, void *buf, uint64_t block, size_t count) { + struct dap dap = {0}; + + if (count * volume->sector_size > XFER_BUF_SIZE) + panic(false, "XFER"); + + if (xfer_buf == NULL) + xfer_buf = conv_mem_alloc(XFER_BUF_SIZE); + + dap.size = 16; + dap.count = count; + dap.segment = rm_seg(xfer_buf); + dap.offset = rm_off(xfer_buf); + dap.lba = block; + + struct rm_regs r = {0}; + r.eax = 0x4200; + r.edx = volume->drive; + r.esi = (uint32_t)rm_off(&dap); + r.ds = rm_seg(&dap); + + rm_int(0x13, &r, &r); + + if (r.eflags & EFLAGS_CF) { + return DISK_FAILURE; + } + + if (buf != NULL) + memcpy(buf, xfer_buf, count * volume->sector_size); + + return DISK_SUCCESS; +} + +static bool detect_sector_size(struct volume *volume) { + struct dap dap = {0}; + + if (xfer_buf == NULL) + xfer_buf = conv_mem_alloc(XFER_BUF_SIZE); + + dap.size = 16; + dap.count = 1; + dap.segment = rm_seg(xfer_buf); + dap.offset = rm_off(xfer_buf); + dap.lba = 0; + + struct rm_regs r = {0}; + r.eax = 0x4200; + r.edx = volume->drive; + r.esi = (uint32_t)rm_off(&dap); + r.ds = rm_seg(&dap); + + struct rm_regs r_copy = r; + struct dap dap_copy = dap; + + memset(xfer_buf, 0, XFER_BUF_SIZE); + + rm_int(0x13, &r, &r); + + if (r.eflags & EFLAGS_CF) { + return false; + } + + size_t sector_size_a = 0; + for (long i = XFER_BUF_SIZE - 1; i >= 0; i--) { + if (xfer_buf[i] != 0) { + sector_size_a = i + 1; + break; + } + } + + r = r_copy; + dap = dap_copy; + + memset(xfer_buf, 0xff, XFER_BUF_SIZE); + + rm_int(0x13, &r, &r); + + if (r.eflags & EFLAGS_CF) { + return false; + } + + size_t sector_size_b = 0; + for (long i = XFER_BUF_SIZE - 1; i >= 0; i--) { + if (xfer_buf[i] != 0xff) { + sector_size_b = i + 1; + break; + } + } + + volume->sector_size = sector_size_a > sector_size_b ? sector_size_a : sector_size_b; + + if (volume->sector_size == 0) { + return false; + } + + return true; +} + +void disk_create_index(void) { + // Disk count (only non-removable) at 0040:0075 + uint8_t bda_disk_count = mminb(rm_desegment(0x0040, 0x0075)); + + int optical_indices = 1, hdd_indices = 1, consumed_bda_disks = 0; + + for (uint8_t drive = 0x80; drive < 0xf0; drive++) { + struct rm_regs r = {0}; + struct bios_drive_params drive_params; + + r.eax = 0x4800; + r.edx = drive; + r.ds = rm_seg(&drive_params); + r.esi = rm_off(&drive_params); + + drive_params.buf_size = sizeof(struct bios_drive_params); + + rm_int(0x13, &r, &r); + + if (r.eflags & EFLAGS_CF) { + continue; + } + + bool is_removable = drive_params.info_flags & (1 << 2); + + struct dpte *dpte = NULL; + if (drive_params.buf_size >= 0x1e + && (drive_params.dpte_seg != 0x0000 || drive_params.dpte_off != 0x0000) + && (drive_params.dpte_seg != 0xffff || drive_params.dpte_off != 0xffff)) { + dpte = (void *)rm_desegment(drive_params.dpte_seg, drive_params.dpte_off); + if ((dpte->control_port & 0xff00) != 0xa000) { + // Check for removable (5) or ATAPI (6) + is_removable = is_removable || ((dpte->flags & (1 << 5)) || (dpte->flags & (1 << 6))); + } + } + + struct volume *block = ext_mem_alloc(sizeof(struct volume)); + + block->drive = drive; + block->partition = 0; + block->first_sect = 0; + block->max_partition = -1; + + if (!detect_sector_size(block)) { + pmm_free(block, sizeof(struct volume)); + continue; + } + + // Normalize sect_count to 512-byte sectors for consistency with partitions + // Preserve (uint64_t)-1 sentinel value (means "unknown size") + if (drive_params.lba_count == (uint64_t)-1 || drive_params.lba_count == 0) { + block->sect_count = (uint64_t)-1; + } else { + block->sect_count = drive_params.lba_count * (block->sector_size / 512); + } + + // Detect optical drives via DPTE ATAPI bit (bit 6) or sector size heuristic + bool is_atapi = (dpte != NULL && (dpte->flags & (1 << 6))); + block->is_optical = is_atapi || (block->sector_size == 2048 && is_removable); + + if (!is_removable && !block->is_optical) { + if (consumed_bda_disks == bda_disk_count) { + pmm_free(block, sizeof(struct volume)); + continue; + } + consumed_bda_disks++; + } + + if (block->is_optical) { + block->index = optical_indices++; + } else { + block->index = hdd_indices++; + } + + block->fastest_xfer_size = fastest_xfer_size(block); + + if (gpt_get_guid(&block->guid, block)) { + block->guid_valid = true; + } + + volume_index = pmm_realloc( + volume_index, + volume_index_i * sizeof(void *), + (volume_index_i + 1) * sizeof(void *) + ); + volume_index[volume_index_i++] = block; + + for (int part = 0; ; part++) { + struct volume *p = ext_mem_alloc(sizeof(struct volume)); + int ret = part_get(p, block, part); + + if (ret == END_OF_TABLE || ret == INVALID_TABLE) { + pmm_free(p, sizeof(struct volume)); + break; + } + if (ret == NO_PARTITION) { + pmm_free(p, sizeof(struct volume)); + continue; + } + + volume_index = pmm_realloc( + volume_index, + volume_index_i * sizeof(void *), + (volume_index_i + 1) * sizeof(void *) + ); + volume_index[volume_index_i++] = p; + + block->max_partition++; + } + } +} + +#endif + +#if defined (UEFI) + +int disk_read_sectors(struct volume *volume, void *buf, uint64_t block, size_t count) { + EFI_STATUS status; + + status = volume->block_io->ReadBlocks(volume->block_io, + volume->block_io->Media->MediaId, + block, count * volume->sector_size, buf); + + switch (status) { + case EFI_SUCCESS: return DISK_SUCCESS; + case EFI_NO_MEDIA: return DISK_NO_MEDIA; + default: return DISK_FAILURE; + } +} + +static struct volume *pxe_from_efi_handle(EFI_HANDLE efi_handle) { + static struct volume *vol = NULL; + + // There's only one PXE volume + if (vol) { + return vol; + } + + EFI_STATUS status; + + EFI_GUID pxe_base_code_guid = EFI_PXE_BASE_CODE_PROTOCOL_GUID; + EFI_PXE_BASE_CODE *pxe_base_code = NULL; + + status = gBS->HandleProtocol(efi_handle, &pxe_base_code_guid, (void **)&pxe_base_code); + if (status) { + return NULL; + } + + if (!pxe_base_code->Mode->DhcpDiscoverValid) { + print("PXE somehow didn't use DHCP?\n"); + return NULL; + } + + if (pxe_base_code->Mode->UsingIpv6) { + print("Sorry, unsupported: PXE IPv6\n"); + return NULL; + } + + vol = pxe_bind_volume(efi_handle, pxe_base_code); + return vol; +} + +#define UNIQUE_SECTOR_POOL_SIZE 65536 +static uint8_t *unique_sector_pool; +static bool unique_sectors_calculated = false; + +static void find_unique_sectors(void); + +static struct volume *volume_by_unique_sector(void *b2b) { + for (size_t i = 0; i < volume_index_i; i++) { + if (volume_index[i]->unique_sector_valid == false) { + continue; + } + + if (memcmp(volume_index[i]->unique_sector_b2b, b2b, BLAKE2B_OUT_BYTES) == 0) { + return volume_index[i]; + } + } + + return NULL; +} + +// Search for matching hash including invalidated volumes (for collision detection) +static struct volume *volume_by_sector_hash(void *b2b) { + for (size_t i = 0; i < volume_index_i; i++) { + if (volume_index[i]->unique_sector_valid == false + && memcmp(volume_index[i]->unique_sector_b2b, (uint8_t[BLAKE2B_OUT_BYTES]){0}, BLAKE2B_OUT_BYTES) == 0) { + // Hash was never set, skip + continue; + } + + if (memcmp(volume_index[i]->unique_sector_b2b, b2b, BLAKE2B_OUT_BYTES) == 0) { + return volume_index[i]; + } + } + + return NULL; +} + +static bool is_efi_handle_to_skip(EFI_HANDLE efi_handle) { + EFI_STATUS status; + + EFI_GUID dp_guid = EFI_DEVICE_PATH_PROTOCOL_GUID; + EFI_DEVICE_PATH_PROTOCOL *dp = NULL; + + EFI_GUID guids_to_skip[] = { + // skip 7CCE9C94-983F-4D0A-8143-B6C05545B223 since it is apparently used by exposed + // ROM devices that we do not want to touch + // (see https://github.com/limine-bootloader/limine/issues/521#issuecomment-3160168795) + {0x7CCE9C94, 0x983F, 0x4D0A, {0x81, 0x43, 0xB6, 0xC0, 0x55, 0x45, 0xB2, 0x23}}, + }; + + status = gBS->HandleProtocol(efi_handle, &dp_guid, (void **)&dp); + if (status) { + return false; + } + + for (;;) { + if (dp->Type == END_DEVICE_PATH_TYPE && dp->SubType == END_ENTIRE_DEVICE_PATH_SUBTYPE) { + break; + } + + uint16_t len = *(uint16_t *)dp->Length; + + // Validate minimum device path node size before accessing type-specific data + if (len < sizeof(EFI_DEVICE_PATH_PROTOCOL)) { + break; // Malformed device path node + } + + if (dp->Type == HARDWARE_DEVICE_PATH && dp->SubType == HW_VENDOR_DP) { + // Vendor device path must be large enough to contain a GUID + if (len >= sizeof(EFI_DEVICE_PATH_PROTOCOL) + sizeof(EFI_GUID)) { + EFI_GUID *vendor_guid = (void *)dp + sizeof(EFI_DEVICE_PATH_PROTOCOL); + + for (size_t i = 0; i < SIZEOF_ARRAY(guids_to_skip); i++) { + if (memcmp(vendor_guid, &guids_to_skip[i], sizeof(EFI_GUID)) == 0) { + return true; + } + } + } + } + dp = (void *)dp + len; + } + + return false; +} + +static bool is_efi_handle_optical(EFI_HANDLE efi_handle) { + EFI_STATUS status; + + EFI_GUID dp_guid = EFI_DEVICE_PATH_PROTOCOL_GUID; + EFI_DEVICE_PATH_PROTOCOL *dp = NULL; + + status = gBS->HandleProtocol(efi_handle, &dp_guid, (void **)&dp); + if (status) { + return false; + } + + for (;;) { + if (dp->Type == END_DEVICE_PATH_TYPE && dp->SubType == END_ENTIRE_DEVICE_PATH_SUBTYPE) { + break; + } + + if (dp->Type == MEDIA_DEVICE_PATH && dp->SubType == MEDIA_CDROM_DP) { + return true; + } + + uint16_t len = *(uint16_t *)dp->Length; + if (len < sizeof(EFI_DEVICE_PATH_PROTOCOL)) { + break; // Malformed device path node + } + dp = (void *)dp + len; + } + + return false; +} + +static EFI_DEVICE_PATH_PROTOCOL *get_device_path(EFI_HANDLE efi_handle) { + EFI_STATUS status; + EFI_GUID dp_guid = EFI_DEVICE_PATH_PROTOCOL_GUID; + EFI_DEVICE_PATH_PROTOCOL *dp = NULL; + + status = gBS->HandleProtocol(efi_handle, &dp_guid, (void **)&dp); + if (status) { + return NULL; + } + return dp; +} + +// Compare device paths up to (but not including) partition nodes +static bool device_paths_match_disk(EFI_DEVICE_PATH_PROTOCOL *dp1, + EFI_DEVICE_PATH_PROTOCOL *dp2) { + if (dp1 == NULL || dp2 == NULL) { + return false; + } + + while (!IsDevicePathEnd(dp1) && !IsDevicePathEnd(dp2)) { + // Stop at partition nodes + if (dp1->Type == MEDIA_DEVICE_PATH && + (dp1->SubType == MEDIA_HARDDRIVE_DP || dp1->SubType == MEDIA_CDROM_DP)) { + break; + } + if (dp2->Type == MEDIA_DEVICE_PATH && + (dp2->SubType == MEDIA_HARDDRIVE_DP || dp2->SubType == MEDIA_CDROM_DP)) { + break; + } + + uint16_t len1 = DevicePathNodeLength(dp1); + uint16_t len2 = DevicePathNodeLength(dp2); + + if (len1 != len2) { + return false; + } + + if (len1 < sizeof(EFI_DEVICE_PATH_PROTOCOL)) { + return false; + } + + if (memcmp(dp1, dp2, len1) != 0) { + return false; + } + + dp1 = (void *)dp1 + len1; + dp2 = (void *)dp2 + len2; + } + + return true; +} + +static struct volume *volume_by_device_path(EFI_HANDLE query_handle) { + EFI_DEVICE_PATH_PROTOCOL *query_dp = get_device_path(query_handle); + if (query_dp == NULL) { + return NULL; + } + + for (size_t i = 0; i < volume_index_i; i++) { + EFI_DEVICE_PATH_PROTOCOL *vol_dp = get_device_path(volume_index[i]->efi_handle); + if (vol_dp == NULL) { + continue; + } + + if (device_paths_match_disk(query_dp, vol_dp)) { + // Convert first_sect from 512-byte sectors to device LBAs + int sector_size = volume_index[i]->sector_size; + if ((volume_index[i]->first_sect * 512) % sector_size) { + continue; // Misaligned, skip this volume + } + uint64_t first_sect_lba = (volume_index[i]->first_sect * 512) / sector_size; + + EFI_DEVICE_PATH_PROTOCOL *qp = query_dp; + while (!IsDevicePathEnd(qp)) { + if (qp->Type == MEDIA_DEVICE_PATH && qp->SubType == MEDIA_HARDDRIVE_DP) { + uint16_t len = DevicePathNodeLength(qp); + // UEFI spec size is 42 bytes, but sizeof() may be larger due to padding + if (len < 42) { + break; + } + HARDDRIVE_DEVICE_PATH *query_hd = (HARDDRIVE_DEVICE_PATH *)qp; + if (first_sect_lba == query_hd->PartitionStart) { + return volume_index[i]; + } + break; + } + if (qp->Type == MEDIA_DEVICE_PATH && qp->SubType == MEDIA_CDROM_DP) { + uint16_t len = DevicePathNodeLength(qp); + if (len < sizeof(CDROM_DEVICE_PATH)) { + break; + } + CDROM_DEVICE_PATH *query_cd = (CDROM_DEVICE_PATH *)qp; + if (first_sect_lba == query_cd->PartitionStart) { + return volume_index[i]; + } + break; + } + uint16_t len = DevicePathNodeLength(qp); + if (len < sizeof(EFI_DEVICE_PATH_PROTOCOL)) { + break; + } + qp = (void *)qp + len; + } + + if (IsDevicePathEnd(qp) && volume_index[i]->partition == 0) { + return volume_index[i]; + } + } + } + + return NULL; +} + +struct volume *disk_volume_from_efi_handle(EFI_HANDLE efi_handle) { + EFI_STATUS status; + + EFI_GUID block_io_guid = BLOCK_IO_PROTOCOL; + EFI_BLOCK_IO *block_io = NULL; + + if (is_efi_handle_to_skip(efi_handle)) { + return NULL; + } + + status = gBS->HandleProtocol(efi_handle, &block_io_guid, (void **)&block_io); + if (status) { + return pxe_from_efi_handle(efi_handle); + } + + // Try device path matching first (primary method) + struct volume *ret = volume_by_device_path(efi_handle); + if (ret != NULL) { + return ret; + } + + // Fallback to unique sector matching + uint64_t bdev_size = ((uint64_t)block_io->Media->LastBlock + 1) * (uint64_t)block_io->Media->BlockSize; + if (bdev_size >= UNIQUE_SECTOR_POOL_SIZE) { + status = block_io->ReadBlocks(block_io, block_io->Media->MediaId, + 0, + UNIQUE_SECTOR_POOL_SIZE, + unique_sector_pool); + if (status == 0) { + find_unique_sectors(); + + uint8_t b2b[BLAKE2B_OUT_BYTES]; + blake2b(b2b, unique_sector_pool, UNIQUE_SECTOR_POOL_SIZE); + + ret = volume_by_unique_sector(b2b); + if (ret != NULL) { + // Verify size, block size, and partition status match + if (block_io->Media->BlockSize == (uint32_t)ret->sector_size + && bdev_size == ret->sect_count * 512 + && block_io->Media->LogicalPartition == (ret->partition != 0)) { + return ret; + } + } + } + } + + return NULL; +} + +static void find_unique_sectors(void) { + if (unique_sectors_calculated) { + return; + } + unique_sectors_calculated = true; + + EFI_STATUS status; + + for (size_t i = 0; i < volume_index_i; i++) { + if ((volume_index[i]->first_sect * 512) % volume_index[i]->sector_size) { + continue; + } + + size_t first_sect = (volume_index[i]->first_sect * 512) / volume_index[i]->sector_size; + + // sect_count is always in 512-byte sectors + if (volume_index[i]->sect_count * 512 < UNIQUE_SECTOR_POOL_SIZE) { + continue; + } + + status = volume_index[i]->block_io->ReadBlocks( + volume_index[i]->block_io, + volume_index[i]->block_io->Media->MediaId, + first_sect, + UNIQUE_SECTOR_POOL_SIZE, + unique_sector_pool); + if (status != 0) { + continue; + } + + uint8_t b2b[BLAKE2B_OUT_BYTES]; + blake2b(b2b, unique_sector_pool, UNIQUE_SECTOR_POOL_SIZE); + + // Check for collision BEFORE storing hash (so we don't find ourselves) + // This searches all volumes including previously invalidated ones + struct volume *collision = volume_by_sector_hash(b2b); + + // Always store the hash so future volumes can detect collisions + memcpy(volume_index[i]->unique_sector_b2b, b2b, BLAKE2B_OUT_BYTES); + + if (collision == NULL) { + volume_index[i]->unique_sector_valid = true; + continue; + } + + // Collision found - invalidate both volumes + collision->unique_sector_valid = false; + volume_index[i]->unique_sector_valid = false; + } +} + +static void find_part_handles(EFI_HANDLE *handles, size_t handle_count) { + for (size_t i = 0; i < handle_count; i++) { + struct volume *vol = disk_volume_from_efi_handle(handles[i]); + if (vol == NULL) { + continue; + } + vol->efi_part_handle = handles[i]; + } +} + +void disk_create_index(void) { + EFI_STATUS status; + + unique_sector_pool = ext_mem_alloc(UNIQUE_SECTOR_POOL_SIZE); + + EFI_HANDLE tmp_handles[1]; + + EFI_GUID block_io_guid = BLOCK_IO_PROTOCOL; + EFI_HANDLE *handles = tmp_handles; + UINTN handles_size = sizeof(tmp_handles); + + status = gBS->LocateHandle(ByProtocol, &block_io_guid, NULL, &handles_size, handles); + + // we only care about the first handle, so ignore if we get EFI_BUFFER_TOO_SMALL + if (status != EFI_BUFFER_TOO_SMALL && status != EFI_SUCCESS) { + EFI_GUID pxe_guid = EFI_PXE_BASE_CODE_PROTOCOL_GUID; + status = gBS->LocateHandle(ByProtocol, &pxe_guid, NULL, &handles_size, handles); + // likewise, all that matters is that the protocol is present + if (status == EFI_BUFFER_TOO_SMALL || status == EFI_SUCCESS) { + return; + } + + goto fail; + } + + handles = ext_mem_alloc(handles_size); + + status = gBS->LocateHandle(ByProtocol, &block_io_guid, NULL, &handles_size, handles); + + if (status != EFI_SUCCESS) { +fail: + panic(false, "LocateHandle for BLOCK_IO_PROTOCOL failed. Machine not supported by Limine UEFI."); + } + + int optical_indices = 1, hdd_indices = 1; + + size_t handle_count = handles_size / sizeof(EFI_HANDLE); + + for (size_t i = 0; i < handle_count; i++) { + EFI_BLOCK_IO *drive = NULL; + + if (is_efi_handle_to_skip(handles[i])) { + continue; + } + + status = gBS->HandleProtocol(handles[i], &block_io_guid, (void **)&drive); + + if (status != 0 || drive == NULL || drive->Media->LastBlock == 0) + continue; + + if (drive->Media->LogicalPartition) + continue; + + // Read test to ensure device is responsive (skipping this causes hangs on some systems) + status = drive->ReadBlocks(drive, drive->Media->MediaId, 0, 4096, unique_sector_pool); + if (status) { + continue; + } + + if (drive->Media->BlockSize == 0) { + continue; + } + if (drive->Media->LastBlock == UINT64_MAX) { + continue; + } + + struct volume *block = ext_mem_alloc(sizeof(struct volume)); + + bool is_optical = is_efi_handle_optical(handles[i]) || + (drive->Media->ReadOnly && drive->Media->BlockSize == 2048); + + if (is_optical) { + block->index = optical_indices++; + block->is_optical = true; + } else { + block->index = hdd_indices++; + } + + block->efi_handle = handles[i]; + block->block_io = drive; + block->partition = 0; + block->sector_size = drive->Media->BlockSize; + block->first_sect = 0; + // Normalize sect_count to 512-byte sectors for consistency with partitions + block->sect_count = (drive->Media->LastBlock + 1) * (drive->Media->BlockSize / 512); + block->max_partition = -1; + + if (drive->Revision >= EFI_BLOCK_IO_PROTOCOL_REVISION3) { + block->fastest_xfer_size = drive->Media->OptimalTransferLengthGranularity; + } + + if (block->fastest_xfer_size == 0) { + block->fastest_xfer_size = DEFAULT_FASTEST_XFER_SIZE; + } else if (block->fastest_xfer_size >= MAX_FASTEST_XFER_SIZE) { + block->fastest_xfer_size = MAX_FASTEST_XFER_SIZE; + } + + if (gpt_get_guid(&block->guid, block)) { + block->guid_valid = true; + } + + volume_index = pmm_realloc( + volume_index, + volume_index_i * sizeof(void *), + (volume_index_i + 1) * sizeof(void *) + ); + volume_index[volume_index_i++] = block; + + for (int part = 0; ; part++) { + struct volume _p = {0}; + + int ret = part_get(&_p, block, part); + + if (ret == END_OF_TABLE || ret == INVALID_TABLE) + break; + if (ret == NO_PARTITION) + continue; + + struct volume *p = ext_mem_alloc(sizeof(struct volume)); + memcpy(p, &_p, sizeof(struct volume)); + + volume_index = pmm_realloc( + volume_index, + volume_index_i * sizeof(void *), + (volume_index_i + 1) * sizeof(void *) + ); + volume_index[volume_index_i++] = p; + + block->max_partition++; + } + } + + find_part_handles(handles, handle_count); + + pmm_free(handles, handles_size); +} + +#endif diff --git a/limine/common/drivers/edid.c b/limine/common/drivers/edid.c new file mode 100644 index 0000000..d14dd05 --- /dev/null +++ b/limine/common/drivers/edid.c @@ -0,0 +1,85 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined (BIOS) + +#include + +struct edid_info_struct *get_edid_info(void) { + static struct edid_info_struct *buf = NULL; + + if (buf == NULL) + buf = conv_mem_alloc(sizeof(struct edid_info_struct)); + + struct rm_regs r = {0}; + + r.eax = 0x4f15; + r.ebx = 0x0001; + r.edi = (uint32_t)rm_off(buf); + r.ds = (uint32_t)rm_seg(buf); + r.es = r.ds; + rm_int(0x10, &r, &r); + + if ((r.eax & 0x00ff) != 0x4f) + goto fail; + if ((r.eax & 0xff00) != 0) + goto fail; + + for (size_t i = 0; i < sizeof(struct edid_info_struct); i++) + if (((uint8_t *)buf)[i] != 0) + goto success; + +fail: + printv("edid: Could not fetch EDID data.\n"); + return NULL; + +success: + printv("edid: Success.\n"); + return buf; +} + +#endif + +#if defined (UEFI) + +#include + +struct edid_info_struct *get_edid_info(EFI_HANDLE gop_handle) { + struct edid_info_struct *buf = ext_mem_alloc(sizeof(struct edid_info_struct)); + + EFI_STATUS status; + + EFI_EDID_ACTIVE_PROTOCOL *edid = NULL; + EFI_GUID edid_guid = EFI_EDID_ACTIVE_PROTOCOL_GUID; + + status = gBS->HandleProtocol(gop_handle, &edid_guid, (void **)&edid); + + if (status) + goto fail; + + if (edid->SizeOfEdid < sizeof(struct edid_info_struct)) + goto fail; + + memcpy(buf, edid->Edid, sizeof(struct edid_info_struct)); + + for (size_t i = 0; i < sizeof(struct edid_info_struct); i++) + if (((uint8_t *)buf)[i] != 0) + goto success; + +fail: + pmm_free(buf, sizeof(struct edid_info_struct)); + printv("edid: Could not fetch EDID data.\n"); + return NULL; + +success: + printv("edid: Success.\n"); + return buf; +} + +#endif diff --git a/limine/common/drivers/edid.h b/limine/common/drivers/edid.h new file mode 100644 index 0000000..81599ea --- /dev/null +++ b/limine/common/drivers/edid.h @@ -0,0 +1,43 @@ +#ifndef DRIVERS__EDID_H__ +#define DRIVERS__EDID_H__ + +#include + +struct edid_info_struct { + uint8_t padding[8]; + uint16_t manufacturer_id_be; + uint16_t edid_id_code; + uint32_t serial_num; + uint8_t man_week; + uint8_t man_year; + uint8_t edid_version; + uint8_t edid_revision; + uint8_t video_input_type; + uint8_t max_hor_size; + uint8_t max_ver_size; + uint8_t gamma_factor; + uint8_t dpms_flags; + uint8_t chroma_info[10]; + uint8_t est_timings1; + uint8_t est_timings2; + uint8_t man_res_timing; + uint16_t std_timing_id[8]; + uint8_t det_timing_desc1[18]; + uint8_t det_timing_desc2[18]; + uint8_t det_timing_desc3[18]; + uint8_t det_timing_desc4[18]; + uint8_t unused; + uint8_t checksum; +} __attribute__((packed)); + +#if defined (UEFI) +#include + +struct edid_info_struct *get_edid_info(EFI_HANDLE gop_handle); +#endif + +#if defined (BIOS) +struct edid_info_struct *get_edid_info(void); +#endif + +#endif diff --git a/limine/common/drivers/gop.c b/limine/common/drivers/gop.c new file mode 100644 index 0000000..59fa374 --- /dev/null +++ b/limine/common/drivers/gop.c @@ -0,0 +1,380 @@ +#if defined (UEFI) + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static uint16_t linear_masks_to_bpp(uint32_t red_mask, uint32_t green_mask, + uint32_t blue_mask, uint32_t alpha_mask) { + uint32_t compound_mask = red_mask | green_mask | blue_mask | alpha_mask; + uint16_t ret = 32; + while ((compound_mask & (1 << 31)) == 0) { + ret--; + compound_mask <<= 1; + } + return ret; +} + +static void linear_mask_to_mask_shift( + uint8_t *mask, uint8_t *shift, uint32_t linear_mask) { + *shift = 0; + *mask = 0; + if (linear_mask == 0) { + return; + } + while ((linear_mask & 1) == 0) { + (*shift)++; + linear_mask >>= 1; + } + while ((linear_mask & 1) == 1) { + (*mask)++; + linear_mask >>= 1; + } +} + +static bool validate_pitch(struct fb_info *ret, size_t mode) { + uint64_t bytes_per_pixel = ret->framebuffer_bpp / 8; + if (bytes_per_pixel == 0 + || ret->framebuffer_pitch % bytes_per_pixel != 0 + || ret->framebuffer_pitch < ret->framebuffer_width * bytes_per_pixel) { + printv("gop: Mode %u has invalid pitch %u (width=%u, bpp=%u), skipping.\n", + (uint32_t)mode, (uint32_t)ret->framebuffer_pitch, + (uint32_t)ret->framebuffer_width, (uint32_t)ret->framebuffer_bpp); + return false; + } + return true; +} + +// Most of this code taken from https://wiki.osdev.org/GOP + +static bool mode_to_fb_info(struct fb_info *ret, EFI_GRAPHICS_OUTPUT_PROTOCOL *gop, size_t mode) { + EFI_STATUS status; + + EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *mode_info; + UINTN mode_info_size; + + status = gop->QueryMode(gop, mode, &mode_info_size, &mode_info); + + if (status) { + return false; + } + + switch (mode_info->PixelFormat) { + case PixelBlueGreenRedReserved8BitPerColor: + ret->framebuffer_bpp = 32; + ret->red_mask_size = 8; + ret->red_mask_shift = 16; + ret->green_mask_size = 8; + ret->green_mask_shift = 8; + ret->blue_mask_size = 8; + ret->blue_mask_shift = 0; + break; + case PixelRedGreenBlueReserved8BitPerColor: + ret->framebuffer_bpp = 32; + ret->red_mask_size = 8; + ret->red_mask_shift = 0; + ret->green_mask_size = 8; + ret->green_mask_shift = 8; + ret->blue_mask_size = 8; + ret->blue_mask_shift = 16; + break; + case PixelBitMask: + if ((mode_info->PixelInformation.RedMask + | mode_info->PixelInformation.GreenMask + | mode_info->PixelInformation.BlueMask + | mode_info->PixelInformation.ReservedMask) == 0) { + return false; + } + ret->framebuffer_bpp = linear_masks_to_bpp( + mode_info->PixelInformation.RedMask, + mode_info->PixelInformation.GreenMask, + mode_info->PixelInformation.BlueMask, + mode_info->PixelInformation.ReservedMask); + linear_mask_to_mask_shift(&ret->red_mask_size, + &ret->red_mask_shift, + mode_info->PixelInformation.RedMask); + linear_mask_to_mask_shift(&ret->green_mask_size, + &ret->green_mask_shift, + mode_info->PixelInformation.GreenMask); + linear_mask_to_mask_shift(&ret->blue_mask_size, + &ret->blue_mask_shift, + mode_info->PixelInformation.BlueMask); + break; + default: + return false; + } + + ret->memory_model = 0x06; + ret->framebuffer_pitch = mode_info->PixelsPerScanLine * (ret->framebuffer_bpp / 8); + ret->framebuffer_width = mode_info->HorizontalResolution; + ret->framebuffer_height = mode_info->VerticalResolution; + + if (!validate_pitch(ret, mode)) { + return false; + } + + return true; +} + +bool gop_force_16 = false; + +static bool try_mode(struct fb_info *ret, EFI_GRAPHICS_OUTPUT_PROTOCOL *gop, + size_t mode, uint64_t width, uint64_t height, int bpp, + struct fb_info *fbs, size_t fbs_count, + bool *setmode_called) { + EFI_STATUS status; + + if (!mode_to_fb_info(ret, gop, mode)) { + return false; + } + + if (width != 0 && height != 0 && bpp != 0) { + if (ret->framebuffer_width != width + || ret->framebuffer_height != height + || ret->framebuffer_bpp != bpp) { + return false; + } + } + + if (gop_force_16) { + if (ret->framebuffer_width >= 65536 + || ret->framebuffer_height >= 65536 + || ret->framebuffer_pitch >= 65536) { + return false; + } + } + + for (size_t i = 0; i < fbs_count; i++) { + if (gop->Mode->FrameBufferBase == fbs[i].framebuffer_addr) { + return false; + } + } + + printv("gop: Found matching mode %X, attempting to set...\n", (uint64_t)mode); + + if (mode == gop->Mode->Mode && *setmode_called) { + printv("gop: Mode was already set, perfect!\n"); + } else { + status = gop->SetMode(gop, mode); + + if (status) { + printv("gop: Failed to set video mode %X, moving on...\n", (uint64_t)mode); + return false; + } + + *setmode_called = true; + } + + // Recalculate pitch from gop->Mode->Info, as some firmware (e.g. Apple + // Macs) report incorrect PixelsPerScanLine via QueryMode. + ret->framebuffer_pitch = gop->Mode->Info->PixelsPerScanLine * (ret->framebuffer_bpp / 8); + + if (!validate_pitch(ret, mode)) { + return false; + } + + ret->framebuffer_addr = gop->Mode->FrameBufferBase; + + fb_clear(ret); + + return true; +} + +static struct fb_info *get_mode_list(size_t *count, EFI_GRAPHICS_OUTPUT_PROTOCOL *gop) { + UINTN modes_count = gop->Mode->MaxMode; + + struct fb_info *ret = ext_mem_alloc(modes_count * sizeof(struct fb_info)); + + size_t actual_count = 0; + for (size_t i = 0; i < modes_count; i++) { + if (mode_to_fb_info(&ret[actual_count], gop, i)) { + actual_count++; + } + } + + struct fb_info *tmp = ext_mem_alloc(actual_count * sizeof(struct fb_info)); + memcpy(tmp, ret, actual_count * sizeof(struct fb_info)); + + pmm_free(ret, modes_count * sizeof(struct fb_info)); + ret = tmp; + + *count = actual_count; + return ret; +} + +#define MAX_PRESET_MODES 128 +no_unwind static int preset_modes[MAX_PRESET_MODES]; +no_unwind static bool setmode_called[MAX_PRESET_MODES]; +no_unwind static bool preset_modes_initialised = false; + +void init_gop(struct fb_info **ret, size_t *_fbs_count, + uint64_t target_width, uint64_t target_height, uint16_t target_bpp) { + if (preset_modes_initialised == false) { + for (size_t i = 0; i < MAX_PRESET_MODES; i++) { + preset_modes[i] = -1; + setmode_called[i] = false; + } + preset_modes_initialised = true; + } + + EFI_STATUS status; + + EFI_HANDLE tmp_handles[1]; + + EFI_HANDLE *handles = tmp_handles; + UINTN handles_size = sizeof(EFI_HANDLE); + EFI_GUID gop_guid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID; + + status = gBS->LocateHandle(ByProtocol, &gop_guid, NULL, &handles_size, handles); + + if (status != EFI_SUCCESS && status != EFI_BUFFER_TOO_SMALL) { + *_fbs_count = 0; + return; + } + + handles = ext_mem_alloc(handles_size); + + status = gBS->LocateHandle(ByProtocol, &gop_guid, NULL, &handles_size, handles); + if (status != EFI_SUCCESS) { + pmm_free(handles, handles_size); + *_fbs_count = 0; + return; + } + + size_t handles_count = handles_size / sizeof(EFI_HANDLE); + + *ret = ext_mem_alloc(handles_count * sizeof(struct fb_info)); + + const struct resolution fallback_resolutions[] = { + { 0, 0, 0 }, // Overridden by EDID + { 0, 0, 0 }, // Overridden by preset + { 1024, 768, 32 }, + { 800, 600, 32 }, + { 640, 480, 32 }, + { 1024, 768, 24 }, + { 800, 600, 24 }, + { 640, 480, 24 }, + { 1024, 768, 16 }, + { 800, 600, 16 }, + { 640, 480, 16 } + }; + + size_t fbs_count = 0; + for (size_t i = 0; i < handles_count && i < MAX_PRESET_MODES; i++) { + struct fb_info *fb = &(*ret)[fbs_count]; + + uint64_t _target_width = target_width; + uint64_t _target_height = target_height; + uint64_t _target_bpp = target_bpp; + + EFI_GRAPHICS_OUTPUT_PROTOCOL *gop; + + status = gBS->HandleProtocol(handles[i], &gop_guid, (void **)&gop); + if (status != EFI_SUCCESS) { + continue; + } + + EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *mode_info; + UINTN mode_info_size; + + status = gop->QueryMode(gop, gop->Mode == NULL ? 0 : gop->Mode->Mode, + &mode_info_size, &mode_info); + + if (status == EFI_NOT_STARTED) { + status = gop->SetMode(gop, 0); + if (status) { + continue; + } + setmode_called[i] = true; + status = gop->QueryMode(gop, gop->Mode == NULL ? 0 : gop->Mode->Mode, + &mode_info_size, &mode_info); + } + + if (status) { + continue; + } + + if (preset_modes[i] == -1) { + preset_modes[i] = gop->Mode->Mode; + } + + fb->edid = get_edid_info(handles[i]); + + UINTN modes_count = gop->Mode->MaxMode; + + size_t current_fallback = 0; + + if (!_target_width || !_target_height || !_target_bpp) { + goto fallback; + } else { + printv("gop: Requested resolution of %ux%ux%u\n", + _target_width, _target_height, _target_bpp); + } + +retry: + for (size_t j = 0; j < modes_count; j++) { + if (try_mode(fb, gop, j, _target_width, _target_height, _target_bpp, *ret, fbs_count, &setmode_called[i])) { + goto success; + } + } + +fallback: + if (current_fallback == 0) { + current_fallback++; + + if (fb->edid != NULL) { + uint64_t edid_width = (uint64_t)fb->edid->det_timing_desc1[2]; + edid_width += ((uint64_t)fb->edid->det_timing_desc1[4] & 0xf0) << 4; + uint64_t edid_height = (uint64_t)fb->edid->det_timing_desc1[5]; + edid_height += ((uint64_t)fb->edid->det_timing_desc1[7] & 0xf0) << 4; + if (edid_width >= mode_info->HorizontalResolution + && edid_height >= mode_info->VerticalResolution) { + _target_width = edid_width; + _target_height = edid_height; + _target_bpp = 32; + goto retry; + } + } + } + + if (current_fallback == 1) { + current_fallback++; + + if (try_mode(fb, gop, preset_modes[i], 0, 0, 0, *ret, fbs_count, &setmode_called[i])) { + goto success; + } + } + + if (current_fallback < SIZEOF_ARRAY(fallback_resolutions)) { + _target_width = fallback_resolutions[current_fallback].width; + _target_height = fallback_resolutions[current_fallback].height; + _target_bpp = fallback_resolutions[current_fallback].bpp; + + current_fallback++; + goto retry; + } + + continue; + +success:; + size_t mode_count; + fb->mode_list = get_mode_list(&mode_count, gop); + fb->mode_count = mode_count; + + fbs_count++; + } + + pmm_free(handles, handles_size); + + gop_force_16 = false; + + *_fbs_count = fbs_count; +} + +#endif diff --git a/limine/common/drivers/gop.h b/limine/common/drivers/gop.h new file mode 100644 index 0000000..24a463a --- /dev/null +++ b/limine/common/drivers/gop.h @@ -0,0 +1,18 @@ +#ifndef DRIVERS__GOP_H__ +#define DRIVERS__GOP_H__ + +#if defined (UEFI) + +#include +#include +#include +#include + +void init_gop(struct fb_info **ret, size_t *_fbs_count, + uint64_t target_width, uint64_t target_height, uint16_t target_bpp); + +extern bool gop_force_16; + +#endif + +#endif diff --git a/limine/common/drivers/serial.c b/limine/common/drivers/serial.c new file mode 100644 index 0000000..eae75d9 --- /dev/null +++ b/limine/common/drivers/serial.c @@ -0,0 +1,50 @@ +#if defined (BIOS) + +#include +#include +#include +#include +#include + +static bool serial_initialised = false; +uint32_t serial_baudrate; + +static void serial_initialise(void) { + if (serial_initialised || !serial) { + return; + } + + // Init com1 + outb(0x3f8 + 3, 0x00); + outb(0x3f8 + 1, 0x00); + outb(0x3f8 + 3, 0x80); + + uint16_t divisor = (uint16_t)(115200 / serial_baudrate); + outb(0x3f8 + 0, divisor & 0xff); + outb(0x3f8 + 1, (divisor >> 8) & 0xff); + + outb(0x3f8 + 1, 0x00); + outb(0x3f8 + 3, 0x03); + outb(0x3f8 + 2, 0xc7); + outb(0x3f8 + 4, 0x0b); + + serial_initialised = true; +} + +void serial_out(uint8_t b) { + serial_initialise(); + + while ((inb(0x3f8 + 5) & 0x20) == 0); + outb(0x3f8, b); +} + +int serial_in(void) { + serial_initialise(); + + if ((inb(0x3f8 + 5) & 0x01) == 0) { + return -1; + } + return inb(0x3f8); +} + +#endif diff --git a/limine/common/drivers/serial.h b/limine/common/drivers/serial.h new file mode 100644 index 0000000..139e4c2 --- /dev/null +++ b/limine/common/drivers/serial.h @@ -0,0 +1,15 @@ +#ifndef DRIVERS__SERIAL_H__ +#define DRIVERS__SERIAL_H__ + +#if defined (BIOS) + +#include + +extern uint32_t serial_baudrate; + +void serial_out(uint8_t b); +int serial_in(void); + +#endif + +#endif diff --git a/limine/common/drivers/vbe.c b/limine/common/drivers/vbe.c new file mode 100644 index 0000000..735d16b --- /dev/null +++ b/limine/common/drivers/vbe.c @@ -0,0 +1,364 @@ +#if defined (BIOS) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct vbe_info_struct { + char signature[4]; + uint8_t version_min; + uint8_t version_maj; + uint16_t oem_off; + uint16_t oem_seg; + uint32_t capabilities; + uint16_t vid_modes_off; + uint16_t vid_modes_seg; + uint16_t vid_mem_blocks; + uint16_t software_rev; + uint16_t vendor_off; + uint16_t vendor_seg; + uint16_t prod_name_off; + uint16_t prod_name_seg; + uint16_t prod_rev_off; + uint16_t prod_rev_seg; + uint8_t reserved[222]; + uint8_t oem_data[256]; +} __attribute__((packed)); + +struct vbe_mode_info_struct { + uint16_t mode_attributes; + uint8_t wina_attributes; + uint8_t winb_attributes; + uint16_t win_granularity; + uint16_t win_size; + uint16_t wina_segment; + uint16_t winb_segment; + uint32_t win_farptr; + uint16_t bytes_per_scanline; + + uint16_t res_x; + uint16_t res_y; + uint8_t charsize_x; + uint8_t charsize_y; + uint8_t plane_count; + uint8_t bpp; + uint8_t bank_count; + uint8_t memory_model; + uint8_t bank_size; + uint8_t image_count; + uint8_t reserved0; + + uint8_t red_mask_size; + uint8_t red_mask_shift; + uint8_t green_mask_size; + uint8_t green_mask_shift; + uint8_t blue_mask_size; + uint8_t blue_mask_shift; + uint8_t rsvd_mask_size; + uint8_t rsvd_mask_shift; + uint8_t direct_color_info; + + uint32_t framebuffer_addr; + uint8_t reserved1[6]; + + uint16_t lin_bytes_per_scanline; + uint8_t banked_image_count; + uint8_t lin_image_count; + uint8_t lin_red_mask_size; + uint8_t lin_red_mask_shift; + uint8_t lin_green_mask_size; + uint8_t lin_green_mask_shift; + uint8_t lin_blue_mask_size; + uint8_t lin_blue_mask_shift; + uint8_t lin_rsvd_mask_size; + uint8_t lin_rsvd_mask_shift; + uint32_t max_pixel_clock; + + uint8_t reserved2[190]; +} __attribute__((packed)); + +static bool get_vbe_info(struct vbe_info_struct *buf) { + struct rm_regs r = {0}; + + r.eax = 0x4f00; + r.edi = (uint32_t)buf; + rm_int(0x10, &r, &r); + + if ((r.eax & 0xff00) >> 8 != 0 + || (r.eax & 0x00ff) != 0x4f) { + return false; + } + + return true; +} + +static bool get_vbe_mode_info(struct vbe_mode_info_struct *buf, + uint16_t mode) { + struct rm_regs r = {0}; + + r.eax = 0x4f01; + r.ecx = (uint32_t)mode; + r.edi = (uint32_t)buf; + rm_int(0x10, &r, &r); + + if ((r.eax & 0xff00) >> 8 != 0 + || (r.eax & 0x00ff) != 0x4f) { + return false; + } + + return true; +} + +static bool set_vbe_mode(uint16_t mode) { + struct rm_regs r = {0}; + + r.eax = 0x4f02; + r.ebx = (uint32_t)mode | (1 << 14); + rm_int(0x10, &r, &r); + + if ((r.eax & 0xff00) >> 8 != 0 + || (r.eax & 0x00ff) != 0x4f) { + return false; + } + + return true; +} + +// Maximum number of video modes to enumerate to prevent infinite loops +// from corrupted VBE mode lists without a proper 0xffff terminator +#define VBE_MAX_MODES 512 + +struct fb_info *vbe_get_mode_list(size_t *count) { + struct vbe_info_struct vbe_info; + if (!get_vbe_info(&vbe_info)) { + return NULL; + } + + uint16_t *vid_modes = (uint16_t *)rm_desegment(vbe_info.vid_modes_seg, + vbe_info.vid_modes_off); + + size_t modes_count = 0; + for (size_t i = 0; i < VBE_MAX_MODES && vid_modes[i] != 0xffff; i++) { + struct vbe_mode_info_struct vbe_mode_info; + if (!get_vbe_mode_info(&vbe_mode_info, vid_modes[i])) { + continue; + } + + // We only support RGB for now + if (vbe_mode_info.memory_model != 0x06) + continue; + // We only support linear modes + if (!(vbe_mode_info.mode_attributes & (1 << 7))) + continue; + + uint16_t pitch = (vbe_info.version_maj < 3) + ? vbe_mode_info.bytes_per_scanline + : vbe_mode_info.lin_bytes_per_scanline; + uint16_t bytes_per_pixel = vbe_mode_info.bpp / 8; + if (bytes_per_pixel == 0 + || pitch % bytes_per_pixel != 0 + || pitch < (uint32_t)vbe_mode_info.res_x * bytes_per_pixel) + continue; + + modes_count++; + } + + struct fb_info *ret = ext_mem_alloc(modes_count * sizeof(struct fb_info)); + + for (size_t i = 0, j = 0; i < VBE_MAX_MODES && vid_modes[i] != 0xffff; i++) { + struct vbe_mode_info_struct vbe_mode_info; + if (!get_vbe_mode_info(&vbe_mode_info, vid_modes[i])) { + continue; + } + + // We only support RGB for now + if (vbe_mode_info.memory_model != 0x06) + continue; + // We only support linear modes + if (!(vbe_mode_info.mode_attributes & (1 << 7))) + continue; + + uint16_t pitch = (vbe_info.version_maj < 3) + ? vbe_mode_info.bytes_per_scanline + : vbe_mode_info.lin_bytes_per_scanline; + uint16_t bytes_per_pixel = vbe_mode_info.bpp / 8; + if (bytes_per_pixel == 0 + || pitch % bytes_per_pixel != 0 + || pitch < (uint32_t)vbe_mode_info.res_x * bytes_per_pixel) + continue; + + ret[j].memory_model = vbe_mode_info.memory_model; + + ret[j].framebuffer_width = vbe_mode_info.res_x; + ret[j].framebuffer_height = vbe_mode_info.res_y; + ret[j].framebuffer_bpp = vbe_mode_info.bpp; + + if (vbe_info.version_maj < 3) { + ret[j].framebuffer_pitch = vbe_mode_info.bytes_per_scanline; + ret[j].red_mask_size = vbe_mode_info.red_mask_size; + ret[j].red_mask_shift = vbe_mode_info.red_mask_shift; + ret[j].green_mask_size = vbe_mode_info.green_mask_size; + ret[j].green_mask_shift = vbe_mode_info.green_mask_shift; + ret[j].blue_mask_size = vbe_mode_info.blue_mask_size; + ret[j].blue_mask_shift = vbe_mode_info.blue_mask_shift; + } else { + ret[j].framebuffer_pitch = vbe_mode_info.lin_bytes_per_scanline; + ret[j].red_mask_size = vbe_mode_info.lin_red_mask_size; + ret[j].red_mask_shift = vbe_mode_info.lin_red_mask_shift; + ret[j].green_mask_size = vbe_mode_info.lin_green_mask_size; + ret[j].green_mask_shift = vbe_mode_info.lin_green_mask_shift; + ret[j].blue_mask_size = vbe_mode_info.lin_blue_mask_size; + ret[j].blue_mask_shift = vbe_mode_info.lin_blue_mask_shift; + } + + j++; + } + + *count = modes_count; + + return ret; +} + +bool init_vbe(struct fb_info *ret, + uint16_t target_width, uint16_t target_height, uint16_t target_bpp) { + printv("vbe: Initialising...\n"); + + size_t current_fallback = 0; + + struct vbe_info_struct vbe_info; + if (!get_vbe_info(&vbe_info)) { + return false; + } + + printv("vbe: Version: %u.%u\n", vbe_info.version_maj, vbe_info.version_min); + printv("vbe: OEM: %s\n", (char *)rm_desegment(vbe_info.oem_seg, vbe_info.oem_off)); + printv("vbe: Graphics vendor: %s\n", (char *)rm_desegment(vbe_info.vendor_seg, vbe_info.vendor_off)); + printv("vbe: Product name: %s\n", (char *)rm_desegment(vbe_info.prod_name_seg, vbe_info.prod_name_off)); + printv("vbe: Product revision: %s\n", (char *)rm_desegment(vbe_info.prod_rev_seg, vbe_info.prod_rev_off)); + + uint16_t *vid_modes = (uint16_t *)rm_desegment(vbe_info.vid_modes_seg, + vbe_info.vid_modes_off); + + struct resolution fallback_resolutions[] = { + { 1024, 768, 32 }, + { 800, 600, 32 }, + { 640, 480, 32 }, + { 1024, 768, 24 }, + { 800, 600, 24 }, + { 640, 480, 24 }, + { 1024, 768, 16 }, + { 800, 600, 16 }, + { 640, 480, 16 } + }; + + if (!target_width || !target_height || !target_bpp) { + struct edid_info_struct *edid_info = get_edid_info(); + if (edid_info != NULL) { + int edid_width = (int)edid_info->det_timing_desc1[2]; + edid_width += ((int)edid_info->det_timing_desc1[4] & 0xf0) << 4; + int edid_height = (int)edid_info->det_timing_desc1[5]; + edid_height += ((int)edid_info->det_timing_desc1[7] & 0xf0) << 4; + if (edid_width && edid_height) { + target_width = edid_width; + target_height = edid_height; + target_bpp = 32; + printv("vbe: EDID detected screen resolution of %ux%u\n", + target_width, target_height); + goto retry; + } + } + goto fallback; + } else { + printv("vbe: Requested resolution of %ux%ux%u\n", + target_width, target_height, target_bpp); + } + +retry: + for (size_t i = 0; i < VBE_MAX_MODES && vid_modes[i] != 0xffff; i++) { + struct vbe_mode_info_struct vbe_mode_info; + if (!get_vbe_mode_info(&vbe_mode_info, vid_modes[i])) { + continue; + } + if (vbe_mode_info.res_x == target_width + && vbe_mode_info.res_y == target_height + && vbe_mode_info.bpp == target_bpp) { + // We only support RGB for now + if (vbe_mode_info.memory_model != 0x06) + continue; + // We only support linear modes + if (!(vbe_mode_info.mode_attributes & (1 << 7))) + continue; + printv("vbe: Found matching mode %x, attempting to set...\n", vid_modes[i]); + if (vid_modes[i] == current_video_mode) { + printv("vbe: Mode was already set, perfect!\n"); + } else if (!set_vbe_mode(vid_modes[i])) { + current_video_mode = -1; + printv("vbe: Failed to set video mode %x, moving on...\n", vid_modes[i]); + continue; + } + current_video_mode = vid_modes[i]; + + printv("vbe: Framebuffer address: %x\n", vbe_mode_info.framebuffer_addr); + ret->memory_model = vbe_mode_info.memory_model; + ret->framebuffer_addr = vbe_mode_info.framebuffer_addr; + ret->framebuffer_width = vbe_mode_info.res_x; + ret->framebuffer_height = vbe_mode_info.res_y; + ret->framebuffer_bpp = vbe_mode_info.bpp; + if (vbe_info.version_maj < 3) { + ret->framebuffer_pitch = vbe_mode_info.bytes_per_scanline; + ret->red_mask_size = vbe_mode_info.red_mask_size; + ret->red_mask_shift = vbe_mode_info.red_mask_shift; + ret->green_mask_size = vbe_mode_info.green_mask_size; + ret->green_mask_shift = vbe_mode_info.green_mask_shift; + ret->blue_mask_size = vbe_mode_info.blue_mask_size; + ret->blue_mask_shift = vbe_mode_info.blue_mask_shift; + } else { + ret->framebuffer_pitch = vbe_mode_info.lin_bytes_per_scanline; + ret->red_mask_size = vbe_mode_info.lin_red_mask_size; + ret->red_mask_shift = vbe_mode_info.lin_red_mask_shift; + ret->green_mask_size = vbe_mode_info.lin_green_mask_size; + ret->green_mask_shift = vbe_mode_info.lin_green_mask_shift; + ret->blue_mask_size = vbe_mode_info.lin_blue_mask_size; + ret->blue_mask_shift = vbe_mode_info.lin_blue_mask_shift; + } + + uint16_t bytes_per_pixel = ret->framebuffer_bpp / 8; + if (bytes_per_pixel == 0 + || ret->framebuffer_pitch % bytes_per_pixel != 0 + || ret->framebuffer_pitch < (uint32_t)ret->framebuffer_width * bytes_per_pixel) { + printv("vbe: Mode %x has invalid pitch %u (width=%u, bpp=%u), skipping.\n", + vid_modes[i], (uint32_t)ret->framebuffer_pitch, + (uint32_t)ret->framebuffer_width, (uint32_t)ret->framebuffer_bpp); + continue; + } + + fb_clear(ret); + + return true; + } + } + +fallback: + if (current_fallback < SIZEOF_ARRAY(fallback_resolutions)) { + target_width = fallback_resolutions[current_fallback].width; + target_height = fallback_resolutions[current_fallback].height; + target_bpp = fallback_resolutions[current_fallback].bpp; + current_fallback++; + goto retry; + } + + return false; +} + +#endif diff --git a/limine/common/drivers/vbe.h b/limine/common/drivers/vbe.h new file mode 100644 index 0000000..f5da1f8 --- /dev/null +++ b/limine/common/drivers/vbe.h @@ -0,0 +1,14 @@ +#ifndef DRIVERS__VBE_H__ +#define DRIVERS__VBE_H__ + +#include +#include +#include +#include + +bool init_vbe(struct fb_info *ret, + uint16_t target_width, uint16_t target_height, uint16_t target_bpp); + +struct fb_info *vbe_get_mode_list(size_t *count); + +#endif diff --git a/limine/common/drivers/vga_textmode.c b/limine/common/drivers/vga_textmode.c new file mode 100644 index 0000000..fefb982 --- /dev/null +++ b/limine/common/drivers/vga_textmode.c @@ -0,0 +1,369 @@ +#if defined (BIOS) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define VIDEO_BOTTOM ((VD_ROWS * VD_COLS) - 1) + +static void draw_cursor(struct textmode_context *ctx) { + uint8_t pal = ctx->back_buffer[ctx->cursor_offset + 1]; + ctx->video_mem[ctx->cursor_offset + 1] = ((pal & 0xf0) >> 4) | ((pal & 0x0f) << 4); +} + +static void text_save_state(struct flanterm_context *_ctx) { + struct textmode_context *ctx = (void *)_ctx; + ctx->saved_state_text_palette = ctx->text_palette; + ctx->saved_state_cursor_offset = ctx->cursor_offset; +} + +static void text_restore_state(struct flanterm_context *_ctx) { + struct textmode_context *ctx = (void *)_ctx; + ctx->text_palette = ctx->saved_state_text_palette; + ctx->cursor_offset = ctx->saved_state_cursor_offset; +} + +static void text_swap_palette(struct flanterm_context *_ctx) { + struct textmode_context *ctx = (void *)_ctx; + ctx->text_palette = (ctx->text_palette << 4) | (ctx->text_palette >> 4); +} + +static void text_scroll(struct flanterm_context *_ctx) { + struct textmode_context *ctx = (void *)_ctx; + + // move the text up by one row + for (size_t i = _ctx->scroll_top_margin * VD_COLS; + i < (_ctx->scroll_bottom_margin - 1) * VD_COLS; i++) { + ctx->back_buffer[i] = ctx->back_buffer[i + VD_COLS]; + } + // clear the last line of the screen + for (size_t i = (_ctx->scroll_bottom_margin - 1) * VD_COLS; + i < _ctx->scroll_bottom_margin * VD_COLS; i += 2) { + ctx->back_buffer[i] = ' '; + ctx->back_buffer[i + 1] = ctx->text_palette; + } +} + +static void text_revscroll(struct flanterm_context *_ctx) { + struct textmode_context *ctx = (void *)_ctx; + + // move the text up by one row + for (size_t i = (_ctx->scroll_bottom_margin - 1) * VD_COLS - 2; ; i--) { + ctx->back_buffer[i + VD_COLS] = ctx->back_buffer[i]; + if (i == _ctx->scroll_top_margin * VD_COLS) { + break; + } + } + // clear the first line of the screen + for (size_t i = _ctx->scroll_top_margin * VD_COLS; + i < (_ctx->scroll_top_margin + 1) * VD_COLS; i += 2) { + ctx->back_buffer[i] = ' '; + ctx->back_buffer[i + 1] = ctx->text_palette; + } +} + +static void text_clear(struct flanterm_context *_ctx, bool move) { + struct textmode_context *ctx = (void *)_ctx; + + for (size_t i = 0; i < VIDEO_BOTTOM; i += 2) { + ctx->back_buffer[i] = ' '; + ctx->back_buffer[i + 1] = ctx->text_palette; + } + if (move) { + ctx->cursor_offset = 0; + } +} + +static void text_full_refresh(struct flanterm_context *_ctx) { + struct textmode_context *ctx = (void *)_ctx; + + for (size_t i = 0; i < VD_ROWS * VD_COLS; i++) { + ctx->video_mem[i] = ctx->front_buffer[i]; + ctx->back_buffer[i] = ctx->front_buffer[i]; + } + + if (_ctx->cursor_enabled) { + draw_cursor(ctx); + ctx->old_cursor_offset = ctx->cursor_offset; + } +} + +static void text_double_buffer_flush(struct flanterm_context *_ctx) { + struct textmode_context *ctx = (void *)_ctx; + + if (_ctx->cursor_enabled) { + draw_cursor(ctx); + } + + if (ctx->cursor_offset != ctx->old_cursor_offset || _ctx->cursor_enabled == false) { + ctx->video_mem[ctx->old_cursor_offset + 1] = ctx->back_buffer[ctx->old_cursor_offset + 1]; + } + + for (size_t i = 0; i < VD_ROWS * VD_COLS; i++) { + if (ctx->back_buffer[i] == ctx->front_buffer[i]) { + continue; + } + + ctx->front_buffer[i] = ctx->back_buffer[i]; + + if (_ctx->cursor_enabled && i == ctx->cursor_offset + 1) { + continue; + } + + ctx->video_mem[i] = ctx->back_buffer[i]; + } + + if (_ctx->cursor_enabled) { + ctx->old_cursor_offset = ctx->cursor_offset; + } +} + +static void text_get_cursor_pos(struct flanterm_context *_ctx, size_t *x, size_t *y) { + struct textmode_context *ctx = (void *)_ctx; + + *x = (ctx->cursor_offset % VD_COLS) / 2; + *y = ctx->cursor_offset / VD_COLS; +} + +static void text_move_character(struct flanterm_context *_ctx, size_t new_x, size_t new_y, size_t old_x, size_t old_y) { + struct textmode_context *ctx = (void *)_ctx; + + if (old_x >= VD_COLS / 2 || old_y >= VD_ROWS + || new_x >= VD_COLS / 2 || new_y >= VD_ROWS) { + return; + } + + ctx->back_buffer[new_y * VD_COLS + new_x * 2] = ctx->back_buffer[old_y * VD_COLS + old_x * 2]; + ctx->back_buffer[new_y * VD_COLS + new_x * 2 + 1] = ctx->back_buffer[old_y * VD_COLS + old_x * 2 + 1]; +} + +static void text_set_cursor_pos(struct flanterm_context *_ctx, size_t x, size_t y) { + struct textmode_context *ctx = (void *)_ctx; + + if (x >= VD_COLS / 2) { + if ((int)x < 0) { + x = 0; + } else { + x = VD_COLS / 2 - 1; + } + } + if (y >= VD_ROWS) { + if ((int)y < 0) { + y = 0; + } else { + y = VD_ROWS - 1; + } + } + ctx->cursor_offset = y * VD_COLS + x * 2; +} + +static uint8_t ansi_colours[] = { 0, 4, 2, 6, 1, 5, 3, 7 }; + +static void text_set_text_fg(struct flanterm_context *_ctx, size_t fg) { + struct textmode_context *ctx = (void *)_ctx; + ctx->text_palette = (ctx->text_palette & 0xf0) | ansi_colours[fg]; +} + +static void text_set_text_bg(struct flanterm_context *_ctx, size_t bg) { + struct textmode_context *ctx = (void *)_ctx; + ctx->text_palette = (ctx->text_palette & 0x0f) | (ansi_colours[bg] << 4); +} + +static void text_set_text_fg_bright(struct flanterm_context *_ctx, size_t fg) { + struct textmode_context *ctx = (void *)_ctx; + ctx->text_palette = (ctx->text_palette & 0xf0) | (ansi_colours[fg] | (1 << 3)); +} + +static void text_set_text_bg_bright(struct flanterm_context *_ctx, size_t bg) { + struct textmode_context *ctx = (void *)_ctx; + ctx->text_palette = (ctx->text_palette & 0x0f) | ((ansi_colours[bg] | (1 << 3)) << 4); +} + +static void text_set_text_fg_rgb(struct flanterm_context *ctx, uint32_t n) { + (void)ctx; + (void)n; +} + +static void text_set_text_bg_rgb(struct flanterm_context *ctx, uint32_t n) { + (void)ctx; + (void)n; +} + +static void text_set_text_fg_default(struct flanterm_context *_ctx) { + struct textmode_context *ctx = (void *)_ctx; + ctx->text_palette = (ctx->text_palette & 0xf0) | 7; +} + +static void text_set_text_bg_default(struct flanterm_context *_ctx) { + struct textmode_context *ctx = (void *)_ctx; + ctx->text_palette &= 0x0f; +} + +static void text_set_text_fg_default_bright(struct flanterm_context *_ctx) { + struct textmode_context *ctx = (void *)_ctx; + ctx->text_palette = (ctx->text_palette & 0xf0) | (7 | (1 << 3)); +} + +static void text_set_text_bg_default_bright(struct flanterm_context *_ctx) { + struct textmode_context *ctx = (void *)_ctx; + ctx->text_palette = (ctx->text_palette & 0x0f) | ((1 << 3) << 4); +} + +static void text_putchar(struct flanterm_context *_ctx, uint8_t c) { + struct textmode_context *ctx = (void *)_ctx; + + ctx->back_buffer[ctx->cursor_offset] = c; + ctx->back_buffer[ctx->cursor_offset + 1] = ctx->text_palette; + if (ctx->cursor_offset / VD_COLS == _ctx->scroll_bottom_margin - 1 + && ctx->cursor_offset % VD_COLS == VD_COLS - 2) { + if (_ctx->scroll_enabled) { + text_scroll(_ctx); + ctx->cursor_offset -= ctx->cursor_offset % VD_COLS; + } + } else if (ctx->cursor_offset >= (VIDEO_BOTTOM - 1)) { + ctx->cursor_offset -= ctx->cursor_offset % VD_COLS; + } else { + ctx->cursor_offset += 2; + } +} + +static void text_deinit(struct flanterm_context *_ctx, void (*_free)(void *, size_t)) { + struct textmode_context *ctx = (void *)_ctx; + + if (ctx->back_buffer != NULL) { + _free(ctx->back_buffer, VD_ROWS * VD_COLS); + ctx->back_buffer = NULL; + } + + if (ctx->front_buffer != NULL) { + _free(ctx->front_buffer, VD_ROWS * VD_COLS); + ctx->front_buffer = NULL; + } + + pmm_free(ctx, sizeof(struct textmode_context)); +} + +void vga_textmode_init(bool managed) { + term_notready(); + + if (quiet) { + return; + } + + if (current_video_mode != 0x3) { + struct rm_regs r = {0}; + r.eax = 0x0003; + rm_int(0x10, &r, &r); + + current_video_mode = 0x3; + } + + terms = ext_mem_alloc(sizeof(void *)); + terms_i = 1; + + terms[0] = ext_mem_alloc(sizeof(struct textmode_context)); + + struct flanterm_context *term = terms[0]; + struct textmode_context *ctx = (void *)term; + + if (ctx->back_buffer == NULL) { + ctx->back_buffer = ext_mem_alloc(VD_ROWS * VD_COLS); + } else { + memset(ctx->back_buffer, 0, VD_ROWS * VD_COLS); + } + if (ctx->front_buffer == NULL) { + ctx->front_buffer = ext_mem_alloc(VD_ROWS * VD_COLS); + } else { + memset(ctx->front_buffer, 0, VD_ROWS * VD_COLS); + } + + ctx->cursor_offset = 0; + ctx->text_palette = 0x07; + + ctx->video_mem = (volatile uint8_t *)0xb8000; + + text_clear(term, false); + + // VGA cursor code taken from: https://wiki.osdev.org/Text_Mode_Cursor + + if (!managed) { + term->cursor_enabled = false; + + outb(0x3d4, 0x0a); + outb(0x3d5, (inb(0x3d5) & 0xc0) | 14); + outb(0x3d4, 0x0b); + outb(0x3d5, (inb(0x3d5) & 0xe0) | 15); + outb(0x3d4, 0x0f); + outb(0x3d5, 0); + outb(0x3d4, 0x0e); + outb(0x3d5, 0); + + struct rm_regs r = {0}; + r.eax = 0x0200; + rm_int(0x10, &r, &r); + } else { + outb(0x3d4, 0x0a); + outb(0x3d5, 0x20); + } + + text_double_buffer_flush(term); + + if (managed && serial) { + term->cols = 80; + term->rows = 24; + } else { + term->cols = 80; + term->rows = 25; + } + + term->raw_putchar = text_putchar; + term->clear = text_clear; + term->set_cursor_pos = text_set_cursor_pos; + term->get_cursor_pos = text_get_cursor_pos; + term->set_text_fg = text_set_text_fg; + term->set_text_bg = text_set_text_bg; + term->set_text_fg_bright = text_set_text_fg_bright; + term->set_text_bg_bright = text_set_text_bg_bright; + term->set_text_fg_rgb = text_set_text_fg_rgb; + term->set_text_bg_rgb = text_set_text_bg_rgb; + term->set_text_fg_default = text_set_text_fg_default; + term->set_text_bg_default = text_set_text_bg_default; + term->set_text_fg_default_bright = text_set_text_fg_default_bright; + term->set_text_bg_default_bright = text_set_text_bg_default_bright; + term->move_character = text_move_character; + term->scroll = text_scroll; + term->revscroll = text_revscroll; + term->swap_palette = text_swap_palette; + term->save_state = text_save_state; + term->restore_state = text_restore_state; + term->double_buffer_flush = text_double_buffer_flush; + term->full_refresh = text_full_refresh; + term->deinit = text_deinit; + + flanterm_context_reinit(term); + + if (!managed) { + term->cursor_enabled = false; + } + + term->full_refresh(term); + + if (!managed) { + term->deinit(term, pmm_free_size_t); + pmm_free(terms, sizeof(void *)); + terms_i = 0; + terms = NULL; + term_backend = _NOT_READY; + } else { + term_backend = TEXTMODE; + } +} + +#endif diff --git a/limine/common/drivers/vga_textmode.h b/limine/common/drivers/vga_textmode.h new file mode 100644 index 0000000..c7bca53 --- /dev/null +++ b/limine/common/drivers/vga_textmode.h @@ -0,0 +1,35 @@ +#ifndef DRIVERS__VGA_TEXTMODE_H__ +#define DRIVERS__VGA_TEXTMODE_H__ + +#if defined (BIOS) + +#include +#include +#include +#include + +#define VD_COLS (80 * 2) +#define VD_ROWS 25 + +struct textmode_context { + struct flanterm_context term; + + volatile uint8_t *video_mem; + + uint8_t *back_buffer; + uint8_t *front_buffer; + + size_t cursor_offset; + size_t old_cursor_offset; + bool cursor_status; + uint8_t text_palette; + + uint8_t saved_state_text_palette; + size_t saved_state_cursor_offset; +}; + +void vga_textmode_init(bool managed); + +#endif + +#endif diff --git a/limine/common/efi_thunk.asm_uefi_aarch64 b/limine/common/efi_thunk.asm_uefi_aarch64 new file mode 100644 index 0000000..a524cb0 --- /dev/null +++ b/limine/common/efi_thunk.asm_uefi_aarch64 @@ -0,0 +1,12 @@ +.section .text + +.global efi_main +.extern uefi_entry + +efi_main: + mov x30, xzr + mov x29, xzr + + b uefi_entry + +.section .note.GNU-stack,"",%progbits diff --git a/limine/common/efi_thunk.asm_uefi_ia32 b/limine/common/efi_thunk.asm_uefi_ia32 new file mode 100644 index 0000000..3bcaee5 --- /dev/null +++ b/limine/common/efi_thunk.asm_uefi_ia32 @@ -0,0 +1,10 @@ +section .text + +global efi_main +extern uefi_entry +efi_main: + xor eax, eax + mov [esp], eax + jmp uefi_entry + +section .note.GNU-stack noalloc noexec nowrite progbits diff --git a/limine/common/efi_thunk.asm_uefi_loongarch64 b/limine/common/efi_thunk.asm_uefi_loongarch64 new file mode 100644 index 0000000..3184c67 --- /dev/null +++ b/limine/common/efi_thunk.asm_uefi_loongarch64 @@ -0,0 +1,10 @@ +.section .text + +.global efi_main +.extern uefi_entry +efi_main: + move $fp, $r0 + move $ra, $r0 + b uefi_entry + +.section .note.GNU-stack,"",%progbits diff --git a/limine/common/efi_thunk.asm_uefi_riscv64 b/limine/common/efi_thunk.asm_uefi_riscv64 new file mode 100644 index 0000000..966faee --- /dev/null +++ b/limine/common/efi_thunk.asm_uefi_riscv64 @@ -0,0 +1,11 @@ +.section .text + +.global efi_main +.extern uefi_entry +efi_main: +.option norelax + mv fp, zero + mv ra, zero + j uefi_entry + +.section .note.GNU-stack,"",%progbits diff --git a/limine/common/efi_thunk.asm_uefi_x86_64 b/limine/common/efi_thunk.asm_uefi_x86_64 new file mode 100644 index 0000000..dad1647 --- /dev/null +++ b/limine/common/efi_thunk.asm_uefi_x86_64 @@ -0,0 +1,10 @@ +section .text + +global efi_main +extern uefi_entry +efi_main: + xor eax, eax + mov [rsp], rax + jmp uefi_entry + +section .note.GNU-stack noalloc noexec nowrite progbits diff --git a/limine/common/entry.s2.c b/limine/common/entry.s2.c new file mode 100644 index 0000000..261a40b --- /dev/null +++ b/limine/common/entry.s2.c @@ -0,0 +1,134 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct volume *boot_volume; + +#if defined (BIOS) + +bool stage3_loaded = false; +static bool stage3_found = false; + +extern symbol stage3_addr; +extern symbol limine_bios_sys_size; +extern symbol build_id_s2; +extern symbol build_id_s3; + +static bool stage3_init(struct volume *part) { + struct file_handle *stage3; + + bool old_cif = case_insensitive_fopen; + case_insensitive_fopen = true; + if (true + && (stage3 = fopen(part, "/boot/limine/limine-bios.sys")) == NULL + && (stage3 = fopen(part, "/boot/limine-bios.sys")) == NULL + && (stage3 = fopen(part, "/limine/limine-bios.sys")) == NULL + && (stage3 = fopen(part, "/limine-bios.sys")) == NULL + ) { + case_insensitive_fopen = old_cif; + return false; + } + case_insensitive_fopen = old_cif; + + stage3_found = true; + + if (stage3->size != (size_t)limine_bios_sys_size) { + print("limine-bios.sys size incorrect.\n"); + return false; + } + + fread(stage3, stage3_addr, + (uintptr_t)stage3_addr - 0xf000, + stage3->size - ((uintptr_t)stage3_addr - 0xf000)); + + fclose(stage3); + + if (memcmp(build_id_s2 + 16, build_id_s3 + 16, 20) != 0) { + print("limine-bios.sys build ID mismatch.\n"); + return false; + } + + stage3_loaded = true; + + return true; +} + +enum { + BOOTED_FROM_HDD = 0, + BOOTED_FROM_PXE = 1, + BOOTED_FROM_CD = 2 +}; + +noreturn void entry(uint8_t boot_drive, int boot_from) { + // XXX DO NOT MOVE A20 ENABLE CALL + if (!a20_enable()) { + panic(false, "Could not enable A20 line"); + } + + calibrate_tsc(); + uint64_t usec_at_entry = rdtsc_usec(); + + init_e820(); + init_memmap(); + + init_idt(); + + disk_create_index(); + + if (boot_from == BOOTED_FROM_HDD || boot_from == BOOTED_FROM_CD) { + boot_volume = volume_get_by_bios_drive(boot_drive); + } else if (boot_from == BOOTED_FROM_PXE) { + pxe_init(); + boot_volume = pxe_bind_volume(); + } + + if (boot_volume == NULL) { + panic(false, "Could not determine boot drive"); + } + + volume_iterate_parts(boot_volume, + if (stage3_init(_PART)) { + break; + } + ); + + if (!stage3_found) { + print("\n" + "!! Stage 3 file not found!\n" + "!! Have you copied limine-bios.sys to the root, /boot, /limine, or /boot/limine\n" + "!! directories of one of the partitions on the boot device?\n\n"); + } + + if (!stage3_loaded) { + panic(false, "Failed to load stage 3."); + } + + term_fallback(); + + usec_at_bootloader_entry = usec_at_entry; + stage3_common(); +} + +#endif diff --git a/limine/common/entry.s3.c b/limine/common/entry.s3.c new file mode 100644 index 0000000..520232f --- /dev/null +++ b/limine/common/entry.s3.c @@ -0,0 +1,202 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +void stage3_common(void); + +#if defined (UEFI) +extern symbol __slide, __image_base, __image_end; +extern symbol _start; + +noreturn void uefi_entry(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) { + gST = SystemTable; + gBS = SystemTable->BootServices; + gRT = SystemTable->RuntimeServices; + efi_image_handle = ImageHandle; + + calibrate_tsc(); + usec_at_bootloader_entry = rdtsc_usec(); + + EFI_STATUS status; + + const char *deferred_error = NULL; + +#if defined (__x86_64__) + if ((uintptr_t)__slide >= 0x100000000) { + size_t image_size = ALIGN_UP((uintptr_t)__image_end - (uintptr_t)__image_base, 4096); + size_t image_size_pages = ALIGN_UP((size_t)image_size, 4096) / 4096; + size_t new_base; + for (new_base = 0x1000; new_base + (size_t)image_size < 0x100000000; new_base += 0x1000) { + EFI_PHYSICAL_ADDRESS _new_base = (EFI_PHYSICAL_ADDRESS)new_base; + status = gBS->AllocatePages(AllocateAddress, EfiLoaderCode, image_size_pages, &_new_base); + if (status == 0) { + goto new_base_gotten; + } + } + deferred_error = "Limine does not support being loaded above 4GiB and no alternative loading spot found"; + goto defer_error; +new_base_gotten: + memcpy((void *)new_base, __slide, (size_t)image_size); + __attribute__((ms_abi)) + void (*new_entry_point)(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable); + new_entry_point = (void *)(new_base + ((uintptr_t)_start - (uintptr_t)__slide)); + new_entry_point(ImageHandle, SystemTable); + __builtin_unreachable(); + } + +defer_error: +#endif + + gST->ConOut->EnableCursor(gST->ConOut, false); + + init_memmap(); + + term_fallback(); + + status = gBS->SetWatchdogTimer(0, 0x10000, 0, NULL); + if (status) { + print("WARNING: Failed to disable watchdog timer!\n"); + } + + if (deferred_error != NULL) { + panic(false, "%s", deferred_error); + } + +#if defined (__x86_64__) || defined (__i386__) + init_gdt(); +#endif + + disk_create_index(); + + boot_volume = NULL; + + EFI_HANDLE current_handle = ImageHandle; + for (size_t j = 0; j < 25; j++) { + if (current_handle == NULL) { +could_not_match: + print("WARNING: Could not meaningfully match the boot device handle with a volume.\n"); + print(" Using the first volume containing a Limine configuration!\n"); + print("\n"); + print("THIS IS A BUG! Please report this issue upstream.\n"); + print("Press any key to continue...\n"); + for (;;) { + int ret = pit_sleep_and_quit_on_keypress(65535); + if (ret != 0) { + break; + } + } + + for (size_t i = 0; i < volume_index_i; i++) { + struct file_handle *f; + + bool old_cif = case_insensitive_fopen; + case_insensitive_fopen = true; + if ( + false +#if defined (UEFI) + || (f = fopen(volume_index[i], "/EFI/limine/limine.conf")) != NULL + || (f = fopen(volume_index[i], "/EFI/BOOT/limine.conf")) != NULL +#endif + || (f = fopen(volume_index[i], "/boot/limine/limine.conf")) != NULL + || (f = fopen(volume_index[i], "/boot/limine.conf")) != NULL + || (f = fopen(volume_index[i], "/limine/limine.conf")) != NULL + || (f = fopen(volume_index[i], "/limine.conf")) != NULL + ) { + goto opened; + } + + case_insensitive_fopen = old_cif; + continue; + +opened: + case_insensitive_fopen = old_cif; + + fclose(f); + + if (volume_index[i]->backing_dev != NULL) { + boot_volume = volume_index[i]->backing_dev; + } else { + boot_volume = volume_index[i]; + } + + break; + } + + if (boot_volume != NULL) { + stage3_common(); + } + + panic(false, "No volume contained a Limine configuration file"); + } + + EFI_GUID loaded_img_prot_guid = EFI_LOADED_IMAGE_PROTOCOL_GUID; + EFI_LOADED_IMAGE_PROTOCOL *loaded_image = NULL; + + status = gBS->HandleProtocol(current_handle, &loaded_img_prot_guid, + (void **)&loaded_image); + + if (status) { + goto could_not_match; + } + + boot_volume = disk_volume_from_efi_handle(loaded_image->DeviceHandle); + + if (boot_volume != NULL) { + stage3_common(); + } + + current_handle = loaded_image->ParentHandle; + } + + goto could_not_match; +} +#endif + +noreturn void stage3_common(void) { +#if defined (__x86_64__) || defined (__i386__) + init_flush_irqs(); + init_io_apics(); +#endif + +#if defined (__riscv) +#if defined (UEFI) + RISCV_EFI_BOOT_PROTOCOL *rv_proto = get_riscv_boot_protocol(); + if (rv_proto == NULL || rv_proto->GetBootHartId(rv_proto, &bsp_hartid) != EFI_SUCCESS) { + panic(false, "failed to get BSP's hartid"); + } +#else +#error riscv: only UEFI is supported +#endif +#endif + + term_notready(); + +#if defined (UEFI) + init_bli(); +#endif + + menu(true); +} diff --git a/limine/common/entry_asm.s2.asm_bios_ia32 b/limine/common/entry_asm.s2.asm_bios_ia32 new file mode 100644 index 0000000..565e635 --- /dev/null +++ b/limine/common/entry_asm.s2.asm_bios_ia32 @@ -0,0 +1,31 @@ +extern bss_begin +extern bss_end +extern entry +extern gdt + +section .entry progbits alloc exec nowrite align=16 + +global _start +_start: + cld + + ; Zero out .bss + xor al, al + mov edi, bss_begin + mov ecx, bss_end + sub ecx, bss_begin + rep stosb + + lgdt [gdt] + jmp 0x18:.reload_cs + .reload_cs: + mov eax, 0x20 + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + mov ss, ax + + jmp entry + +section .note.GNU-stack noalloc noexec nowrite progbits diff --git a/limine/common/fs/fat32.h b/limine/common/fs/fat32.h new file mode 100644 index 0000000..0b6b89e --- /dev/null +++ b/limine/common/fs/fat32.h @@ -0,0 +1,11 @@ +#ifndef FS__FAT32_H__ +#define FS__FAT32_H__ + +#include +#include + +char *fat32_get_label(struct volume *part); + +struct file_handle *fat32_open(struct volume *part, const char *path); + +#endif diff --git a/limine/common/fs/fat32.s2.c b/limine/common/fs/fat32.s2.c new file mode 100644 index 0000000..cfac741 --- /dev/null +++ b/limine/common/fs/fat32.s2.c @@ -0,0 +1,733 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#define FAT32_LFN_MAX_ENTRIES 20 +#define FAT32_LFN_MAX_FILENAME_LENGTH (FAT32_LFN_MAX_ENTRIES * 13 + 1) + +#define FAT32_ATTRIBUTE_SUBDIRECTORY 0x10 +#define FAT32_LFN_ATTRIBUTE 0x0F +#define FAT32_ATTRIBUTE_VOLLABEL 0x08 + +struct fat32_context { + struct volume *part; + int type; + char *label; + uint16_t bytes_per_sector; + uint8_t sectors_per_cluster; + uint16_t reserved_sectors; + uint8_t number_of_fats; + uint32_t hidden_sectors; + uint32_t sectors_per_fat; + uint32_t fat_start_lba; + uint32_t data_start_lba; + uint32_t root_directory_cluster; + uint16_t root_entries; + uint32_t root_start; + uint32_t root_size; +}; + +struct fat32_file_handle { + struct fat32_context context; + uint32_t first_cluster; + uint32_t size_bytes; + + uint32_t *cluster_chain; + size_t chain_len; +}; + +struct fat32_bpb { + union { + struct { + uint8_t jump[3]; + char oem[8]; + uint16_t bytes_per_sector; + uint8_t sectors_per_cluster; + uint16_t reserved_sectors; + uint8_t fats_count; + uint16_t root_entries_count; + uint16_t sectors_count_16; + uint8_t media_descriptor_type; + uint16_t sectors_per_fat_16; + uint16_t sectors_per_track; + uint16_t heads_count; + uint32_t hidden_sectors_count; + uint32_t sectors_count_32; + uint32_t sectors_per_fat_32; + uint16_t flags; + uint16_t fat_version_number; + uint32_t root_directory_cluster; + uint16_t fs_info_sector; + uint16_t backup_boot_sector; + uint8_t reserved[12]; + uint8_t drive_number; + uint8_t nt_flags; + uint8_t signature; + uint32_t volume_serial_number; + char label[11]; + char system_identifier[8]; + } __attribute__((packed)); + uint8_t padding[512]; + }; +} __attribute__((packed)); + +struct fat32_directory_entry { + char file_name_and_ext[8 + 3]; + uint8_t attribute; + uint8_t file_data_1[8]; + uint16_t cluster_num_high; + uint8_t file_data_2[4]; + uint16_t cluster_num_low; + uint32_t file_size_bytes; +} __attribute__((packed)); + +struct fat32_lfn_entry { + uint8_t sequence_number; + char name1[10]; + uint8_t attribute; + uint8_t type; + uint8_t dos_checksum; + char name2[12]; + uint16_t first_cluster; + char name3[4]; +} __attribute__((packed)); + +static int fat32_open_in(struct fat32_context* context, struct fat32_directory_entry* directory, struct fat32_directory_entry* file, const char* name); + +static int fat32_init_context(struct fat32_context* context, struct volume *part) { + context->part = part; + + struct fat32_bpb bpb; + if (!volume_read(context->part, &bpb, 0, sizeof(struct fat32_bpb))) { + return 1; + } + + // Sanity check of bpb + + // Checks for FAT12/16 + if (strncmp((((void *)&bpb) + 0x36), "FAT", 3) == 0) { + goto signature_valid; + } + + // Checks for FAT32 + if (strncmp((((void *)&bpb) + 0x52), "FAT", 3) == 0) { + goto signature_valid; + } + + // Checks for FAT32 (with 64-bit sector count) + if (strncmp((((void *)&bpb) + 0x03), "FAT32", 5) == 0) { + goto signature_valid; + } + + return 1; + +signature_valid:; + + const uint8_t sector_per_cluster_valid_values[] = { 1, 2, 4, 8, 16, 32, 64, 128 }; + for (size_t i = 0; i < SIZEOF_ARRAY(sector_per_cluster_valid_values); i++) { + if (bpb.sectors_per_cluster == sector_per_cluster_valid_values[i]) { + goto sector_per_cluster_valid; + } + } + + return 1; + +sector_per_cluster_valid:; + + const uint16_t bytes_per_sector_valid_values[] = { 512, 1024, 2048, 4096 }; + for (size_t i = 0; i < SIZEOF_ARRAY(bytes_per_sector_valid_values); i++) { + if (bpb.bytes_per_sector == bytes_per_sector_valid_values[i]) { + goto bytes_per_sector_valid; + } + } + + return 1; + +bytes_per_sector_valid:; + + // Validate fats_count (typically 1 or 2, but allow up to 4) + if (bpb.fats_count == 0 || bpb.fats_count > 4) { + return 1; + } + + // The boot sector itself occupies at least sector 0 + if (bpb.reserved_sectors == 0) { + return 1; + } + + // The following mess to identify the FAT type is from the FAT spec + // at paragraph 3.5 + size_t root_dir_sects = ((bpb.root_entries_count * 32) + (bpb.bytes_per_sector - 1)) / bpb.bytes_per_sector; + + // Calculate total sectors and metadata sectors separately to check for underflow + uint64_t total_sects = bpb.sectors_count_16 ? bpb.sectors_count_16 : bpb.sectors_count_32; + uint64_t sectors_per_fat = bpb.sectors_per_fat_16 ? bpb.sectors_per_fat_16 : bpb.sectors_per_fat_32; + uint64_t metadata_sects = (uint64_t)bpb.reserved_sectors + ((uint64_t)bpb.fats_count * sectors_per_fat) + root_dir_sects; + + // Check for underflow before subtraction + if (metadata_sects >= total_sects) { + return 1; // Invalid filesystem: metadata exceeds total size + } + + size_t data_sects = total_sects - metadata_sects; + size_t clusters_count = data_sects / bpb.sectors_per_cluster; + + if (clusters_count < 4085) { + context->type = 12; + } else if (clusters_count < 65525) { + context->type = 16; + } else { + context->type = 32; + } + + context->bytes_per_sector = bpb.bytes_per_sector; + context->sectors_per_cluster = bpb.sectors_per_cluster; + context->reserved_sectors = bpb.reserved_sectors; + context->number_of_fats = bpb.fats_count; + context->hidden_sectors = bpb.hidden_sectors_count; + context->sectors_per_fat = context->type == 32 ? bpb.sectors_per_fat_32 : bpb.sectors_per_fat_16; + if (context->sectors_per_fat == 0) { + return 1; + } + context->root_directory_cluster = bpb.root_directory_cluster; + context->fat_start_lba = bpb.reserved_sectors; + context->root_entries = bpb.root_entries_count; + + // FAT12/16 require a non-zero root directory entry count + if (context->type != 32 && context->root_entries == 0) { + return 1; + } + + // Calculate root_start with overflow check + uint64_t root_start_64 = (uint64_t)context->reserved_sectors + (uint64_t)context->number_of_fats * context->sectors_per_fat; + if (root_start_64 > UINT32_MAX) { + return 1; // Overflow in root_start calculation + } + context->root_start = (uint32_t)root_start_64; + context->root_size = DIV_ROUNDUP(context->root_entries * sizeof(struct fat32_directory_entry), context->bytes_per_sector); + switch (context->type) { + case 12: + case 16: + // Check for overflow in data_start_lba calculation + if (__builtin_add_overflow(context->root_start, context->root_size, &context->data_start_lba)) { + return 1; + } + break; + case 32: + context->data_start_lba = context->root_start; + break; + default: + __builtin_unreachable(); + } + + // get the volume label + struct fat32_directory_entry _current_directory; + struct fat32_directory_entry *current_directory; + + switch (context->type) { + case 12: + case 16: + current_directory = NULL; + break; + case 32: + _current_directory.cluster_num_low = context->root_directory_cluster & 0xFFFF; + _current_directory.cluster_num_high = context->root_directory_cluster >> 16; + current_directory = &_current_directory; + break; + default: + __builtin_unreachable(); + } + + char *vol_label; + if (fat32_open_in(context, current_directory, (struct fat32_directory_entry *)&vol_label, NULL) == 0) { + context->label = vol_label; + } else { + context->label = NULL; + } + + return 0; +} + +static int read_cluster_from_map(struct fat32_context *context, uint32_t cluster, uint32_t *out) { + uint64_t fat_base = (uint64_t)context->fat_start_lba * context->bytes_per_sector; + uint64_t fat_size = (uint64_t)context->sectors_per_fat * context->bytes_per_sector; + + switch (context->type) { + case 12: { + *out = 0; + uint16_t tmp = 0; + uint64_t offset = (uint64_t)cluster + (uint64_t)(cluster / 2); + + // Ensure 2-byte reads won't exceed FAT table bounds + if (offset + sizeof(uint16_t) > fat_size) { + return -1; + } + + if (!volume_read(context->part, &tmp, fat_base + offset, sizeof(uint16_t))) { + return -1; + } + if (cluster % 2 == 0) { + *out = tmp & 0xfff; + } else { + *out = tmp >> 4; + } + break; + } + case 16: { + *out = 0; + uint64_t offset = (uint64_t)cluster * sizeof(uint16_t); + if (offset + sizeof(uint16_t) > fat_size) { + return -1; + } + if (!volume_read(context->part, out, fat_base + offset, sizeof(uint16_t))) { + return -1; + } + break; + } + case 32: { + uint64_t offset = (uint64_t)cluster * sizeof(uint32_t); + if (offset + sizeof(uint32_t) > fat_size) { + return -1; + } + if (!volume_read(context->part, out, fat_base + offset, sizeof(uint32_t))) { + return -1; + } + *out &= 0x0fffffff; + break; + } + default: + __builtin_unreachable(); + } + + return 0; +} + +// Maximum cluster chain length to prevent memory exhaustion (64MB of cluster chain data) +#define FAT32_MAX_CHAIN_LENGTH (64 * 1024 * 1024 / sizeof(uint32_t)) + +static uint32_t *cache_cluster_chain(struct fat32_context *context, + uint32_t initial_cluster, + size_t *_chain_length) { + uint32_t cluster_limit = (context->type == 12 ? 0xfef : 0) + | (context->type == 16 ? 0xffef : 0) + | (context->type == 32 ? 0xfffffef : 0); + if (initial_cluster < 0x2 || initial_cluster > cluster_limit) + return NULL; + + // Limit chain length to prevent memory exhaustion from malicious filesystems + size_t max_clusters = cluster_limit - 1; + if (max_clusters > FAT32_MAX_CHAIN_LENGTH) { + max_clusters = FAT32_MAX_CHAIN_LENGTH; + } + + uint32_t cluster = initial_cluster; + size_t chain_length; + for (chain_length = 1; chain_length <= max_clusters; chain_length++) { + if (read_cluster_from_map(context, cluster, &cluster) != 0) { + return NULL; + } + if (cluster < 0x2 || cluster > cluster_limit) + break; + } + + if (chain_length > max_clusters) { + // Circular or corrupted cluster chain detected + return NULL; + } + + size_t alloc_size; + if (__builtin_mul_overflow(chain_length, sizeof(uint32_t), &alloc_size)) { + return NULL; + } + uint32_t *cluster_chain = ext_mem_alloc(alloc_size); + cluster = initial_cluster; + for (size_t i = 0; i < chain_length; i++) { + cluster_chain[i] = cluster; + if (read_cluster_from_map(context, cluster, &cluster) != 0) { + pmm_free(cluster_chain, alloc_size); + return NULL; + } + } + *_chain_length = chain_length; + return cluster_chain; +} + +static bool read_cluster_chain(struct fat32_context *context, + uint32_t *cluster_chain, + size_t chain_len, + void *buf, uint64_t loc, uint64_t count) { + uint64_t block_size = (uint64_t)context->sectors_per_cluster * (uint64_t)context->bytes_per_sector; + for (uint64_t progress = 0; progress < count;) { + uint64_t block = (loc + progress) / block_size; + + // Bounds check: ensure block index is within cluster chain + if (block >= chain_len) { + return false; + } + + // Validate cluster number before arithmetic to prevent underflow + uint32_t cluster = cluster_chain[block]; + if (cluster < 2) { + return false; + } + + uint64_t chunk = count - progress; + uint64_t offset = (loc + progress) % block_size; + if (chunk > block_size - offset) + chunk = block_size - offset; + + uint64_t base = ((uint64_t)context->data_start_lba + (uint64_t)(cluster - 2) * context->sectors_per_cluster) * context->bytes_per_sector; + if (!volume_read(context->part, buf + progress, base + offset, chunk)) { + return false; + } + + progress += chunk; + } + + return true; +} + +// Copy ucs-2 characters to char*, with bounds checking +static void fat32_lfncpy(char* destination, size_t dest_size, size_t dest_offset, + const void* source, unsigned int size) { + for (unsigned int i = 0; i < size; i++) { + if (dest_offset + i >= dest_size) { + return; // Prevent buffer overflow + } + // ignore high bytes + *(((uint8_t*) destination) + dest_offset + i) = *(((uint8_t*) source) + (i * 2)); + } +} + +static bool fat32_filename_to_8_3(char *dest, const char *src) { + int i = 0, j = 0; + bool ext = false; + + for (size_t k = 0; k < 8+3; k++) + dest[k] = ' '; + + while (src[i]) { + if (src[i] == '.') { + if (ext) { + // This is a double extension here, just give up. + return false; + } + ext = true; + j = 8; + i++; + continue; + } + if (j >= 8+3 || (j >= 8 && !ext)) { + // Filename too long, give up. + return false; + } + dest[j++] = toupper(src[i++]); + } + + return true; +} + +static int fat32_open_in(struct fat32_context* context, struct fat32_directory_entry* directory, struct fat32_directory_entry* file, const char* name) { + size_t block_size = context->sectors_per_cluster * context->bytes_per_sector; + char current_lfn[FAT32_LFN_MAX_FILENAME_LENGTH] = {0}; + + size_t dir_chain_len; + struct fat32_directory_entry *directory_entries; + + if (directory != NULL) { + uint32_t current_cluster_number = directory->cluster_num_low; + if (context->type == 32) + current_cluster_number |= (uint32_t)directory->cluster_num_high << 16; + + uint32_t *directory_cluster_chain = cache_cluster_chain(context, current_cluster_number, &dir_chain_len); + + if (directory_cluster_chain == NULL) + return -1; + + // Check for integer overflow in allocation size + size_t alloc_size; + if (__builtin_mul_overflow(dir_chain_len, block_size, &alloc_size) || alloc_size > 256 * 1024 * 1024) { + // Limit directory size to 256MB to prevent memory exhaustion + pmm_free(directory_cluster_chain, dir_chain_len * sizeof(uint32_t)); + return -1; + } + + directory_entries = ext_mem_alloc(alloc_size); + + if (!read_cluster_chain(context, directory_cluster_chain, dir_chain_len, directory_entries, 0, alloc_size)) { + pmm_free(directory_entries, alloc_size); + pmm_free(directory_cluster_chain, dir_chain_len * sizeof(uint32_t)); + return -1; + } + + pmm_free(directory_cluster_chain, dir_chain_len * sizeof(uint32_t)); + } else { + dir_chain_len = DIV_ROUNDUP(context->root_entries * sizeof(struct fat32_directory_entry), block_size); + + // Check for overflow + size_t alloc_size; + if (__builtin_mul_overflow(dir_chain_len, block_size, &alloc_size) || alloc_size > 256 * 1024 * 1024) { + return -1; + } + + directory_entries = ext_mem_alloc(alloc_size); + + if (!volume_read(context->part, directory_entries, (uint64_t)context->root_start * context->bytes_per_sector, context->root_entries * sizeof(struct fat32_directory_entry))) { + pmm_free(directory_entries, alloc_size); + return -1; + } + } + + int ret; + + for (size_t i = 0; i < (dir_chain_len * block_size) / sizeof(struct fat32_directory_entry); i++) { + if (directory_entries[i].file_name_and_ext[0] == 0x00) { + // no more entries here + break; + } + + if (name == NULL) { + if (directory_entries[i].attribute != FAT32_ATTRIBUTE_VOLLABEL) { + continue; + } + char *r = ext_mem_alloc(12); + memcpy(r, directory_entries[i].file_name_and_ext, 11); + // remove trailing spaces + for (int j = 10; j >= 0; j--) { + if (r[j] == ' ') { + r[j] = 0; + continue; + } + break; + } + *((char **)file) = r; + ret = 0; + goto out; + } + + if (directory_entries[i].attribute == FAT32_LFN_ATTRIBUTE) { + struct fat32_lfn_entry* lfn = (struct fat32_lfn_entry*) &directory_entries[i]; + + if (lfn->sequence_number & 0b01000000) { + // this lfn is the first entry in the table, clear the lfn buffer + memset(current_lfn, ' ', sizeof(current_lfn)); + } + + const unsigned int seq_num = lfn->sequence_number & 0b00011111; + if (seq_num == 0) { + continue; // Invalid sequence number, skip + } + const unsigned int lfn_index = (seq_num - 1U) * 13U; + if (lfn_index >= FAT32_LFN_MAX_ENTRIES * 13) { + continue; + } + + fat32_lfncpy(current_lfn, sizeof(current_lfn), lfn_index + 0, lfn->name1, 5); + fat32_lfncpy(current_lfn, sizeof(current_lfn), lfn_index + 5, lfn->name2, 6); + fat32_lfncpy(current_lfn, sizeof(current_lfn), lfn_index + 11, lfn->name3, 2); + + if (lfn_index != 0) + continue; + + // remove trailing spaces + for (int j = SIZEOF_ARRAY(current_lfn) - 2; j >= -1; j--) { + if (j == -1 || current_lfn[j] != ' ') { + current_lfn[j + 1] = 0; + break; + } + } + + int (*strcmpfn)(const char *, const char *) = case_insensitive_fopen ? strcasecmp : strcmp; + + if (strcmpfn(current_lfn, name) == 0) { + // Ensure i+1 is within bounds before accessing + if (i + 1 >= (dir_chain_len * block_size) / sizeof(struct fat32_directory_entry)) { + ret = -1; + goto out; + } + // Validate that the next entry is a valid SFN entry (not LFN, deleted, or end-of-dir) + struct fat32_directory_entry *sfn_entry = &directory_entries[i+1]; + if (sfn_entry->file_name_and_ext[0] == 0x00 || + (uint8_t)sfn_entry->file_name_and_ext[0] == 0xE5 || + sfn_entry->attribute == FAT32_LFN_ATTRIBUTE) { + // Corrupted LFN sequence - expected SFN entry not found + ret = -1; + goto out; + } + *file = *sfn_entry; + ret = 0; + goto out; + } + } + + if (directory_entries[i].attribute & (1 << 3)) { + // It is a volume label, skip + continue; + } + // SFN + char fn[8+3]; + if (!fat32_filename_to_8_3(fn, name)) { + continue; + } + if (!strncmp(directory_entries[i].file_name_and_ext, fn, 8+3)) { + *file = directory_entries[i]; + ret = 0; + goto out; + } + } + + // file not found + ret = -1; + +out: + pmm_free(directory_entries, dir_chain_len * block_size); + return ret; +} + +char *fat32_get_label(struct volume *part) { + struct fat32_context context; + if (fat32_init_context(&context, part) != 0) { + return NULL; + } + + return context.label; +} + +static void fat32_read(struct file_handle *handle, void *buf, uint64_t loc, uint64_t count); +static void fat32_close(struct file_handle *file); + +struct file_handle *fat32_open(struct volume *part, const char *path) { + struct fat32_context context; + int r = fat32_init_context(&context, part); + + if (r) { + return NULL; + } + + struct fat32_directory_entry _current_directory; + struct fat32_directory_entry *current_directory; + struct fat32_directory_entry current_file; + unsigned int current_index = 0; + char current_part[FAT32_LFN_MAX_FILENAME_LENGTH]; + + // skip trailing slashes + while (path[current_index] == '/') { + current_index++; + } + + // walk down the directory tree + switch (context.type) { + case 12: + case 16: + current_directory = NULL; + break; + case 32: + _current_directory.cluster_num_low = context.root_directory_cluster & 0xFFFF; + _current_directory.cluster_num_high = context.root_directory_cluster >> 16; + current_directory = &_current_directory; + break; + default: + __builtin_unreachable(); + } + + for (;;) { + bool expect_directory = false; + bool found_terminator = false; + + for (unsigned int i = 0; i < SIZEOF_ARRAY(current_part) - 1; i++) { + // Check for overflow before computing path index + unsigned int path_idx; + if (__builtin_add_overflow(i, current_index, &path_idx)) { + return NULL; // Path index would overflow + } + + if (path[path_idx] == 0) { + memcpy(current_part, path + current_index, i); + current_part[i] = 0; + expect_directory = false; + found_terminator = true; + break; + } + + if (path[path_idx] == '/') { + memcpy(current_part, path + current_index, i); + current_part[i] = 0; + // Check for overflow before updating current_index + unsigned int new_index; + if (__builtin_add_overflow(current_index, i + 1, &new_index)) { + return NULL; // current_index would overflow + } + current_index = new_index; + expect_directory = true; + found_terminator = true; + break; + } + } + + // If loop completed without finding terminator, path component is too long + if (!found_terminator) { + return NULL; + } + + if ((r = fat32_open_in(&context, current_directory, ¤t_file, current_part)) != 0) { + return NULL; + } + + if (expect_directory) { + if (!(current_file.attribute & FAT32_ATTRIBUTE_SUBDIRECTORY)) { + return NULL; + } + _current_directory = current_file; + current_directory = &_current_directory; + } else { + struct file_handle *handle = ext_mem_alloc(sizeof(struct file_handle)); + struct fat32_file_handle *ret = ext_mem_alloc(sizeof(struct fat32_file_handle)); + + ret->context = context; + ret->first_cluster = current_file.cluster_num_low; + if (context.type == 32) + ret->first_cluster |= (uint64_t)current_file.cluster_num_high << 16; + + ret->size_bytes = current_file.file_size_bytes; + // Initialize chain_len before calling cache_cluster_chain + // (cache_cluster_chain may return NULL without setting it for empty files) + ret->chain_len = 0; + ret->cluster_chain = cache_cluster_chain(&context, ret->first_cluster, &ret->chain_len); + + if (ret->cluster_chain == NULL && ret->size_bytes != 0) { + pmm_free(ret, sizeof(struct fat32_file_handle)); + pmm_free(handle, sizeof(struct file_handle)); + return NULL; + } + + handle->fd = (void *)ret; + handle->read = (void *)fat32_read; + handle->close = (void *)fat32_close; + handle->size = ret->size_bytes; + handle->vol = part; +#if defined (UEFI) + handle->efi_part_handle = part->efi_part_handle; +#endif + + return handle; + } + } +} + +static void fat32_read(struct file_handle *file, void *buf, uint64_t loc, uint64_t count) { + struct fat32_file_handle *f = file->fd; + if (!read_cluster_chain(&f->context, f->cluster_chain, f->chain_len, buf, loc, count)) { + panic(false, "fat32: cluster chain read failed (corrupted filesystem?)"); + } +} + +static void fat32_close(struct file_handle *file) { + struct fat32_file_handle *f = file->fd; + pmm_free(f->cluster_chain, f->chain_len * sizeof(uint32_t)); + pmm_free(f, sizeof(struct fat32_file_handle)); +} diff --git a/limine/common/fs/file.h b/limine/common/fs/file.h new file mode 100644 index 0000000..86474bd --- /dev/null +++ b/limine/common/fs/file.h @@ -0,0 +1,45 @@ +#ifndef FS__FILE_H__ +#define FS__FILE_H__ + +#include +#include +#include +#include +#if defined (UEFI) +# include +#endif + +extern bool case_insensitive_fopen; + +bool fs_get_guid(struct guid *guid, struct volume *part); +char *fs_get_label(struct volume *part); + +struct file_handle { + bool is_memfile; + bool readall; + struct volume *vol; + char *path; + size_t path_len; + void *fd; + void (*read)(void *fd, void *buf, uint64_t loc, uint64_t count); + void (*close)(void *fd); + uint64_t size; +#if defined (UEFI) + EFI_HANDLE efi_part_handle; +#endif + bool pxe; + uint32_t pxe_ip; + uint16_t pxe_port; +}; + +struct file_handle *fopen(struct volume *part, const char *filename); +void fread(struct file_handle *fd, void *buf, uint64_t loc, uint64_t count); +void fclose(struct file_handle *fd); +void *freadall(struct file_handle *fd, uint32_t type); +void *freadall_mode(struct file_handle *fd, uint32_t type, bool allow_high_allocs +#if defined (__i386__) + , void (*memcpy_to_64)(uint64_t dst, void *src, size_t count) +#endif +); + +#endif diff --git a/limine/common/fs/file.s2.c b/limine/common/fs/file.s2.c new file mode 100644 index 0000000..3bc064b --- /dev/null +++ b/limine/common/fs/file.s2.c @@ -0,0 +1,184 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +char *fs_get_label(struct volume *part) { + char *ret; + + if ((ret = fat32_get_label(part)) != NULL) { + return ret; + } + + return NULL; +} + +bool fs_get_guid(struct guid *guid, struct volume *part) { + (void)guid; (void)part; + + return false; +} + +bool case_insensitive_fopen = false; + +struct file_handle *fopen(struct volume *part, const char *filename) { + size_t filename_new_len = strlen(filename) + 2; + char *filename_new = ext_mem_alloc(filename_new_len); + + if (filename[0] != '/') { + filename_new[0] = '/'; + strcpy(&filename_new[1], filename); + } else { + strcpy(filename_new, filename); + } + + filename = filename_new; + + struct file_handle *ret; + + if (part->pxe) { + if ((ret = tftp_open(part, "", filename)) == NULL) { + goto err; + } + pmm_free(filename_new, filename_new_len); + return ret; + } + + if ((ret = iso9660_open(part, filename)) != NULL) { + goto success; + } + if ((ret = fat32_open(part, filename)) != NULL) { + goto success; + } + +err: + pmm_free(filename_new, filename_new_len); + return NULL; + +success: + ret->path = (char *)filename; + ret->path_len = filename_new_len; + + return ret; +} + +void fclose(struct file_handle *fd) { + if (fd->is_memfile) { + if (fd->readall == false) { + pmm_free(fd->fd, fd->size); + } + } else { + fd->close(fd); + } + pmm_free(fd->path, fd->path_len); + pmm_free(fd, sizeof(struct file_handle)); +} + +void fread(struct file_handle *fd, void *buf, uint64_t loc, uint64_t count) { + if (fd->is_memfile) { + if (loc >= fd->size || count > fd->size - loc) { + panic(false, "fread: attempted out of bounds read"); + } + memcpy(buf, fd->fd + loc, count); + } else { + fd->read(fd, buf, loc, count); + } +} + +void *freadall(struct file_handle *fd, uint32_t type) { + return freadall_mode(fd, type, false +#if defined (__i386__) + , NULL +#endif + ); +} + +void *freadall_mode(struct file_handle *fd, uint32_t type, bool allow_high_allocs +#if defined (__i386__) + , void (*memcpy_to_64)(uint64_t dst, void *src, size_t count) +#endif +) { +#if defined (__i386__) + static uint64_t high_ret; + + if (memcpy_to_64 == NULL) { + allow_high_allocs = false; + } +#endif + + if (fd->is_memfile) { + if (fd->readall) { +#if defined (__i386__) + if (allow_high_allocs == true) { + high_ret = (uintptr_t)fd->fd; + return &high_ret; + } +#endif + return fd->fd; + } +#if defined (UEFI) && defined (__x86_64__) + if (!allow_high_allocs && (uintptr_t)fd->fd >= 0x100000000) { + void *newptr = ext_mem_alloc_type(fd->size, type); + memcpy(newptr, fd->fd, fd->size); + pmm_free(fd->fd, fd->size); + fd->fd = newptr; + } else { +#endif + memmap_alloc_range((uint64_t)(size_t)fd->fd, ALIGN_UP(fd->size, 4096), type, 0, true, false, false); +#if defined (UEFI) && defined (__x86_64__) + } +#endif + fd->readall = true; +#if defined (__i386__) + if (allow_high_allocs == true) { + high_ret = (uintptr_t)fd->fd; + return &high_ret; + } +#endif + return fd->fd; + } else { + void *ret = ext_mem_alloc_type_aligned_mode(fd->size, type, 4096, allow_high_allocs); +#if defined (__i386__) + if (allow_high_allocs == true) { + high_ret = *(uint64_t *)ret; + if (high_ret < 0x100000000) { + ret = (void *)(uintptr_t)high_ret; + goto low_ret; + } + void *pool = ext_mem_alloc(0x100000); + for (uint64_t i = 0; i < fd->size; i += 0x100000) { + size_t count; + if (fd->size - i < 0x100000) { + count = fd->size - i; + } else { + count = 0x100000; + } + fd->read(fd, pool, i, count); + memcpy_to_64(high_ret + i, pool, count); + } + pmm_free(pool, 0x100000); + fd->close(fd); + return &high_ret; + } +low_ret: +#endif + fd->read(fd, ret, 0, fd->size); + fd->close(fd); + fd->fd = ret; + fd->readall = true; + fd->is_memfile = true; +#if defined (__i386__) + if (allow_high_allocs == true) { + return &high_ret; + } +#endif + return ret; + } +} diff --git a/limine/common/fs/iso9660.h b/limine/common/fs/iso9660.h new file mode 100644 index 0000000..6ab6d26 --- /dev/null +++ b/limine/common/fs/iso9660.h @@ -0,0 +1,10 @@ +#ifndef FS__ISO9660_H__ +#define FS__ISO9660_H__ + +#include +#include +#include + +struct file_handle *iso9660_open(struct volume *vol, const char *path); + +#endif diff --git a/limine/common/fs/iso9660.s2.c b/limine/common/fs/iso9660.s2.c new file mode 100644 index 0000000..a953c27 --- /dev/null +++ b/limine/common/fs/iso9660.s2.c @@ -0,0 +1,544 @@ +#include +#include +#include +#include +#include +#include + +#define ISO9660_SECTOR_SIZE (2 << 10) + +struct iso9660_context { + struct volume *vol; + void *root; + uint32_t root_size; +}; + +struct iso9660_extent { + uint32_t LBA; + uint32_t size; +}; + +struct iso9660_file_handle { + struct iso9660_context *context; + uint64_t total_size; + uint32_t extent_count; + struct iso9660_extent *extents; +}; + +#define ISO9660_FLAG_MULTI_EXTENT 0x80 + +#define ISO9660_FIRST_VOLUME_DESCRIPTOR 0x10 +#define ISO9660_VOLUME_DESCRIPTOR_SIZE ISO9660_SECTOR_SIZE +#define ROCK_RIDGE_MAX_FILENAME 255 +#define ISO9660_MAX_EXTENT_COUNT 65536 + +// --- Both endian structures --- +struct BE16_t { uint16_t little, big; } __attribute__((packed)); +struct BE32_t { uint32_t little, big; } __attribute__((packed)); + +// --- Directory entries --- +struct iso9660_directory_entry { + uint8_t length; + uint8_t extended_attribute_length; + struct BE32_t extent; + struct BE32_t extent_size; + uint8_t datetime[7]; + uint8_t flags; + uint8_t interleaved_unit_size; + uint8_t interleaved_gap_size; + struct BE16_t volume_seq; + uint8_t filename_size; + char name[]; +} __attribute__((packed)); + +// --- Volume descriptors --- +// VDT = Volume Descriptor Type +enum { + ISO9660_VDT_BOOT_RECORD, + ISO9660_VDT_PRIMARY, + ISO9660_VDT_SUPPLEMENTARY, + ISO9660_VDT_PARTITION_DESCRIPTOR, + ISO9660_VDT_TERMINATOR = 255 +}; + +struct iso9660_volume_descriptor { + uint8_t type; + char identifier[5]; + uint8_t version; +} __attribute__((packed)); + +struct iso9660_primary_volume { + struct iso9660_volume_descriptor volume_descriptor; + + union { + struct { + uint8_t unused0[1]; + char system_identifier[32]; + char volume_identifier[32]; + uint8_t unused1[8]; + struct BE32_t space_size; + uint8_t unused2[32]; + struct BE16_t set_size; + struct BE16_t volume_seq; + struct BE16_t LBA_size; + struct BE32_t path_table_size; + + uint32_t LBA_path_table_little; + uint32_t LBA_optional_path_table_little; + uint32_t LBA_path_table_big; + uint32_t LBA_optional_path_table_big; + + struct iso9660_directory_entry root; + } __attribute__((packed)); + + uint8_t padding[2041]; + }; +} __attribute__((packed)); + + +// --- Implementation --- +struct iso9660_contexts_node { + struct iso9660_context context; + struct iso9660_contexts_node *next; +}; + +static struct iso9660_contexts_node *contexts = NULL; + +// Maximum number of volume descriptors to scan before giving up +#define ISO9660_MAX_VOLUME_DESCRIPTORS 256 + +// Maximum directory size to prevent memory exhaustion (64MB) +#define ISO9660_MAX_DIR_SIZE (64 * 1024 * 1024) + +static void iso9660_find_PVD(struct iso9660_primary_volume *desc, struct volume *vol) { + uint32_t lba = ISO9660_FIRST_VOLUME_DESCRIPTOR; + uint32_t max_lba = ISO9660_FIRST_VOLUME_DESCRIPTOR + ISO9660_MAX_VOLUME_DESCRIPTORS; + + while (lba < max_lba) { + uint64_t offset = (uint64_t)lba * ISO9660_SECTOR_SIZE; + if (!volume_read(vol, desc, offset, sizeof(struct iso9660_primary_volume))) { + panic(false, "ISO9660: failed to read volume descriptor"); + } + + switch (desc->volume_descriptor.type) { + case ISO9660_VDT_PRIMARY: + return; + case ISO9660_VDT_TERMINATOR: + panic(false, "ISO9660: no primary volume descriptor"); + break; + } + + ++lba; + } + + panic(false, "ISO9660: exceeded maximum volume descriptor search limit"); +} + +static void iso9660_cache_root(struct volume *vol, + void **root, + uint32_t *root_size) { + struct iso9660_primary_volume pv; + iso9660_find_PVD(&pv, vol); + + *root_size = pv.root.extent_size.little; + + // Validate root directory size to prevent memory exhaustion + if (*root_size == 0 || *root_size > ISO9660_MAX_DIR_SIZE) { + panic(false, "ISO9660: Invalid root directory size"); + } + + *root = ext_mem_alloc(*root_size); + uint64_t offset = (uint64_t)pv.root.extent.little * ISO9660_SECTOR_SIZE; + if (!volume_read(vol, *root, offset, *root_size)) { + panic(false, "ISO9660: failed to read root directory"); + } +} + +static struct iso9660_context *iso9660_get_context(struct volume *vol) { + struct iso9660_contexts_node *current = contexts; + while (current) { + if (current->context.vol == vol) + return ¤t->context; + current = current->next; + } + + // The context is not cached at this point + struct iso9660_contexts_node *node = ext_mem_alloc(sizeof(struct iso9660_contexts_node)); + node->context.vol = vol; + iso9660_cache_root(vol, &node->context.root, &node->context.root_size); + + node->next = contexts; + contexts = node; + return &node->context; +} + +static bool load_name(char *buf, size_t limit, struct iso9660_directory_entry *entry) { + unsigned char* sysarea = ((unsigned char*)entry) + sizeof(struct iso9660_directory_entry) + entry->filename_size; + + // Validate entry->length is large enough + if (entry->length < sizeof(struct iso9660_directory_entry) + entry->filename_size) { + goto use_iso_name; + } + + size_t sysarea_len = entry->length - sizeof(struct iso9660_directory_entry) - entry->filename_size; + if ((entry->filename_size & 0x1) == 0) { + if (sysarea_len == 0) { + goto use_iso_name; + } + sysarea++; + sysarea_len--; + } + + int rrnamelen = 0; + unsigned char *nm_entry = NULL; + while ((sysarea_len >= 4) && (sysarea[3] == 1)) { + // Validate entry length doesn't exceed remaining sysarea + if (sysarea[2] > sysarea_len) { + break; + } + if (sysarea[0] == 'N' && sysarea[1] == 'M') { + // Validate Rock Ridge NM entry length + // sysarea[2] is total entry length, must be >= 5 (header) and within sysarea bounds + if (sysarea[2] >= 5 && sysarea[2] <= sysarea_len) { + rrnamelen = sysarea[2] - 5; + nm_entry = sysarea; + } + break; + } + // Prevent infinite loop from zero-length entry + if (sysarea[2] == 0) { + break; + } + sysarea_len -= sysarea[2]; + sysarea += sysarea[2]; + } + + size_t name_len = 0; + if (rrnamelen > 0 && nm_entry != NULL) { + /* rock ridge naming scheme */ + name_len = rrnamelen; + if (name_len >= limit) { + panic(false, "iso9660: Filename size exceeded"); + } + memcpy(buf, nm_entry + 5, name_len); + buf[name_len] = 0; + return true; + } + +use_iso_name: + name_len = entry->filename_size; + if (name_len >= limit) { + panic(false, "iso9660: Filename size exceeded"); + } + // Validate that entry->length can actually hold the filename + // entry->length must be >= sizeof(struct) + filename_size for safe access + if (entry->length < sizeof(struct iso9660_directory_entry) + name_len) { + // Corrupted entry: claimed filename_size exceeds actual entry data + // Clamp name_len to what's actually available + if (entry->length <= sizeof(struct iso9660_directory_entry)) { + name_len = 0; + } else { + name_len = entry->length - sizeof(struct iso9660_directory_entry); + } + } + size_t j; + for (j = 0; j < name_len; j++) { + if (entry->name[j] == ';') + break; + if (entry->name[j] == '.' && j + 1 < name_len && entry->name[j+1] == ';') + break; + buf[j] = entry->name[j]; + } + buf[j] = 0; + return false; +} + +// Advance to the next directory entry in the buffer +// Returns NULL if no more entries or invalid entry +static struct iso9660_directory_entry *iso9660_next_entry(void *current, void *buffer_end) { + struct iso9660_directory_entry *entry = current; + + if (entry->length == 0) { + // Skip to next sector boundary + uintptr_t current_addr = (uintptr_t)current; + uintptr_t next_sector = ALIGN_UP(current_addr + 1, ISO9660_SECTOR_SIZE); + if (next_sector >= (uintptr_t)buffer_end) + return NULL; + entry = (struct iso9660_directory_entry *)next_sector; + if (entry->length == 0) + return NULL; + return entry; + } + + void *next = (uint8_t *)current + entry->length; + if (next >= buffer_end) + return NULL; + + entry = next; + + // Handle zero-length entries (padding at sector boundaries) + if (entry->length == 0) { + uintptr_t next_sector = ALIGN_UP((uintptr_t)next + 1, ISO9660_SECTOR_SIZE); + if (next_sector >= (uintptr_t)buffer_end) + return NULL; + entry = (struct iso9660_directory_entry *)next_sector; + if (entry->length == 0) + return NULL; + } + + // Validate minimum entry size + if (entry->length < sizeof(struct iso9660_directory_entry)) + return NULL; + + return entry; +} + +static struct iso9660_directory_entry *iso9660_find(void *buffer, uint32_t size, const char *filename) { + while (size) { + struct iso9660_directory_entry *entry = buffer; + + if (entry->length == 0) { + if (size <= ISO9660_SECTOR_SIZE) + return NULL; + size_t prev_size = size; + size = ALIGN_DOWN(size, ISO9660_SECTOR_SIZE); + // If size didn't change (was already aligned), force move to next sector + if (prev_size == size) { + if (size <= ISO9660_SECTOR_SIZE) + return NULL; + size -= ISO9660_SECTOR_SIZE; + buffer += ISO9660_SECTOR_SIZE; + } else { + buffer += prev_size - size; + } + continue; + } + + // Validate entry->length doesn't exceed remaining buffer + if (entry->length > size) { + return NULL; // Corrupted directory entry + } + + // Minimum valid directory entry size + if (entry->length < sizeof(struct iso9660_directory_entry)) { + return NULL; // Corrupted directory entry + } + + char entry_filename[256]; + bool rr = load_name(entry_filename, 256, entry); + + if (rr && !case_insensitive_fopen) { + if (strcmp(filename, entry_filename) == 0) { + return buffer; + } + } else { + if (strcasecmp(filename, entry_filename) == 0) { + return buffer; + } + } + + size -= entry->length; + buffer += entry->length; + } + + return NULL; +} + +static void iso9660_read(struct file_handle *handle, void *buf, uint64_t loc, uint64_t count); +static void iso9660_close(struct file_handle *file); + +struct file_handle *iso9660_open(struct volume *vol, const char *path) { + char buf[6]; + const uint64_t signature = ISO9660_FIRST_VOLUME_DESCRIPTOR * ISO9660_SECTOR_SIZE + 1; + if (!volume_read(vol, buf, signature, 5)) { + return NULL; + } + buf[5] = '\0'; + if (strcmp(buf, "CD001") != 0) { + return NULL; + } + + struct iso9660_file_handle *ret = ext_mem_alloc(sizeof(struct iso9660_file_handle)); + + ret->context = iso9660_get_context(vol); + + while (*path == '/') + ++path; + + struct iso9660_directory_entry *current = ret->context->root; + uint32_t current_size = ret->context->root_size; + + bool first = true; + + uint32_t next_sector = 0; + uint32_t next_size = 0; + + char filename[ROCK_RIDGE_MAX_FILENAME]; + while (true) { + // Skip any consecutive slashes + while (*path == '/') { + path++; + } + + // Check if we've reached the end of the path (handles trailing slashes) + if (*path == '\0') { + // Use the current directory's extent info + // For root, this was set from ret->context->root + // For subdirs, it was set from the last matched entry + if (!first) { + pmm_free(current, current_size); + } + pmm_free(ret, sizeof(struct iso9660_file_handle)); + return NULL; + } + + char *aux = filename; + char *aux_end = filename + ROCK_RIDGE_MAX_FILENAME - 1; + while (!(*path == '/' || *path == '\0')) { + if (aux >= aux_end) { + panic(false, "iso9660: Path component exceeds maximum length"); + } + *aux++ = *path++; + } + *aux = '\0'; + + struct iso9660_directory_entry *entry = iso9660_find(current, current_size, filename); + if (!entry) { + if (!first) { + pmm_free(current, current_size); + } + pmm_free(ret, sizeof(struct iso9660_file_handle)); + return NULL; // Not found :( + } + + next_sector = entry->extent.little; + next_size = entry->extent_size.little; + + if (*path == '\0') { + // Found the file - collect all extents for multi-extent files + void *buffer_end = (uint8_t *)current + current_size; + + // First pass: count extents and calculate total size + uint32_t extent_count = 1; + uint64_t total_size = entry->extent_size.little; + struct iso9660_directory_entry *e = entry; + + while (e->flags & ISO9660_FLAG_MULTI_EXTENT) { + struct iso9660_directory_entry *next = iso9660_next_entry(e, buffer_end); + if (next == NULL) + break; + e = next; + extent_count++; + total_size += e->extent_size.little; + + // Sanity check to prevent runaway on corrupted directories + if (extent_count >= ISO9660_MAX_EXTENT_COUNT) { + break; + } + } + + // Allocate extent array + ret->extents = ext_mem_alloc(extent_count * sizeof(struct iso9660_extent)); + ret->extent_count = extent_count; + ret->total_size = total_size; + + // Second pass: populate extent array + e = entry; + for (uint32_t i = 0; i < extent_count; i++) { + ret->extents[i].LBA = e->extent.little; + ret->extents[i].size = e->extent_size.little; + if (i + 1 < extent_count) { + struct iso9660_directory_entry *next = iso9660_next_entry(e, buffer_end); + if (next == NULL) + break; + e = next; + } + } + + // Free the directory buffer if we allocated one + if (!first) { + pmm_free(current, current_size); + } + + goto setup_handle; + } + + path++; // Skip the '/' separator + + if (!first) { + pmm_free(current, current_size); + } + + // Validate directory size to prevent memory exhaustion + if (next_size == 0 || next_size > ISO9660_MAX_DIR_SIZE) { + pmm_free(ret, sizeof(struct iso9660_file_handle)); + return NULL; + } + + current_size = next_size; + current = ext_mem_alloc(current_size); + + first = false; + + uint64_t dir_offset = (uint64_t)next_sector * ISO9660_SECTOR_SIZE; + if (!volume_read(vol, current, dir_offset, current_size)) { + pmm_free(current, current_size); + pmm_free(ret, sizeof(struct iso9660_file_handle)); + return NULL; + } + } + +setup_handle:; + struct file_handle *handle = ext_mem_alloc(sizeof(struct file_handle)); + + handle->fd = ret; + handle->read = (void *)iso9660_read; + handle->close = (void *)iso9660_close; + handle->size = ret->total_size; + handle->vol = vol; +#if defined (UEFI) + handle->efi_part_handle = vol->efi_part_handle; +#endif + + return handle; +} + +static void iso9660_read(struct file_handle *file, void *buf, uint64_t loc, uint64_t count) { + struct iso9660_file_handle *f = file->fd; + + // Find which extent 'loc' falls into and read across extents as needed + uint64_t extent_start = 0; + for (uint32_t i = 0; i < f->extent_count && count > 0; i++) { + uint64_t extent_size = f->extents[i].size; + uint64_t extent_end = extent_start + extent_size; + + if (loc < extent_end) { + // Read starts (or continues) in this extent + uint64_t offset_in_extent = (loc > extent_start) ? (loc - extent_start) : 0; + uint64_t bytes_available = extent_size - offset_in_extent; + uint64_t to_read = (count < bytes_available) ? count : bytes_available; + + uint64_t disk_offset = (uint64_t)f->extents[i].LBA * ISO9660_SECTOR_SIZE + offset_in_extent; + + if (!volume_read(f->context->vol, buf, disk_offset, to_read)) { + panic(false, "iso9660: failed to read file data"); + } + + buf = (uint8_t *)buf + to_read; + loc += to_read; + count -= to_read; + } + + extent_start = extent_end; + } + + if (count > 0) { + panic(false, "iso9660: read beyond end of file"); + } +} + +static void iso9660_close(struct file_handle *file) { + struct iso9660_file_handle *f = file->fd; + pmm_free(f->extents, f->extent_count * sizeof(struct iso9660_extent)); + pmm_free(f, sizeof(struct iso9660_file_handle)); +} diff --git a/limine/common/gensyms.sh b/limine/common/gensyms.sh new file mode 100755 index 0000000..e79bec7 --- /dev/null +++ b/limine/common/gensyms.sh @@ -0,0 +1,51 @@ +#! /bin/sh + +set -e + +LC_ALL=C +export LC_ALL + +TMP0="$(mktemp)" + +cat >"$TMP0" </dev/null +EOF + +chmod +x "$TMP0" + +"$TMP0" && set -o pipefail + +rm "$TMP0" + +TMP1="$(mktemp)" +TMP2="$(mktemp)" +TMP3="$(mktemp)" +TMP4="$(mktemp)" + +trap 'rm -f "$TMP1" "$TMP2" "$TMP3" "$TMP4"' EXIT + +"$OBJDUMP_FOR_TARGET" -t "$1" | ( "$SED" '/[[:<:]]d[[:>:]]/d' 2>/dev/null || "$SED" '/\bd\b/d' ) | sort > "$TMP1" +"$GREP" "F $4" < "$TMP1" | cut -d' ' -f1 > "$TMP2" +"$GREP" "F $4" < "$TMP1" | "$AWK" 'NF{ print $NF }' > "$TMP3" + +echo ".section .$2_map" > "$TMP4" +echo ".globl $2_map" >> "$TMP4" +echo "$2_map:" >> "$TMP4" + +if [ "$3" = "32" ]; then + paste -d'#' "$TMP2" "$TMP3" | "$SED" 's/^/.long 0x/g;s/$/"/g;s/#/\ +.asciz "/g' >> "$TMP4" + echo ".long 0xffffffff" >> "$TMP4" +elif [ "$3" = "64" ]; then + paste -d'#' "$TMP2" "$TMP3" | "$SED" 's/^/.quad 0x/g;s/$/"/g;s/#/\ +.asciz "/g' >> "$TMP4" + echo ".quad 0xffffffffffffffff" >> "$TMP4" +fi + +echo '.section .note.GNU-stack,"",%progbits' >> "$TMP4" + +mv "$TMP4" "$2.map.S" diff --git a/limine/common/lib/acpi.c b/limine/common/lib/acpi.c new file mode 100644 index 0000000..32e9c65 --- /dev/null +++ b/limine/common/lib/acpi.c @@ -0,0 +1,475 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +// Following function based on https://github.com/managarm/lai/blob/master/helpers/pc-bios.c's function lai_bios_calc_checksum() +uint8_t acpi_checksum(void *ptr, size_t size) { + uint8_t sum = 0, *_ptr = ptr; + for (size_t i = 0; i < size; i++) + sum += _ptr[i]; + return sum; +} + +#if defined (BIOS) + +void *acpi_get_rsdp(void) { + size_t ebda = EBDA; + + for (size_t i = ebda; i < 0x100000; i += 16) { + if (i == ebda + 1024) { + // We probed the 1st KiB of the EBDA as per spec, move onto 0xe0000 + i = 0xe0000; + } + if (!memcmp((char *)i, "RSD PTR ", 8) + && !acpi_checksum((void *)i, 20)) { + printv("acpi: Found RSDP at %p\n", i); + return (void *)i; + } + } + + return NULL; +} + +/// Returns the RSDP v1 pointer if available or else NULL. +void *acpi_get_rsdp_v1(void) { + // In BIOS according to the ACPI spec (see ACPI 6.2 section + // 5.2.5.1 'Finding the RSDP on IA-PC Systems') it either contains + // the RSDP or the XSDP and it cannot contain both. So, we directly + // use acpi_get_rsdp function to find the RSDP and if it has the correct + // revision, return it. + struct rsdp *rsdp = acpi_get_rsdp(); + + if (rsdp != NULL && rsdp->rev < 2) + return rsdp; + + return NULL; +} + +void acpi_get_smbios(void **smbios32, void **smbios64) { + *smbios32 = NULL; + *smbios64 = NULL; + + for (size_t i = 0xf0000; i < 0x100000; i += 16) { + struct smbios_entry_point_32 *ptr = (struct smbios_entry_point_32 *)i; + + if (!memcmp(ptr->anchor_str, "_SM_", 4) && + !acpi_checksum((void *)ptr, ptr->length)) { + printv("acpi: Found SMBIOS 32-bit entry point at %p\n", i); + *smbios32 = (void *)ptr; + break; + } + } + + for (size_t i = 0xf0000; i < 0x100000; i += 16) { + struct smbios_entry_point_64 *ptr = (struct smbios_entry_point_64 *)i; + + if (!memcmp(ptr->anchor_str, "_SM3_", 5) && + !acpi_checksum((void *)ptr, ptr->length)) { + printv("acpi: Found SMBIOS 64-bit entry point at %p\n", i); + *smbios64 = (void *)ptr; + break; + } + } +} + +#endif + +#if defined (UEFI) + +#include + +void *acpi_get_rsdp(void) { + EFI_GUID acpi_2_guid = ACPI_20_TABLE_GUID; + EFI_GUID acpi_1_guid = ACPI_TABLE_GUID; + + void *rsdp = NULL; + + for (size_t i = 0; i < gST->NumberOfTableEntries; i++) { + EFI_CONFIGURATION_TABLE *cur_table = &gST->ConfigurationTable[i]; + + bool is_xsdp = memcmp(&cur_table->VendorGuid, &acpi_2_guid, sizeof(EFI_GUID)) == 0; + bool is_rsdp = memcmp(&cur_table->VendorGuid, &acpi_1_guid, sizeof(EFI_GUID)) == 0; + + if (!is_xsdp && !is_rsdp) + continue; + + if ((is_xsdp && acpi_checksum(cur_table->VendorTable, sizeof(struct rsdp)) != 0) || // XSDP is 36 bytes wide + (is_rsdp && acpi_checksum(cur_table->VendorTable, 20) != 0)) // RSDP is 20 bytes wide + continue; + + printv("acpi: Found %s at %p\n", is_xsdp ? "XSDP" : "RSDP", cur_table->VendorTable); + + // We want to return the XSDP if it exists rather then returning + // the RSDP. We need to add a check for that since the table entries + // are not in the same order for all EFI systems since it might be the + // case where the RSDP occurs before the XSDP. + if (is_xsdp) { + rsdp = (void *)cur_table->VendorTable; + break; // Found it!. + } else { + // Found the RSDP but we continue to loop since we might + // find the XSDP. + rsdp = (void *)cur_table->VendorTable; + } + } + + return rsdp; +} + +/// Returns the RSDP v1 pointer if available or else NULL. +void *acpi_get_rsdp_v1(void) { + // To maintain GRUB compatibility we will need to probe for the RSDP + // again since UEFI can contain both XSDP and RSDP (see ACPI 6.2 section + // 5.2.5.2 'Finding the RSDP on UEFI Enabled Systems') and in the acpi_get_rsdp + // function we look for the RSDP with the latest revision. + EFI_GUID acpi_1_guid = ACPI_TABLE_GUID; + + for (size_t i = 0; i < gST->NumberOfTableEntries; i++) { + EFI_CONFIGURATION_TABLE *cur_table = &gST->ConfigurationTable[i]; + + if (memcmp(&cur_table->VendorGuid, &acpi_1_guid, sizeof(EFI_GUID)) != 0) + continue; + + if (acpi_checksum(cur_table->VendorTable, 20) != 0) + continue; + + return (void *)cur_table->VendorTable; + } + + return NULL; +} + +void acpi_get_smbios(void **smbios32, void **smbios64) { + *smbios32 = NULL; + *smbios64 = NULL; + + for (size_t i = 0; i < gST->NumberOfTableEntries; i++) { + EFI_CONFIGURATION_TABLE *cur_table = &gST->ConfigurationTable[i]; + EFI_GUID smbios_guid = SMBIOS_TABLE_GUID; + + if (memcmp(&cur_table->VendorGuid, &smbios_guid, sizeof(EFI_GUID)) != 0) + continue; + + struct smbios_entry_point_32 *ptr = (struct smbios_entry_point_32 *)cur_table->VendorTable; + + if (acpi_checksum((void *)ptr, ptr->length) != 0) + continue; + + printv("acpi: Found SMBIOS 32-bit entry point at %p\n", ptr); + + *smbios32 = (void *)ptr; + + break; + } + + for (size_t i = 0; i < gST->NumberOfTableEntries; i++) { + EFI_CONFIGURATION_TABLE *cur_table = &gST->ConfigurationTable[i]; + EFI_GUID smbios3_guid = SMBIOS3_TABLE_GUID; + + if (memcmp(&cur_table->VendorGuid, &smbios3_guid, sizeof(EFI_GUID)) != 0) + continue; + + struct smbios_entry_point_64 *ptr = (struct smbios_entry_point_64 *)cur_table->VendorTable; + + if (acpi_checksum((void *)ptr, ptr->length) != 0) + continue; + + printv("acpi: Found SMBIOS 64-bit entry point at %p\n", ptr); + + *smbios64 = (void *)ptr; + + break; + } +} + +#endif + +/// Returns the RSDP v2 pointer if available or else NULL. +void *acpi_get_rsdp_v2(void) { + // Since the acpi_get_rsdp function already looks for the XSDP we can + // just check if it has the correct revision and return the pointer :^) + struct rsdp *rsdp = acpi_get_rsdp(); + + if (rsdp != NULL && rsdp->rev >= 2) + return rsdp; + + return NULL; +} + +void *acpi_get_table(const char *signature, int index) { + int cnt = 0; + + struct rsdp *rsdp = acpi_get_rsdp(); + if (rsdp == NULL) + return NULL; + + bool use_xsdt = false; + if (rsdp->rev >= 2 && rsdp->xsdt_addr + && (sizeof(uintptr_t) >= 8 || rsdp->xsdt_addr <= UINT32_MAX)) + use_xsdt = true; + + struct rsdt *rsdt; + if (use_xsdt) + rsdt = (struct rsdt *)(uintptr_t)rsdp->xsdt_addr; + else + rsdt = (struct rsdt *)(uintptr_t)rsdp->rsdt_addr; + + if (rsdt == NULL) { + return NULL; + } + + // Validate RSDT/XSDT header length + if (rsdt->header.length < sizeof(struct sdt)) { + printv("acpi: Invalid %s header length\n", use_xsdt ? "XSDT" : "RSDT"); + return NULL; + } + + size_t entry_size = use_xsdt ? 8 : 4; + size_t entry_count = (rsdt->header.length - sizeof(struct sdt)) / entry_size; + + for (size_t i = 0; i < entry_count; i++) { + struct sdt *ptr; + if (use_xsdt) + ptr = (struct sdt *)(uintptr_t)((uint64_t *)rsdt->ptrs_start)[i]; + else + ptr = (struct sdt *)(uintptr_t)((uint32_t *)rsdt->ptrs_start)[i]; + + if (ptr == NULL) { + continue; + } + + if (!memcmp(ptr->signature, signature, 4) + && !acpi_checksum(ptr, ptr->length) + && cnt++ == index) { + printv("acpi: Found \"%s\" at %p\n", signature, ptr); + return ptr; + } + } + + printv("acpi: \"%s\" not found\n", signature); + return NULL; +} + +static bool acpi_padding_is_safe(uint64_t base, uint64_t length) { + if (length == 0) { + return true; + } + + uint64_t top = base + length; + + for (size_t i = 0; i < memmap_entries; i++) { + uint64_t entry_base = memmap[i].base; + uint64_t entry_top = entry_base + memmap[i].length; + + if (entry_base >= top || entry_top <= base) { + continue; + } + + if (memmap[i].type != MEMMAP_USABLE && memmap[i].type != MEMMAP_RESERVED) { + return false; + } + } + + return true; +} + +static void map_single_table(uint64_t addr, uint32_t len) { +#if defined (__i386__) + if (addr >= 0x100000000) { + print("acpi: warning: Cannot get length of ACPI table above 4GiB\n"); + return; + } +#endif + + uint32_t length = len != (uint32_t)-1 ? len : *(uint32_t *)(uintptr_t)(addr + 4); + + uint64_t aligned_base = ALIGN_DOWN(addr, 4096); + uint64_t aligned_top = ALIGN_UP(addr + length, 4096); + + if (!acpi_padding_is_safe(aligned_base, addr - aligned_base)) { + aligned_base = addr; + } + if (!acpi_padding_is_safe(addr + length, aligned_top - (addr + length))) { + aligned_top = addr + length; + } + + uint64_t memmap_type = pmm_check_type(addr); + + if (memmap_type != MEMMAP_ACPI_RECLAIMABLE && memmap_type != MEMMAP_ACPI_NVS) { + memmap_alloc_range(aligned_base, aligned_top - aligned_base, MEMMAP_RESERVED_MAPPED, 0, true, false, true); + } +} + + +void acpi_map_tables(void) { + struct rsdp *rsdp = acpi_get_rsdp(); + if (rsdp == NULL) + return; + + uint64_t rsdp_length; + if (rsdp->rev < 2) { + rsdp_length = 20; + } else { + rsdp_length = rsdp->length; + } + + map_single_table((uintptr_t)rsdp, rsdp_length); + + if (!(rsdp->rev >= 2 && rsdp->xsdt_addr)) { + goto no_xsdt; + } + + struct rsdt *xsdt = (void *)(uintptr_t)rsdp->xsdt_addr; + if (xsdt->header.length < sizeof(struct sdt)) { + goto no_xsdt; + } + size_t xsdt_entry_count = (xsdt->header.length - sizeof(struct sdt)) / 8; + + map_single_table((uintptr_t)xsdt, (uint32_t)-1); + + for (size_t i = 0; i < xsdt_entry_count; i++) { + uint64_t entry = ((uint64_t *)xsdt->ptrs_start)[i]; + if (entry == 0) + continue; + struct sdt *sdt = (void *)(uintptr_t)entry; + + map_single_table((uintptr_t)sdt, (uint32_t)-1); + } + +no_xsdt:; + if (rsdp->rsdt_addr == 0) { + goto no_rsdt; + } + + struct rsdt *rsdt = (void *)(uintptr_t)rsdp->rsdt_addr; + if (rsdt->header.length < sizeof(struct sdt)) { + goto no_rsdt; + } + size_t rsdt_entry_count = (rsdt->header.length - sizeof(struct sdt)) / 4; + + map_single_table((uintptr_t)rsdt, (uint32_t)-1); + + for (size_t i = 0; i < rsdt_entry_count; i++) { + uint32_t entry = ((uint32_t *)rsdt->ptrs_start)[i]; + if (entry == 0) + continue; + struct sdt *sdt = (void *)(uintptr_t)entry; + + map_single_table((uintptr_t)sdt, (uint32_t)-1); + } + +no_rsdt:; + uint8_t *fadt = acpi_get_table("FACP", 0); + if (fadt == NULL) { + return; + } + uint32_t fadt_length; + memcpy(&fadt_length, fadt + 4, sizeof(fadt_length)); + + // Read the single fields from the FADT without defining a struct for the whole table + if (fadt_length >= 132 + 8) { + uint64_t x_facs; + memcpy(&x_facs, fadt + 132, sizeof(x_facs)); + if (x_facs != 0) { + map_single_table(x_facs, (uint32_t)-1); + } + } + if (fadt_length >= 140 + 8) { + uint64_t x_dsdt; + memcpy(&x_dsdt, fadt + 140, sizeof(x_dsdt)); + if (x_dsdt != 0) { + map_single_table(x_dsdt, (uint32_t)-1); + } + } + if (fadt_length >= 36 + 4) { + uint32_t facs; + memcpy(&facs, fadt + 36, sizeof(facs)); + if (facs != 0) { + map_single_table(facs, (uint32_t)-1); + } + } + if (fadt_length >= 40 + 4) { + uint32_t dsdt; + memcpy(&dsdt, fadt + 40, sizeof(dsdt)); + if (dsdt != 0) { + map_single_table(dsdt, (uint32_t)-1); + } + } +} + +void smbios_map_tables(void) { + void *smbios32_ptr = NULL, *smbios64_ptr = NULL; + acpi_get_smbios(&smbios32_ptr, &smbios64_ptr); + + if (smbios32_ptr != NULL) { + struct smbios_entry_point_32 *smbios32 = smbios32_ptr; + map_single_table((uintptr_t)smbios32, smbios32->length); + if (smbios32->table_address != 0) { + map_single_table(smbios32->table_address, smbios32->table_length); + } + } + + if (smbios64_ptr != NULL) { + struct smbios_entry_point_64 *smbios64 = smbios64_ptr; + map_single_table((uintptr_t)smbios64, smbios64->length); + if (smbios64->table_address != 0) { + map_single_table(smbios64->table_address, smbios64->table_maximum_size); + } + } +} + +#if defined (UEFI) +void efi_map_runtime_entries(void) { + size_t entry_count = efi_mmap_size / efi_desc_size; + + for (size_t i = 0; i < entry_count; i++) { + EFI_MEMORY_DESCRIPTOR *entry = (void *)efi_mmap + i * efi_desc_size; + + if (entry->Type != EfiRuntimeServicesCode + && entry->Type != EfiRuntimeServicesData) { + continue; + } + + uint64_t base = entry->PhysicalStart; + uint64_t length; + if (__builtin_mul_overflow(entry->NumberOfPages, (uint64_t)4096, &length)) { + continue; + } + + memmap_alloc_range(base, length, MEMMAP_RESERVED_MAPPED, 0, true, false, true); + } + + // Explicitly map the EFI system table and the data it references. + // The UEFI spec does not guarantee these reside in EfiRuntimeServicesData, + // so we map them separately to ensure they are always accessible via HHDM. + map_single_table((uintptr_t)gST, sizeof(*gST)); + + if (gST->RuntimeServices != NULL) { + map_single_table((uintptr_t)gST->RuntimeServices, + sizeof(*gST->RuntimeServices)); + } + + if (gST->ConfigurationTable != NULL && gST->NumberOfTableEntries > 0) { + uint64_t ct_size; + if (!__builtin_mul_overflow(gST->NumberOfTableEntries, + (uint64_t)sizeof(EFI_CONFIGURATION_TABLE), &ct_size) + && ct_size <= UINT32_MAX) { + map_single_table((uintptr_t)gST->ConfigurationTable, (uint32_t)ct_size); + } + } + + if (gST->FirmwareVendor != NULL) { + size_t len = 0; + while (gST->FirmwareVendor[len] != 0) { + len++; + } + map_single_table((uintptr_t)gST->FirmwareVendor, + (len + 1) * sizeof(*gST->FirmwareVendor)); + } +} +#endif diff --git a/limine/common/lib/acpi.h b/limine/common/lib/acpi.h new file mode 100644 index 0000000..89dd158 --- /dev/null +++ b/limine/common/lib/acpi.h @@ -0,0 +1,205 @@ +#ifndef LIB__ACPI_H__ +#define LIB__ACPI_H__ + +#include +#include +#include + +#define EBDA (ebda_get()) + +#if defined (BIOS) +static inline uintptr_t ebda_get(void) { + uintptr_t ebda = (uintptr_t)mminw(0x40e) << 4; + + // Sanity checks + if (ebda < 0x80000 || ebda >= 0xa0000) { + ebda = 0x80000; + } + + return ebda; +} +#endif + +struct sdt { + char signature[4]; + uint32_t length; + uint8_t rev; + uint8_t checksum; + char oem_id[6]; + char oem_table_id[8]; + uint32_t oem_rev; + char creator_id[4]; + uint32_t creator_rev; +} __attribute__((packed)); + +struct rsdp { + char signature[8]; + uint8_t checksum; + char oem_id[6]; + uint8_t rev; + uint32_t rsdt_addr; + // Revision 2 only after this comment + uint32_t length; + uint64_t xsdt_addr; + uint8_t ext_checksum; + uint8_t reserved[3]; +} __attribute__((packed)); + +struct rsdt { + struct sdt header; + char ptrs_start[]; +} __attribute__((packed)); + +struct smbios_entry_point_32 { + char anchor_str[4]; + /// This value summed with all the values of the table. + uint8_t checksum; + /// Length of the entry point table. + uint8_t length; + /// Major version of SMBIOS. + uint8_t major_version; + /// Minor version of SMBIOS. + uint8_t minor_version; + /// Size of the largest SMBIOS structure, in bytes, and encompasses the + /// structure’s formatted area and text strings + uint16_t max_structure_size; + uint8_t entry_point_revision; + char formatted_area[5]; + + char intermediate_anchor_str[5]; + /// Checksum for values from intermediate anchor str to the + /// end of table. + uint8_t intermediate_checksum; + /// Total length of SMBIOS Structure Table, pointed to by the structure + /// table address, in bytes. + uint16_t table_length; + /// 32-bit physical starting address of the read-only SMBIOS Structure + /// Table. + uint32_t table_address; + /// Total number of structures present in the SMBIOS Structure Table. + uint16_t number_of_structures; + /// Indicates compliance with a revision of this specification. + uint8_t bcd_revision; +} __attribute__((packed)); + +struct smbios_entry_point_64 { + char anchor_str[5]; + /// This value summed with all the values of the table. + uint8_t checksum; + /// Length of the entry point table. + uint8_t length; + /// Major version of SMBIOS. + uint8_t major_version; + /// Minor version of SMBIOS. + uint8_t minor_version; + uint8_t docrev; + uint8_t entry_point_revision; + uint8_t reserved; + /// Size of the SMBIOS Structure Table, in bytes. + uint32_t table_maximum_size; + /// 64-bit physical starting address of the read-only SMBIOS Structure + /// Table. + uint64_t table_address; +} __attribute__((packed)); + +struct madt { + struct sdt header; + uint32_t local_controller_addr; + uint32_t flags; + char madt_entries_begin[]; +} __attribute__((packed)); + +struct madt_header { + uint8_t type; + uint8_t length; +} __attribute__((packed)); + +struct madt_lapic { + struct madt_header header; + uint8_t acpi_processor_uid; + uint8_t lapic_id; + uint32_t flags; +} __attribute__((packed)); + +struct madt_x2apic { + struct madt_header header; + uint8_t reserved[2]; + uint32_t x2apic_id; + uint32_t flags; + uint32_t acpi_processor_uid; +} __attribute__((packed)); + +struct madt_io_apic { + uint8_t type; + uint8_t length; + uint8_t apic_id; + uint8_t reserved; + uint32_t address; + uint32_t gsib; +} __attribute__((packed)); + +struct madt_lapic_nmi { + struct madt_header header; // type=4, length=6 + uint8_t acpi_processor_uid; // 0xff = all processors + uint16_t flags; // MPS INTI flags + uint8_t lint; // 0 or 1 +} __attribute__((packed)); + +struct madt_x2apic_nmi { + struct madt_header header; // type=0x0a, length=12 + uint16_t flags; // MPS INTI flags + uint32_t acpi_processor_uid; // 0xffffffff = all processors + uint8_t lint; // 0 or 1 + uint8_t reserved[3]; +} __attribute__((packed)); + +struct madt_gicc { + struct madt_header header; + uint8_t reserved1[2]; + uint32_t iface_no; + uint32_t acpi_uid; + uint32_t flags; + uint32_t parking_ver; + uint32_t perf_gsiv; + uint64_t parking_addr; + uint64_t gicc_base_addr; + uint64_t gicv_base_addr; + uint64_t gich_base_addr; + uint32_t vgic_maint_gsiv; + uint64_t gicr_base_addr; + uint64_t mpidr; + uint8_t power_eff_class; + uint8_t reserved2; + uint16_t spe_overflow_gsiv; +} __attribute__((packed)); + +// Reference: https://github.com/riscv-non-isa/riscv-acpi/issues/15 +struct madt_riscv_intc { + struct madt_header header; + uint8_t version; + uint8_t reserved; + uint32_t flags; + uint64_t hartid; + uint32_t acpi_processor_uid; +} __attribute__((packed)); + +#define MADT_RISCV_INTC_ENABLED ((uint32_t)1 << 0) +#define MADT_RISCV_INTC_ONLINE_CAPABLE ((uint32_t)1 << 1) + +uint8_t acpi_checksum(void *ptr, size_t size); +void *acpi_get_rsdp(void); + +void *acpi_get_rsdp_v1(void); +void *acpi_get_rsdp_v2(void); + +void *acpi_get_table(const char *signature, int index); +void acpi_get_smbios(void **smbios32, void **smbios64); + +void acpi_map_tables(void); +void smbios_map_tables(void); + +#if defined (UEFI) +void efi_map_runtime_entries(void); +#endif + +#endif diff --git a/limine/common/lib/bli.c b/limine/common/lib/bli.c new file mode 100644 index 0000000..4aee0f2 --- /dev/null +++ b/limine/common/lib/bli.c @@ -0,0 +1,90 @@ +#if defined (UEFI) + +#include +#include +#include +#include +#include +#include +#include +#include + +#define LIMINE_BRAND L"Limine " LIMINE_VERSION + +static EFI_GUID bli_vendor_guid = { 0x4a67b082, 0x0a4c, 0x41cf, { 0xb6, 0xc7, 0x44, 0x0b, 0x29, 0xbb, 0x8c, 0x4f } }; + +// The buffer must be at least 21 bytes long +void uint64_to_decwstr(uint64_t value, wchar_t *buf) { + wchar_t tmp[21]; + size_t i = 0; + + if (buf == NULL) { + return; + } + + if (value == 0) { + buf[0] = '0'; + buf[1] = '\0'; + return; + } + + // Convert digits in reverse order + while (value > 0) { + tmp[i++] = '0' + (value % 10); + value /= 10; + } + + // Reverse the string into the buffer + for (size_t j = 0; j < i; j++) { + buf[j] = tmp[i - j - 1]; + } + buf[i] = '\0'; +} + +void bli_set_loader_time(wchar_t *variable, uint64_t time) { + if (time == 0) + return; + + wchar_t time_wstr[21]; + uint64_to_decwstr(time, time_wstr); + + size_t len = 0; + while (time_wstr[len] != L'\0') len++; + + gRT->SetVariable(variable, + &bli_vendor_guid, + EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS, + (len + 1) * sizeof(wchar_t), + time_wstr); +} + +void init_bli(void) { + bli_set_loader_time(L"LoaderTimeInitUSec", usec_at_bootloader_entry); + + gRT->SetVariable(L"LoaderInfo", + &bli_vendor_guid, + EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS, + sizeof(LIMINE_BRAND), + LIMINE_BRAND); + + char part_uuid_str[37]; + guid_to_string(&boot_volume->part_guid, part_uuid_str); + + // Convert part_uuid_str to a wide-char string + wchar_t part_uuid[37]; + for (size_t i = 0; i < 37; i++) { + part_uuid[i] = (wchar_t) part_uuid_str[i]; + } + + gRT->SetVariable(L"LoaderDevicePartUUID", + &bli_vendor_guid, + EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS, + sizeof(part_uuid), + part_uuid); +} + +void bli_on_boot(void) { + bli_set_loader_time(L"LoaderTimeExecUSec", rdtsc_usec()); +} + +#endif diff --git a/limine/common/lib/bli.h b/limine/common/lib/bli.h new file mode 100644 index 0000000..2e48041 --- /dev/null +++ b/limine/common/lib/bli.h @@ -0,0 +1,11 @@ +#ifndef LIB__BLI_H__ +#define LIB__BLI_H__ + +#if defined (UEFI) + +void init_bli(void); +void bli_on_boot(void); + +#endif + +#endif diff --git a/limine/common/lib/config.c b/limine/common/lib/config.c new file mode 100644 index 0000000..2f4c9fe --- /dev/null +++ b/limine/common/lib/config.c @@ -0,0 +1,756 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CONFIG_B2SUM_SIGNATURE "++CONFIG_B2SUM_SIGNATURE++" +#define CONFIG_B2SUM_EMPTY "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + +const char *config_b2sum = CONFIG_B2SUM_SIGNATURE CONFIG_B2SUM_EMPTY; + +static bool config_get_entry_name(char *ret, size_t index, size_t limit); +static char *config_get_entry(size_t *size, size_t index); + +#define SEPARATOR '\n' + +bool config_ready = false; +no_unwind bool bad_config = false; + +static char *config_addr; + +#if defined (UEFI) + +#define EFI_APP_PATH_LEN 128 +static char efi_app_path[128] = {0}; + +static bool init_efi_app_path(size_t *len_out) { + EFI_STATUS status; + EFI_LOADED_IMAGE_PROTOCOL *loaded_image; + EFI_DEVICE_PATH_PROTOCOL *path; + CHAR16 *file_path, *p, *last_slash; + + EFI_GUID loaded_image_protocol_guid = EFI_LOADED_IMAGE_PROTOCOL_GUID; + + status = gBS->HandleProtocol(efi_image_handle, &loaded_image_protocol_guid, + (void **)&loaded_image); + if (status != 0) { + return false; + } + + path = loaded_image->FilePath; + + while (!(path->Type == END_DEVICE_PATH_TYPE && path->SubType == END_ENTIRE_DEVICE_PATH_SUBTYPE)) { + if (path->Type == MEDIA_DEVICE_PATH && path->SubType == MEDIA_FILEPATH_DP) { + goto found; + } + + uint16_t node_length = *((uint16_t *)&path->Length[0]); + if (node_length < 4) { + return false; + } + path = (void *)path + node_length; + } + + return false; + +found: + file_path = (CHAR16 *)((void *)path + 4); + + last_slash = NULL; + for (p = file_path; *p; p++) { + if (*p == L'\\') { + last_slash = p; + } + } + + if (last_slash) { + size_t len = (last_slash - file_path) + 1; + if (len >= EFI_APP_PATH_LEN) { + len = EFI_APP_PATH_LEN - 1; + } + + for (size_t i = 0; i < len; i++) { + efi_app_path[i] = (char)(file_path[i] & 0xff); + if (efi_app_path[i] == '\\') { + efi_app_path[i] = '/'; + } + } + efi_app_path[len] = 0; + if (len_out != NULL) { + *len_out = len; + } + } else { + efi_app_path[0] = '/'; + efi_app_path[1] = 0; + if (len_out != NULL) { + *len_out = 1; + } + } + + return true; +} +#endif + +int init_config_disk(struct volume *part) { +#if defined (UEFI) + bool use_default_efi_search_path = false; + + size_t len; + if (!init_efi_app_path(&len)) { + use_default_efi_search_path = true; + } else { + if (len + sizeof("limine.conf") >= EFI_APP_PATH_LEN) { + use_default_efi_search_path = true; + } else { + strcpy(efi_app_path + len, "limine.conf"); + } + } +#endif + + struct file_handle *f; + + bool old_cif = case_insensitive_fopen; + case_insensitive_fopen = true; + if ( + false +#if defined (UEFI) + || (f = fopen(part, use_default_efi_search_path ? "/EFI/BOOT/limine.conf" : efi_app_path)) != NULL +#endif + || (f = fopen(part, "/boot/limine/limine.conf")) != NULL + || (f = fopen(part, "/boot/limine.conf")) != NULL + || (f = fopen(part, "/limine/limine.conf")) != NULL + || (f = fopen(part, "/limine.conf")) != NULL + ) { + goto opened; + } + + case_insensitive_fopen = old_cif; + return -1; + +opened: + case_insensitive_fopen = old_cif; + + size_t config_size = f->size + 2; + config_addr = ext_mem_alloc(config_size); + + fread(f, config_addr, 0, f->size); + + fclose(f); + + return init_config(config_size); +} + +struct smbios_struct_header { + uint8_t type; + uint8_t length; + uint16_t handle; +} __attribute__((packed)); + +static size_t smbios_struct_size(struct smbios_struct_header *hdr, size_t remaining) { + // Validate minimum structure header size + if (remaining < sizeof(struct smbios_struct_header)) { + return 0; + } + if (hdr->length < sizeof(struct smbios_struct_header)) { + return 0; // Invalid structure + } + if (hdr->length > remaining) { + return 0; // Structure header claims more than remaining + } + + const char *string_data = (void *)((uintptr_t)hdr + hdr->length); + size_t string_area_max = remaining - hdr->length; + size_t i = 1; + for (; i < string_area_max && (string_data[i - 1] != '\0' || string_data[i] != '\0'); i++); + if (i >= string_area_max) { + return 0; // Unterminated string area + } + return hdr->length + i + 1; +} + +bool init_config_smbios(void) { + struct smbios_entry_point_32 *smbios_entry_32 = NULL; + struct smbios_entry_point_64 *smbios_entry_64 = NULL; + acpi_get_smbios((void **)&smbios_entry_32, (void **)&smbios_entry_64); + if (smbios_entry_32 == NULL && smbios_entry_64 == NULL) { + return false; + } + + struct smbios_struct_header *hdr = NULL; + size_t struct_count = 0; + size_t table_length = 0; + + if (smbios_entry_64) { + hdr = (void *)(uintptr_t) smbios_entry_64->table_address; + table_length = smbios_entry_64->table_maximum_size; + } else { + hdr = (void *)(uintptr_t) smbios_entry_32->table_address; + struct_count = smbios_entry_32->number_of_structures; + table_length = smbios_entry_32->table_length; + } + + if (hdr == NULL || table_length == 0) { + return false; + } + + size_t structure_bytes_processed = 0; + for (size_t struct_num = 0; hdr && (!struct_count || struct_num < struct_count); struct_num++) { + size_t remaining = table_length - structure_bytes_processed; + if (remaining < sizeof(struct smbios_struct_header)) { + return false; + } + + if (hdr->type == 127) + return false; + + size_t struct_size = smbios_struct_size(hdr, remaining); + if (struct_size == 0) { + return false; // Invalid structure + } + + if (hdr->type == 11 && hdr->length >= sizeof(struct smbios_struct_header)) { + const char *string_data = (void *)((uintptr_t) hdr + hdr->length); + size_t string_area_size = struct_size - hdr->length; + + size_t prefix_len = sizeof("limine:config:") - 1; + if (string_area_size > prefix_len && !strncmp(string_data, "limine:config:", prefix_len)) { + size_t total_len = strnlen(string_data, string_area_size); + if (total_len <= prefix_len) + continue; + size_t config_size = total_len - prefix_len + 2; + config_addr = ext_mem_alloc(config_size); + memcpy(config_addr, &string_data[prefix_len], config_size - 1); + config_addr[config_size - 1] = '\0'; + return !init_config(config_size); + } + } + + structure_bytes_processed += struct_size; + if (structure_bytes_processed >= table_length) { + return false; + } + + hdr = (void *)((uintptr_t) hdr + struct_size); + } + + return false; +} + +#define NOT_CHILD (-1) +#define DIRECT_CHILD 0 +#define INDIRECT_CHILD 1 + +static int is_child(char *buf, size_t limit, + size_t current_depth, size_t index) { + if (!config_get_entry_name(buf, index, limit)) + return NOT_CHILD; + if (strlen(buf) < current_depth + 1) + return NOT_CHILD; + for (size_t j = 0; j < current_depth; j++) + if (buf[j] != '/') + return NOT_CHILD; + if (buf[current_depth] == '/') + return INDIRECT_CHILD; + return DIRECT_CHILD; +} + +static bool is_directory(char *buf, size_t limit, + size_t current_depth, size_t index) { + switch (is_child(buf, limit, current_depth + 1, index + 1)) { + default: + case NOT_CHILD: + return false; + case INDIRECT_CHILD: + bad_config = true; + panic(true, "config: Malformed config file. Parentless child."); + case DIRECT_CHILD: + return true; + } +} + +static struct menu_entry *create_menu_tree(struct menu_entry *parent, + size_t current_depth, size_t index) { + struct menu_entry *root = NULL, *prev = NULL; + + for (size_t i = index; ; i++) { + static char name[64]; + + switch (is_child(name, 64, current_depth, i)) { + case NOT_CHILD: + return root; + case INDIRECT_CHILD: + continue; + case DIRECT_CHILD: + break; + } + + struct menu_entry *entry = ext_mem_alloc(sizeof(struct menu_entry)); + + if (root == NULL) + root = entry; + + config_get_entry_name(name, i, 64); + + bool default_expanded = name[current_depth] == '+'; + + char *n = &name[current_depth + default_expanded]; + while (*n == ' ') { + n++; + } + + size_t n_len = strlen(n); + if (n_len >= sizeof(entry->name)) { + n_len = sizeof(entry->name) - 1; + } + memcpy(entry->name, n, n_len); + entry->name[n_len] = 0; + entry->parent = parent; + + size_t entry_size; + char *config_entry = config_get_entry(&entry_size, i); + entry->body = ext_mem_alloc(entry_size + 1); + memcpy(entry->body, config_entry, entry_size); + entry->body[entry_size] = 0; + + if (is_directory(name, 64, current_depth, i)) { + entry->sub = create_menu_tree(entry, current_depth + 1, i + 1); + entry->expanded = default_expanded; + } + + char *comment = config_get_value(entry->body, 0, "COMMENT"); + if (comment != NULL) { + entry->comment = strdup(comment); + } + + if (prev != NULL) + prev->next = entry; + prev = entry; + } +} + +struct menu_entry *menu_tree = NULL; + +struct macro { + char name[1024]; + char value[2048]; + struct macro *next; +}; + +static struct macro *macros = NULL; + +int init_config(size_t config_size) { + config_b2sum += sizeof(CONFIG_B2SUM_SIGNATURE) - 1; + + if (memcmp((void *)config_b2sum, CONFIG_B2SUM_EMPTY, 128) != 0) { + editor_enabled = false; + + uint8_t out_buf[BLAKE2B_OUT_BYTES]; + blake2b(out_buf, config_addr, config_size - 2); + uint8_t hash_buf[BLAKE2B_OUT_BYTES]; + + for (size_t i = 0; i < BLAKE2B_OUT_BYTES; i++) { + hash_buf[i] = digit_to_int(config_b2sum[i * 2]) << 4 | digit_to_int(config_b2sum[i * 2 + 1]); + } + + if (memcmp(hash_buf, out_buf, BLAKE2B_OUT_BYTES) != 0) { + panic(false, "!!! CHECKSUM MISMATCH FOR CONFIG FILE !!!"); + } + } + + // add trailing newline if not present + config_addr[config_size - 2] = '\n'; + + size_t config_alloc_size = config_size; + + // remove windows carriage returns and spaces at the start and end of lines, if any + for (size_t i = 0; i < config_size; i++) { + size_t skip = 0; + if (config_addr[i] == ' ' || config_addr[i] == '\t') { + while (i + skip < config_size && (config_addr[i + skip] == ' ' || config_addr[i + skip] == '\t')) { + skip++; + } + if (i + skip < config_size && config_addr[i + skip] == '\n') { + goto skip_loop; + } + skip = 0; + } + while (i + skip < config_size + && ((config_addr[i + skip] == '\r') + || ((!i || config_addr[i - 1] == '\n') && (config_addr[i + skip] == ' ' || config_addr[i + skip] == '\t'))) + ) { + skip++; + } +skip_loop: + if (skip) { + for (size_t j = i; j < config_size - skip; j++) + config_addr[j] = config_addr[j + skip]; + config_size -= skip; + } + } + + // Load macros + struct macro *arch_macro = ext_mem_alloc(sizeof(struct macro)); + strcpy(arch_macro->name, "ARCH"); +#if defined (__x86_64__) + strcpy(arch_macro->value, "x86-64"); +#elif defined (__i386__) + { + uint32_t eax, ebx, ecx, edx; + if (!cpuid(0x80000001, 0, &eax, &ebx, &ecx, &edx) || !(edx & (1 << 29))) { + strcpy(arch_macro->value, "ia-32"); + } else { + strcpy(arch_macro->value, "x86-64"); + } + } +#elif defined (__aarch64__) + strcpy(arch_macro->value, "aarch64"); +#elif defined (__riscv) + strcpy(arch_macro->value, "riscv64"); +#elif defined (__loongarch64) + strcpy(arch_macro->value, "loongarch64"); +#else +#error "Unspecified architecture" +#endif + arch_macro->next = macros; + macros = arch_macro; + + struct macro *fw_type_macro = ext_mem_alloc(sizeof(struct macro)); + strcpy(fw_type_macro->name, "FW_TYPE"); +#if defined (UEFI) + strcpy(fw_type_macro->value, "UEFI"); +#else + strcpy(fw_type_macro->value, "BIOS"); +#endif + fw_type_macro->next = macros; + macros = fw_type_macro; + + for (size_t i = 0; i < config_size;) { + if ((config_size - i >= 3 && memcmp(config_addr + i, "\n${", 3) == 0) + || (config_size - i >= 2 && i == 0 && memcmp(config_addr, "${", 2) == 0)) { + struct macro *macro = ext_mem_alloc(sizeof(struct macro)); + + i += i ? 3 : 2; + size_t j; + for (j = 0; config_addr[i] != '}' && config_addr[i] != '\n' && config_addr[i] != 0; j++, i++) { + if (j >= sizeof(macro->name) - 1) { + bad_config = true; + panic(true, "config: Macro name too long (max %U)", (uint64_t)(sizeof(macro->name) - 1)); + } + macro->name[j] = config_addr[i]; + } + + if (config_addr[i] == '\n' || config_addr[i] == 0 || config_addr[i+1] != '=') { + pmm_free(macro, sizeof(struct macro)); + continue; + } + i += 2; + + macro->name[j] = 0; + + for (j = 0; config_addr[i] != '\n' && config_addr[i] != 0; j++, i++) { + if (j >= sizeof(macro->value) - 1) { + bad_config = true; + panic(true, "config: Macro value too long (max %U)", (uint64_t)(sizeof(macro->value) - 1)); + } + macro->value[j] = config_addr[i]; + } + macro->value[j] = 0; + + macro->next = macros; + macros = macro; + + continue; + } + + i++; + } + + // Expand macros + if (macros != NULL) { + // Check for overflow before multiplication + if (config_size > SIZE_MAX / 4) { + bad_config = true; + panic(true, "config: Config file too large for macro expansion"); + } + size_t new_config_size = config_size * 4; + char *new_config = ext_mem_alloc(new_config_size); + + size_t i, in; + for (i = 0, in = 0; i < config_size;) { + if ((config_size - i >= 3 && memcmp(config_addr + i, "\n${", 3) == 0) + || (config_size - i >= 2 && i == 0 && memcmp(config_addr, "${", 2) == 0)) { + size_t orig_i = i; + i += i ? 3 : 2; + while (i < config_size && config_addr[i] != '}') { + i++; + } + if (i >= config_size) { + bad_config = true; + panic(true, "config: Malformed macro usage"); + } + i++; // skip '}' + if (i >= config_size || config_addr[i++] != '=') { + i = orig_i; + goto next; + } + while (config_addr[i] != '\n' && config_addr[i] != 0) { + i++; + if (i >= config_size) { + bad_config = true; + panic(true, "config: Malformed macro usage"); + } + } + continue; + } + +next: + if (config_size - i >= 2 && memcmp(config_addr + i, "${", 2) == 0) { + char *macro_name = ext_mem_alloc(1024); + i += 2; + size_t j; + for (j = 0; j < 1023 && config_addr[i] != '}' && config_addr[i] != '\n' && config_addr[i] != 0; j++, i++) { + macro_name[j] = config_addr[i]; + } + if (config_addr[i] != '}') { + bad_config = true; + panic(true, "config: Malformed macro usage"); + } + i++; + macro_name[j] = 0; + char *macro_value = ""; + struct macro *macro = macros; + for (;;) { + if (macro == NULL) { + break; + } + if (strcmp(macro->name, macro_name) == 0) { + macro_value = macro->value; + break; + } + macro = macro->next; + } + pmm_free(macro_name, 1024); + for (j = 0; macro_value[j] != 0; j++, in++) { + if (in >= new_config_size) { + goto overflow; + } + new_config[in] = macro_value[j]; + } + continue; + } + + if (in >= new_config_size) { +overflow: + bad_config = true; + panic(true, "config: Macro-induced buffer overflow"); + } + new_config[in++] = config_addr[i++]; + } + + pmm_free(config_addr, config_alloc_size); + + config_addr = new_config; + config_size = in; + + // Free macros + struct macro *macro = macros; + for (;;) { + if (macro == NULL) { + break; + } + struct macro *next = macro->next; + pmm_free(macro, sizeof(struct macro)); + macro = next; + } + macros = NULL; + } + + config_ready = true; + + menu_tree = create_menu_tree(NULL, 1, 0); + + size_t s; + char *c = config_get_entry(&s, 0); + if (c != NULL) { + while (*c != '/' && c > config_addr) { + c--; + } + if (*c == '/' && c > config_addr) { + c[-1] = 0; + } + } + + return 0; +} + +static bool config_get_entry_name(char *ret, size_t index, size_t limit) { + if (!config_ready) + return false; + + char *p = config_addr; + + for (size_t i = 0; i <= index; i++) { + while (*p != '/') { + if (!*p) + return false; + p++; + } + p++; + if ((p - 1) != config_addr && *(p - 2) != '\n') + i--; + } + + p--; + + size_t i; + for (i = 0; i < (limit - 1); i++) { + if (p[i] == SEPARATOR) + break; + ret[i] = p[i]; + } + + ret[i] = 0; + return true; +} + +static char *config_get_entry(size_t *size, size_t index) { + if (!config_ready) + return NULL; + + char *ret; + char *p = config_addr; + + for (size_t i = 0; i <= index; i++) { + while (*p != '/') { + if (!*p) + return NULL; + p++; + } + p++; + if ((p - 1) != config_addr && *(p - 2) != '\n') + i--; + } + + do { + p++; + } while (*p != '\n' && *p != '\0'); + + ret = p; + +cont: + while (*p != '/' && *p) + p++; + + if (*p && *(p - 1) != '\n') { + p++; + goto cont; + } + + *size = p - ret; + + return ret; +} + +static const char *lastkey; + +struct conf_tuple config_get_tuple(const char *config, size_t index, + const char *key1, const char *key2) { + // Static buffers for return values. + // Callers must copy the result if they need persistence across calls. + #define CONF_TUPLE_BUF_SIZE 4096 + static char value1_buf[CONF_TUPLE_BUF_SIZE]; + static char value2_buf[CONF_TUPLE_BUF_SIZE]; + + struct conf_tuple conf_tuple; + + char *tmp = config_get_value(config, index, key1); + if (tmp == NULL) { + return (struct conf_tuple){0}; + } + size_t len = strlen(tmp); + if (len >= CONF_TUPLE_BUF_SIZE) { + len = CONF_TUPLE_BUF_SIZE - 1; + } + memcpy(value1_buf, tmp, len); + value1_buf[len] = '\0'; + conf_tuple.value1 = value1_buf; + + const char *lk1 = lastkey; + + tmp = config_get_value(lk1, 0, key2); + if (tmp != NULL) { + len = strlen(tmp); + if (len >= CONF_TUPLE_BUF_SIZE) { + len = CONF_TUPLE_BUF_SIZE - 1; + } + memcpy(value2_buf, tmp, len); + value2_buf[len] = '\0'; + conf_tuple.value2 = value2_buf; + } else { + conf_tuple.value2 = NULL; + } + + const char *lk2 = lastkey; + + const char *next_value1 = config_get_value(config, index + 1, key1); + + const char *lk3 = lastkey; + + if (conf_tuple.value2 != NULL && next_value1 != NULL) { + if ((uintptr_t)lk2 > (uintptr_t)lk3) { + conf_tuple.value2 = NULL; + } + } + + return conf_tuple; +} + +char *config_get_value(const char *config, size_t index, const char *key) { + // Static buffer for return values. + // Callers must copy the result if they need persistence across calls. + #define CONFIG_VALUE_BUF_SIZE 4096 + static char buf[CONFIG_VALUE_BUF_SIZE]; + + if (!key || !config_ready) + return NULL; + + if (config == NULL) + config = config_addr; + + size_t key_len = strlen(key); + + for (size_t i = 0; config[i]; i++) { + if (!strncasecmp(&config[i], key, key_len) && config[i + key_len] == ':') { + if (i && config[i - 1] != SEPARATOR) + continue; + if (index--) + continue; + i += key_len + 1; + while (config[i] == ' ' || config[i] == '\t') { + i++; + } + size_t value_len; + for (value_len = 0; + config[i + value_len] != SEPARATOR && config[i + value_len]; + value_len++); + if (value_len >= CONFIG_VALUE_BUF_SIZE) { + value_len = CONFIG_VALUE_BUF_SIZE - 1; + } + memcpy(buf, config + i, value_len); + buf[value_len] = '\0'; + lastkey = config + i; + return buf; + } + } + + return NULL; +} diff --git a/limine/common/lib/config.h b/limine/common/lib/config.h new file mode 100644 index 0000000..00c8094 --- /dev/null +++ b/limine/common/lib/config.h @@ -0,0 +1,36 @@ +#ifndef LIB__CONFIG_H__ +#define LIB__CONFIG_H__ + +#include +#include +#include + +extern bool config_ready; +extern bool bad_config; + +struct menu_entry { + char name[64]; + char *comment; + struct menu_entry *parent; + struct menu_entry *sub; + bool expanded; + char *body; + struct menu_entry *next; +}; + +struct conf_tuple { + char *value1; + char *value2; +}; + +extern struct menu_entry *menu_tree; + +int init_config_disk(struct volume *part); +bool init_config_smbios(void); +int init_config(size_t config_size); + +char *config_get_value(const char *config, size_t index, const char *key); +struct conf_tuple config_get_tuple(const char *config, size_t index, + const char *key1, const char *key2); + +#endif diff --git a/limine/common/lib/elf.c b/limine/common/lib/elf.c new file mode 100644 index 0000000..ee686bd --- /dev/null +++ b/limine/common/lib/elf.c @@ -0,0 +1,1209 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ET_NONE 0 +#define ET_REL 1 +#define ET_EXEC 2 +#define ET_DYN 3 + +#define PT_LOAD 0x00000001 +#define PT_DYNAMIC 0x00000002 +#define PT_INTERP 0x00000003 +#define PT_PHDR 0x00000006 + +#define DT_NULL 0x00000000 +#define DT_NEEDED 0x00000001 +#define DT_RELA 0x00000007 +#define DT_RELASZ 0x00000008 +#define DT_RELAENT 0x00000009 +#define DT_RELR 0x00000024 +#define DT_RELRSZ 0x00000023 +#define DT_RELRENT 0x00000025 +#define DT_SYMTAB 0x00000006 +#define DT_SYMENT 0x0000000b +#define DT_STRTAB 0x00000005 +#define DT_PLTREL 0x00000014 +#define DT_PLTRELSZ 0x00000002 +#define DT_JMPREL 0x00000017 +#define DT_FLAGS_1 0x6ffffffb + +#define DF_1_PIE 0x08000000 + +#define ABI_SYSV 0x00 +#define ARCH_X86_64 0x3e +#define ARCH_X86_32 0x03 +#define ARCH_AARCH64 0xb7 +#define ARCH_RISCV 0xf3 +#define ARCH_LOONGARCH 0x102 +#define BITS_LE 0x01 +#define ELFCLASS32 0x01 +#define ELFCLASS64 0x02 +#define SHT_RELA 0x00000004 +#define SHN_UNDEF 0x00000000 +#define STB_WEAK 0x00000002 +#define R_X86_64_NONE 0x00000000 +#define R_AARCH64_NONE 0x00000000 +#define R_RISCV_NONE 0x00000000 +#define R_LARCH_NONE 0x00000000 +#define R_X86_64_RELATIVE 0x00000008 +#define R_AARCH64_RELATIVE 0x00000403 +#define R_RISCV_RELATIVE 0x00000003 +#define R_LARCH_RELATIVE 0x00000003 +#define R_X86_64_GLOB_DAT 0x00000006 +#define R_AARCH64_GLOB_DAT 0x00000401 +#define R_X86_64_JUMP_SLOT 0x00000007 +#define R_AARCH64_JUMP_SLOT 0x00000402 +#define R_RISCV_JUMP_SLOT 0x00000005 +#define R_LARCH_JUMP_SLOT 0x00000005 +#define R_X86_64_64 0x00000001 +#define R_RISCV_64 0x00000002 +#define R_LARCH_64 0x00000002 +#define R_AARCH64_ABS64 0x00000101 + +#define R_INTERNAL_RELR 0xfffffff0 + +/* Indices into identification array */ +#define EI_CLASS 4 +#define EI_DATA 5 +#define EI_VERSION 6 +#define EI_OSABI 7 + +struct elf32_hdr { + uint8_t ident[16]; + uint16_t type; + uint16_t machine; + uint32_t version; + uint32_t entry; + uint32_t phoff; + uint32_t shoff; + uint32_t flags; + uint16_t hdr_size; + uint16_t phdr_size; + uint16_t ph_num; + uint16_t shdr_size; + uint16_t sh_num; + uint16_t shstrndx; +}; + +struct elf64_phdr { + uint32_t p_type; + uint32_t p_flags; + uint64_t p_offset; + uint64_t p_vaddr; + uint64_t p_paddr; + uint64_t p_filesz; + uint64_t p_memsz; + uint64_t p_align; +}; + +struct elf32_phdr { + uint32_t p_type; + uint32_t p_offset; + uint32_t p_vaddr; + uint32_t p_paddr; + uint32_t p_filesz; + uint32_t p_memsz; + uint32_t p_flags; + uint32_t p_align; +}; + +struct elf64_rela { + uint64_t r_addr; + uint32_t r_info; + uint32_t r_symbol; + uint64_t r_addend; +}; + +struct elf32_dyn { + uint32_t d_tag; + uint32_t d_un; +}; + +struct elf64_dyn { + uint64_t d_tag; + uint64_t d_un; +}; + +static bool elf32_validate(struct elf32_hdr *hdr) { + if (strncmp((char *)hdr->ident, "\177ELF", 4)) { + panic(true, "elf: Not a valid ELF file."); + } + + if (hdr->ident[EI_DATA] != BITS_LE) { + panic(true, "elf: Not a Little-endian ELF file."); + } + + if (hdr->machine != ARCH_X86_32) { + panic(true, "elf: Not an IA-32 ELF file."); + } + + return true; +} + +static bool elf64_validate(struct elf64_hdr *hdr) { + if (strncmp((char *)hdr->ident, "\177ELF", 4)) { + panic(true, "elf: Not a valid ELF file."); + } + + if (hdr->ident[EI_DATA] != BITS_LE) { + panic(true, "elf: Not a Little-endian ELF file."); + } + +#if defined (__x86_64__) || defined (__i386__) + if (hdr->machine != ARCH_X86_64) { + panic(true, "elf: Not an x86-64 ELF file."); + } +#elif defined (__aarch64__) + if (hdr->machine != ARCH_AARCH64) { + panic(true, "elf: Not an aarch64 ELF file."); + } +#elif defined (__riscv) + if (hdr->machine != ARCH_RISCV && hdr->ident[EI_CLASS] == ELFCLASS64) { + panic(true, "elf: Not a riscv64 ELF file."); + } +#elif defined (__loongarch64) + if (hdr->machine != ARCH_LOONGARCH && hdr->ident[EI_CLASS] == ELFCLASS64) { + panic(true, "elf: Not a loongarch64 ELF file."); + } +#else +#error Unknown architecture +#endif + + return true; +} + +int elf_bits(uint8_t *elf) { + struct elf64_hdr *hdr = (void *)elf; + + if (strncmp((char *)hdr->ident, "\177ELF", 4)) { + return -1; + } + + switch (hdr->machine) { + case ARCH_X86_64: + case ARCH_AARCH64: + return 64; + case ARCH_RISCV: + case ARCH_LOONGARCH: + if (hdr->ident[EI_CLASS] == ELFCLASS64) return 64; + if (hdr->ident[EI_CLASS] == ELFCLASS32) return 32; + return -1; + case ARCH_X86_32: + return 32; + default: + return -1; + } +} + +struct elf_section_hdr_info elf64_section_hdr_info(uint8_t *elf) { + struct elf_section_hdr_info info = {0}; + + struct elf64_hdr *hdr = (void *)elf; + + elf64_validate(hdr); + + info.num = hdr->sh_num; + info.section_entry_size = hdr->shdr_size; + info.str_section_idx = hdr->shstrndx; + info.section_offset = hdr->shoff; + + return info; +} + +struct elf_section_hdr_info elf32_section_hdr_info(uint8_t *elf) { + struct elf_section_hdr_info info = {0}; + + struct elf32_hdr *hdr = (void *)elf; + + elf32_validate(hdr); + + info.num = hdr->sh_num; + info.section_entry_size = hdr->shdr_size; + info.str_section_idx = hdr->shstrndx; + info.section_offset = hdr->shoff; + + return info; +} + +static bool elf64_is_relocatable(uint8_t *elf, struct elf64_hdr *hdr) { + if (hdr->phdr_size < sizeof(struct elf64_phdr)) { + panic(true, "elf: phdr_size < sizeof(struct elf64_phdr)"); + } + + if (hdr->type != ET_DYN) { + return false; + } + + // Find PT_DYNAMIC segment + for (size_t i = 0; i < hdr->ph_num; i++) { + struct elf64_phdr *phdr = (void *)elf + (hdr->phoff + i * hdr->phdr_size); + + if (phdr->p_type != PT_DYNAMIC) { + continue; + } + + if (phdr->p_filesz == 0) { + panic(true, "elf: ELF file type is ET_DYN, but PT_DYNAMIC segment has 0 size"); + } + + return true; + } + + panic(true, "elf: ELF file type is ET_DYN, but PT_DYNAMIC segment missing"); +} + +static bool elf64_apply_relocations(uint8_t *elf, struct elf64_hdr *hdr, void *buffer, uint64_t vaddr, size_t size, uint64_t slide) { + if (hdr->phdr_size < sizeof(struct elf64_phdr)) { + panic(true, "elf: phdr_size < sizeof(struct elf64_phdr)"); + } + + uint64_t symtab_offset = 0; + uint64_t symtab_ent = 0; + uint64_t symtab_size = 0; // Size of symbol table (if known) + uint64_t strtab_offset = 0; + uint64_t strtab_size = 0; // Size of string table (if known) + + uint64_t dt_pltrel = 0; + uint64_t dt_pltrelsz = 0; + uint64_t dt_jmprel = 0; + + uint64_t relr_offset = 0; + uint64_t relr_size = 0; + uint64_t relr_ent = 0; + + uint64_t rela_offset = 0; + uint64_t rela_size = 0; + uint64_t rela_ent = 0; + + // Find DYN segment + for (uint16_t i = 0; i < hdr->ph_num; i++) { + struct elf64_phdr *phdr = (void *)elf + (hdr->phoff + i * hdr->phdr_size); + + if (phdr->p_type != PT_DYNAMIC) + continue; + + for (uint16_t j = 0; j < phdr->p_filesz / sizeof(struct elf64_dyn); j++) { + struct elf64_dyn *dyn = (void *)elf + (phdr->p_offset + j * sizeof(struct elf64_dyn)); + + switch (dyn->d_tag) { + case DT_RELA: + rela_offset = dyn->d_un; + break; + case DT_RELAENT: + rela_ent = dyn->d_un; + break; + case DT_RELASZ: + rela_size = dyn->d_un; + break; + case DT_RELR: + relr_offset = dyn->d_un; + break; + case DT_RELRENT: + relr_ent = dyn->d_un; + break; + case DT_RELRSZ: + relr_size = dyn->d_un; + break; + case DT_SYMTAB: + symtab_offset = dyn->d_un; + break; + case DT_STRTAB: + strtab_offset = dyn->d_un; + break; + case DT_SYMENT: + symtab_ent = dyn->d_un; + if (symtab_ent < sizeof(struct elf64_sym)) { + panic(true, "elf: symtab_ent < sizeof(struct elf64_sym)"); + } + break; + case DT_PLTREL: + dt_pltrel = dyn->d_un; + break; + case DT_PLTRELSZ: + dt_pltrelsz = dyn->d_un; + break; + case DT_JMPREL: + dt_jmprel = dyn->d_un; + break; + case DT_NEEDED: + panic(true, "elf: ELF file attempts to load a dynamically linked library"); + case DT_NULL: + goto end_of_pt_segment; + } + } + + break; + } +end_of_pt_segment: + + if (rela_offset != 0) { + bool rela_translated = false; + for (uint16_t i = 0; i < hdr->ph_num; i++) { + struct elf64_phdr *_phdr = (void *)elf + (hdr->phoff + i * hdr->phdr_size); + + if (_phdr->p_vaddr <= rela_offset && _phdr->p_vaddr + _phdr->p_filesz > rela_offset) { + rela_offset -= _phdr->p_vaddr; + rela_offset += _phdr->p_offset; + rela_translated = true; + break; + } + } + if (!rela_translated) { + panic(true, "elf: RELA vaddr not in any PT_LOAD segment"); + } + } + + if (relr_offset != 0) { + bool relr_translated = false; + for (uint16_t i = 0; i < hdr->ph_num; i++) { + struct elf64_phdr *_phdr = (void *)elf + (hdr->phoff + i * hdr->phdr_size); + + if (_phdr->p_vaddr <= relr_offset && _phdr->p_vaddr + _phdr->p_filesz > relr_offset) { + relr_offset -= _phdr->p_vaddr; + relr_offset += _phdr->p_offset; + relr_translated = true; + break; + } + } + if (!relr_translated) { + panic(true, "elf: RELR vaddr not in any PT_LOAD segment"); + } + } + + if (symtab_offset != 0) { + bool symtab_translated = false; + for (uint16_t i = 0; i < hdr->ph_num; i++) { + struct elf64_phdr *_phdr = (void *)elf + (hdr->phoff + i * hdr->phdr_size); + + if (_phdr->p_vaddr <= symtab_offset && _phdr->p_vaddr + _phdr->p_filesz > symtab_offset) { + // Calculate size as remaining bytes in segment from symtab start + symtab_size = _phdr->p_filesz - (symtab_offset - _phdr->p_vaddr); + symtab_offset -= _phdr->p_vaddr; + symtab_offset += _phdr->p_offset; + symtab_translated = true; + break; + } + } + if (!symtab_translated) { + panic(true, "elf: SYMTAB vaddr not in any PT_LOAD segment"); + } + } + + if (strtab_offset != 0) { + bool strtab_translated = false; + for (uint16_t i = 0; i < hdr->ph_num; i++) { + struct elf64_phdr *_phdr = (void *)elf + (hdr->phoff + i * hdr->phdr_size); + + if (_phdr->p_vaddr <= strtab_offset && _phdr->p_vaddr + _phdr->p_filesz > strtab_offset) { + // Calculate size as remaining bytes in segment from strtab start + strtab_size = _phdr->p_filesz - (strtab_offset - _phdr->p_vaddr); + strtab_offset -= _phdr->p_vaddr; + strtab_offset += _phdr->p_offset; + strtab_translated = true; + break; + } + } + if (!strtab_translated) { + panic(true, "elf: STRTAB vaddr not in any PT_LOAD segment"); + } + } + + if (dt_jmprel != 0) { + bool jmprel_translated = false; + for (uint16_t i = 0; i < hdr->ph_num; i++) { + struct elf64_phdr *_phdr = (void *)elf + (hdr->phoff + i * hdr->phdr_size); + + if (_phdr->p_vaddr <= dt_jmprel && _phdr->p_vaddr + _phdr->p_filesz > dt_jmprel) { + dt_jmprel -= _phdr->p_vaddr; + dt_jmprel += _phdr->p_offset; + jmprel_translated = true; + break; + } + } + if (!jmprel_translated) { + panic(true, "elf: JMPREL vaddr not in any PT_LOAD segment"); + } + } + + size_t relocs_i = 0; + if (relr_size != 0) { + if (relr_ent != 8) { + panic(true, "elf: relr_ent != 8"); + } + for (size_t i = 0; i < relr_size / relr_ent; i++) { + uint64_t entry = *((uint64_t *)(elf + relr_offset + i * relr_ent)); + + if ((entry & 1) == 0) { + relocs_i++; + } else { + relocs_i += __builtin_popcountll(entry) - 1; + } + } + } + size_t relr_count = relocs_i; + if (rela_size != 0) { + if (rela_ent < sizeof(struct elf64_rela)) { + panic(true, "elf: rela_ent < sizeof(struct elf64_rela)"); + } + relocs_i += rela_size / rela_ent; + } + if (dt_pltrelsz != 0) { + if (dt_pltrel != DT_RELA) { + panic(true, "elf: dt_pltrel != DT_RELA"); + } + if (rela_ent == 0) { + panic(true, "elf: dt_pltrelsz != 0 but rela_ent == 0"); + } + relocs_i += dt_pltrelsz / rela_ent; + } + struct elf64_rela **relocs = ext_mem_alloc(relocs_i * sizeof(struct elf64_rela *)); + + if (relr_size != 0) { + size_t relr_i; + for (relr_i = 0; relr_i < relr_count; relr_i++) { + relocs[relr_i] = ext_mem_alloc(sizeof(struct elf64_rela)); + relocs[relr_i]->r_info = R_INTERNAL_RELR; + } + + // This logic is partially lifted from https://maskray.me/blog/2021-10-31-relative-relocations-and-relr + uint64_t where = 0; + relr_i = 0; + for (size_t i = 0; i < relr_size / relr_ent; i++) { + uint64_t entry = *((uint64_t *)(elf + relr_offset + i * relr_ent)); + + if ((entry & 1) == 0) { + where = entry; + relocs[relr_i++]->r_addr = where; + where += 8; + } else { + for (size_t j = 0; (entry >>= 1) != 0; j++) { + if ((entry & 1) != 0) { + relocs[relr_i++]->r_addr = where + j * 8; + } + } + where += 63 * 8; + } + } + } + + if (rela_size != 0) { + for (uint64_t i = relr_count, offset = 0; offset < rela_size; offset += rela_ent) { + relocs[i++] = (void *)elf + (rela_offset + offset); + } + } + + if (dt_pltrelsz != 0) { + for (uint64_t i = relr_count + rela_size / rela_ent, offset = 0; offset < dt_pltrelsz; offset += rela_ent) { + relocs[i++] = (void *)elf + (dt_jmprel + offset); + } + } + + for (size_t i = 0; i < relocs_i; i++) { + struct elf64_rela *relocation = relocs[i]; + + // Relocation is before buffer + if (relocation->r_addr < vaddr) + continue; + + // Relocation is after buffer + if (size < 8 || relocation->r_addr > vaddr + size - 8) + continue; + + // It's inside it, calculate where it is + uint64_t *ptr = (uint64_t *)((buffer - vaddr) + relocation->r_addr); + + switch (relocation->r_info) { +#if defined (__x86_64__) || defined (__i386__) + case R_X86_64_NONE: +#elif defined (__aarch64__) + case R_AARCH64_NONE: +#elif defined (__riscv) + case R_RISCV_NONE: +#elif defined (__loongarch64) + case R_LARCH_NONE: +#endif + { + break; + } +#if defined (__x86_64__) || defined (__i386__) + case R_X86_64_RELATIVE: +#elif defined (__aarch64__) + case R_AARCH64_RELATIVE: +#elif defined (__riscv) + case R_RISCV_RELATIVE: +#elif defined (__loongarch64) + case R_LARCH_RELATIVE: +#endif + { + *ptr = slide + relocation->r_addend; + break; + } + case R_INTERNAL_RELR: + { + *ptr += slide; + break; + } +#if defined (__x86_64__) || defined (__i386__) + case R_X86_64_GLOB_DAT: + case R_X86_64_JUMP_SLOT: +#elif defined (__aarch64__) + case R_AARCH64_GLOB_DAT: + case R_AARCH64_JUMP_SLOT: +#elif defined (__riscv) + case R_RISCV_JUMP_SLOT: +#elif defined (__loongarch64) + case R_LARCH_JUMP_SLOT: +#endif + { + if (symtab_offset == 0 || symtab_ent == 0) { + panic(true, "elf: Relocation requires symbol table but none present"); + } + if (symtab_size == 0) { + panic(true, "elf: Symtab vaddr translation failed"); + } + // Validate symbol index is within bounds + uint64_t sym_offset = symtab_ent * (uint64_t)relocation->r_symbol; + if (sym_offset + sizeof(struct elf64_sym) > symtab_size) { + panic(true, "elf: Symbol index %u out of bounds", relocation->r_symbol); + } + struct elf64_sym *s = (void *)elf + symtab_offset + sym_offset; + if (s->st_shndx == SHN_UNDEF) { + if ((s->st_info >> 4) == STB_WEAK) { + *ptr = 0; + break; + } + if (strtab_size == 0) { + panic(true, "elf: Strtab vaddr translation failed"); + } + // Validate string table access + if (s->st_name >= strtab_size) { + panic(true, "elf: Symbol name offset out of bounds"); + } + panic(true, "elf: Unresolved symbol \"%s\"", elf + strtab_offset + s->st_name); + } + *ptr = slide + s->st_value +#if defined (__aarch64__) + + relocation->r_addend +#endif + ; + break; + } +#if defined (__x86_64__) || defined (__i386__) + case R_X86_64_64: +#elif defined (__aarch64__) + case R_AARCH64_ABS64: +#elif defined (__riscv) + case R_RISCV_64: +#elif defined (__loongarch64) + case R_LARCH_64: +#endif + { + if (symtab_offset == 0 || symtab_ent == 0) { + panic(true, "elf: Relocation requires symbol table but none present"); + } + if (symtab_size == 0) { + panic(true, "elf: Symtab vaddr translation failed"); + } + // Validate symbol index is within bounds + uint64_t sym_offset = symtab_ent * (uint64_t)relocation->r_symbol; + if (sym_offset + sizeof(struct elf64_sym) > symtab_size) { + panic(true, "elf: Symbol index %u out of bounds", relocation->r_symbol); + } + struct elf64_sym *s = (void *)elf + symtab_offset + sym_offset; + if (s->st_shndx == SHN_UNDEF) { + if ((s->st_info >> 4) == STB_WEAK) { + *ptr = 0; + break; + } + if (strtab_size == 0) { + panic(true, "elf: Strtab vaddr translation failed"); + } + // Validate string table access + if (s->st_name >= strtab_size) { + panic(true, "elf: Symbol name offset out of bounds"); + } + panic(true, "elf: Unresolved symbol \"%s\"", elf + strtab_offset + s->st_name); + } + *ptr = slide + s->st_value + relocation->r_addend; + break; + } + default: { + panic(true, "elf: Unknown relocation type: %x", relocation->r_info); + } + } + } + + for (size_t i = 0; i < relr_count; i++) { + pmm_free(relocs[i], sizeof(struct elf64_rela)); + } + pmm_free(relocs, relocs_i * sizeof(struct elf64_rela *)); + + return true; +} + +bool elf64_load_section(uint8_t *elf, size_t file_size, void *buffer, const char *name, size_t limit, uint64_t slide) { + struct elf64_hdr *hdr = (void *)elf; + + elf64_validate(hdr); + + if (hdr->sh_num == 0) { + return false; + } + + if (hdr->shdr_size < sizeof(struct elf64_shdr)) { + panic(true, "elf: shdr_size < sizeof(struct elf64_shdr)"); + } + + if (hdr->shstrndx >= hdr->sh_num) { + return false; + } + + // Validate section header table is within file bounds + uint64_t shdr_table_end = (uint64_t)hdr->shoff + (uint64_t)hdr->sh_num * hdr->shdr_size; + if (shdr_table_end > file_size) { + return false; + } + + struct elf64_shdr *shstrtab = (void *)elf + (hdr->shoff + hdr->shstrndx * hdr->shdr_size); + + // Validate shstrtab offset and size are within file bounds + if (shstrtab->sh_offset >= file_size || shstrtab->sh_size > file_size - shstrtab->sh_offset) { + return false; + } + + char *names = (void *)elf + shstrtab->sh_offset; + + for (uint16_t i = 0; i < hdr->sh_num; i++) { + struct elf64_shdr *section = (void *)elf + (hdr->shoff + i * hdr->shdr_size); + + // Validate sh_name is within the string table + if (section->sh_name >= shstrtab->sh_size) { + continue; + } + + // Ensure the string is NUL-terminated within the string table + if (!memchr(&names[section->sh_name], '\0', shstrtab->sh_size - section->sh_name)) { + continue; + } + + if (strcmp(&names[section->sh_name], name) == 0) { + // Validate section data is within file bounds + if (section->sh_offset >= file_size || section->sh_size > file_size - section->sh_offset) { + return false; + } + + if (limit == 0) { + *(void **)buffer = ext_mem_alloc(section->sh_size); + buffer = *(void **)buffer; + limit = section->sh_size; + } + if (section->sh_size > limit) { + return false; + } + memcpy(buffer, elf + section->sh_offset, section->sh_size); + return elf64_apply_relocations(elf, hdr, buffer, section->sh_addr, section->sh_size, slide); + } + } + + return false; +} + +static uint64_t elf64_max_align(uint8_t *elf) { + uint64_t ret = 0; + + struct elf64_hdr *hdr = (void *)elf; + + if (hdr->phdr_size < sizeof(struct elf64_phdr)) { + panic(true, "elf: phdr_size < sizeof(struct elf64_phdr)"); + } + + for (uint16_t i = 0; i < hdr->ph_num; i++) { + struct elf64_phdr *phdr = (void *)elf + (hdr->phoff + i * hdr->phdr_size); + + if (phdr->p_type != PT_LOAD || phdr->p_memsz == 0) { + continue; + } + + if (phdr->p_align > 1 && (phdr->p_align & (phdr->p_align - 1)) != 0) { + panic(true, "elf: p_align is not a power of 2"); + } + + if (phdr->p_align > ret) { + ret = phdr->p_align; + } + } + + if (ret == 0) { + panic(true, "elf: Executable has no loadable segments"); + } + + return ret; +} + +static void elf64_get_ranges(uint8_t *elf, uint64_t slide, struct mem_range **_ranges, uint64_t *_ranges_count) { + struct elf64_hdr *hdr = (void *)elf; + + uint64_t ranges_count = 0; + + if (hdr->phdr_size < sizeof(struct elf64_phdr)) { + panic(true, "elf: phdr_size < sizeof(struct elf64_phdr)"); + } + + bool is_reloc = elf64_is_relocatable(elf, hdr); + + for (uint16_t i = 0; i < hdr->ph_num; i++) { + struct elf64_phdr *phdr = (void *)elf + (hdr->phoff + i * hdr->phdr_size); + + if (phdr->p_type != PT_LOAD || phdr->p_memsz == 0) { + continue; + } + + if (phdr->p_vaddr < FIXED_HIGHER_HALF_OFFSET_64) { + if (!is_reloc) { + continue; + } + } + + ranges_count++; + } + + if (ranges_count == 0) { + panic(true, "elf: No higher half PHDRs exist"); + } + + struct mem_range *ranges = ext_mem_alloc(ranges_count * sizeof(struct mem_range)); + + size_t r = 0; + for (uint16_t i = 0; i < hdr->ph_num; i++) { + struct elf64_phdr *phdr = (void *)elf + (hdr->phoff + i * hdr->phdr_size); + + if (phdr->p_type != PT_LOAD || phdr->p_memsz == 0) { + continue; + } + + if (phdr->p_vaddr < FIXED_HIGHER_HALF_OFFSET_64) { + if (!is_reloc) { + continue; + } + } + + uint64_t load_addr = phdr->p_vaddr + slide; + uint64_t this_top = load_addr + phdr->p_memsz; + + uint64_t align = phdr->p_align <= 1 ? 1 : phdr->p_align; + ranges[r].base = load_addr & ~(align - 1); + ranges[r].length = ALIGN_UP(this_top - ranges[r].base, align); + + if (phdr->p_flags & ELF_PF_X) { + ranges[r].permissions |= MEM_RANGE_X; + } + + if (phdr->p_flags & ELF_PF_W) { + ranges[r].permissions |= MEM_RANGE_W; + } + + if (phdr->p_flags & ELF_PF_R) { + ranges[r].permissions |= MEM_RANGE_R; + } + + r++; + } + + *_ranges_count = ranges_count; + *_ranges = ranges; +} + +bool elf64_load(uint8_t *elf, size_t file_size, uint64_t *entry_point, uint64_t *_slide, uint32_t alloc_type, bool kaslr, struct mem_range **ranges, uint64_t *ranges_count, uint64_t *physical_base, uint64_t *virtual_base, uint64_t *_image_size, uint64_t *_image_size_before_bss, bool *is_reloc) { + struct elf64_hdr *hdr = (void *)elf; + + elf64_validate(hdr); + + if (hdr->type != ET_EXEC && hdr->type != ET_DYN) { + panic(true, "elf: ELF file not of type ET_EXEC nor ET_DYN"); + } + + if (hdr->phdr_size < sizeof(struct elf64_phdr)) { + panic(true, "elf: phdr_size < sizeof(struct elf64_phdr)"); + } + + // Validate program header offset and size don't overflow + uint64_t phdr_table_end; + if (__builtin_mul_overflow((uint64_t)hdr->ph_num, (uint64_t)hdr->phdr_size, &phdr_table_end) || + __builtin_add_overflow(phdr_table_end, hdr->phoff, &phdr_table_end)) { + panic(true, "elf: Program header table size overflow"); + } + + if (phdr_table_end > file_size) { + panic(true, "elf: Program header table extends beyond file bounds"); + } + + if (is_reloc) { + *is_reloc = false; + } + if (elf64_is_relocatable(elf, hdr)) { + if (is_reloc) { + *is_reloc = true; + } + } + + uint64_t slide = 0; + size_t try_count = 0; + size_t max_simulated_tries = 0x10000; + + uint64_t entry = hdr->entry; + + uint64_t max_align = elf64_max_align(elf); + + uint64_t image_size = 0; + + bool lower_to_higher = false; + + uint64_t min_vaddr = (uint64_t)-1; + uint64_t max_vaddr = 0; + for (uint16_t i = 0; i < hdr->ph_num; i++) { + struct elf64_phdr *phdr = (void *)elf + (hdr->phoff + i * hdr->phdr_size); + + if (phdr->p_type != PT_LOAD || phdr->p_memsz == 0) { + continue; + } + + if (phdr->p_vaddr < FIXED_HIGHER_HALF_OFFSET_64) { + if (!*is_reloc) { + panic(true, "elf: Lower half PHDRs are not allowed"); + } + lower_to_higher = true; + } else { + if (lower_to_higher) { + panic(true, "elf: Mix of lower and higher half PHDRs in relocatable kernel"); + } + } + + uint64_t phdr_end; + if (__builtin_add_overflow(phdr->p_vaddr, phdr->p_memsz, &phdr_end)) { + panic(true, "elf: p_vaddr + p_memsz overflow in PHDR %u", i); + } + + // check for overlapping phdrs + for (uint16_t j = 0; j < hdr->ph_num; j++) { + struct elf64_phdr *phdr_in = (void *)elf + (hdr->phoff + j * hdr->phdr_size); + + if (phdr_in->p_type != PT_LOAD || phdr_in->p_memsz == 0) { + continue; + } + + if (phdr_in->p_vaddr < FIXED_HIGHER_HALF_OFFSET_64) { + if (!*is_reloc) { + continue; + } + } + + if (phdr_in == phdr) { + continue; + } + + uint64_t phdr_in_end; + if (__builtin_add_overflow(phdr_in->p_vaddr, phdr_in->p_memsz, &phdr_in_end)) { + panic(true, "elf: p_vaddr + p_memsz overflow in PHDR %u", j); + } + + if ((phdr_in->p_vaddr >= phdr->p_vaddr + && phdr_in->p_vaddr < phdr_end) + || + (phdr_in_end > phdr->p_vaddr + && phdr_in_end <= phdr_end)) { + panic(true, "elf: Attempted to load ELF file with overlapping PHDRs (%u and %u overlap)", i, j); + } + + if (ranges != NULL) { + uint64_t page_rounded_base = ALIGN_DOWN(phdr->p_vaddr, 4096); + uint64_t page_rounded_top = ALIGN_UP(phdr_end, 4096); + uint64_t page_rounded_base_in = ALIGN_DOWN(phdr_in->p_vaddr, 4096); + uint64_t page_rounded_top_in = ALIGN_UP(phdr_in_end, 4096); + + if ((page_rounded_base >= page_rounded_base_in + && page_rounded_base < page_rounded_top_in) + || + (page_rounded_top > page_rounded_base_in + && page_rounded_top <= page_rounded_top_in)) { + if ((phdr->p_flags & 0b111) != (phdr_in->p_flags & 0b111)) { + panic(true, "elf: Attempted to load ELF file with PHDRs with different permissions sharing the same memory page."); + } + } + } + } + + if (phdr->p_vaddr < min_vaddr) { + min_vaddr = phdr->p_vaddr; + } + + if (phdr_end > max_vaddr) { + max_vaddr = phdr_end; + } + } + + if (min_vaddr == (uint64_t)-1) { + panic(true, "elf: No usable PHDRs exist"); + } + + if (lower_to_higher) { + slide = FIXED_HIGHER_HALF_OFFSET_64 - min_vaddr; + } + + image_size = max_vaddr - min_vaddr; + + *physical_base = (uintptr_t)ext_mem_alloc_type_aligned(image_size, alloc_type, max_align); + *virtual_base = min_vaddr; + + if (_image_size) { + *_image_size = image_size; + } + +again: + if (*is_reloc && kaslr) { + slide = (rand32() & ~(max_align - 1)) + (lower_to_higher ? FIXED_HIGHER_HALF_OFFSET_64 - min_vaddr : 0); + + if (*virtual_base + slide + image_size < 0xffffffff80000000 /* this comparison relies on overflow */) { + if (++try_count == max_simulated_tries) { + panic(true, "elf: Image wants to load too high"); + } + goto again; + } + } + + uint64_t bss_size = 0; + + for (uint16_t i = 0; i < hdr->ph_num; i++) { + struct elf64_phdr *phdr = (void *)elf + (hdr->phoff + i * hdr->phdr_size); + + if (phdr->p_type != PT_LOAD || phdr->p_memsz == 0) { + continue; + } + + // Sanity checks + if (phdr->p_filesz > phdr->p_memsz) { + panic(true, "elf: p_filesz > p_memsz"); + } + + // Validate p_offset + p_filesz doesn't overflow or exceed file size + uint64_t offset_end; + if (__builtin_add_overflow(phdr->p_offset, phdr->p_filesz, &offset_end)) { + panic(true, "elf: p_offset + p_filesz overflow"); + } + if (offset_end > file_size) { + panic(true, "elf: p_offset + p_filesz exceeds file size"); + } + + uint64_t load_addr = *physical_base + (phdr->p_vaddr - *virtual_base); + +#if defined (__aarch64__) + uint64_t this_top = load_addr + phdr->p_memsz; + + uint64_t mem_base, mem_size; + + uint64_t align = phdr->p_align <= 1 ? 1 : phdr->p_align; + mem_base = load_addr & ~(align - 1); + mem_size = this_top - mem_base; +#endif + + memcpy((void *)(uintptr_t)load_addr, elf + (phdr->p_offset), phdr->p_filesz); + + if (phdr->p_vaddr + phdr->p_memsz == max_vaddr) { + bss_size = phdr->p_memsz - phdr->p_filesz; + } + + if (!elf64_apply_relocations(elf, hdr, (void *)(uintptr_t)load_addr, phdr->p_vaddr, phdr->p_memsz, slide)) { + panic(true, "elf: Failed to apply relocations"); + } + +#if defined (__aarch64__) + clean_dcache_poc(mem_base, mem_base + mem_size); + inval_icache_pou(mem_base, mem_base + mem_size); +#endif + } + + if (_image_size_before_bss != NULL) { + *_image_size_before_bss = image_size - bss_size; + } + + *virtual_base += slide; + *entry_point = entry + slide; + if (_slide) { + *_slide = slide; + } + + if (ranges_count != NULL && ranges != NULL) { + elf64_get_ranges(elf, slide, ranges, ranges_count); + } + + return true; +} + +bool elf32_load_elsewhere(uint8_t *elf, size_t file_size, uint64_t *entry_point, + struct elsewhere_range **ranges) { + struct elf32_hdr *hdr = (void *)elf; + + elf32_validate(hdr); + + if (hdr->type != ET_EXEC && hdr->type != ET_DYN) { + panic(true, "elf: ELF file not of type ET_EXEC nor ET_DYN"); + } + + *entry_point = hdr->entry; + bool entry_adjusted = false; + + if (hdr->phdr_size < sizeof(struct elf32_phdr)) { + panic(true, "elf: phdr_size < sizeof(struct elf32_phdr)"); + } + + uint64_t phdr_table_size32 = (uint64_t)hdr->ph_num * hdr->phdr_size; + if (hdr->phoff > file_size || phdr_table_size32 > file_size - hdr->phoff) { + panic(true, "elf: Program header table extends beyond file bounds"); + } + + size_t image_size = 0; + uint64_t min_paddr = (uint64_t)-1; + uint64_t max_paddr = 0; + for (uint16_t i = 0; i < hdr->ph_num; i++) { + struct elf32_phdr *phdr = (void *)elf + (hdr->phoff + i * hdr->phdr_size); + + if (phdr->p_type != PT_LOAD || phdr->p_memsz == 0) + continue; + + if (phdr->p_paddr < min_paddr) { + min_paddr = phdr->p_paddr; + } + + uint64_t top = (uint64_t)phdr->p_paddr + phdr->p_memsz; + if (top > max_paddr) { + max_paddr = top; + } + } + image_size = max_paddr - min_paddr; + + void *elsewhere = ext_mem_alloc(image_size); + + *ranges = ext_mem_alloc(sizeof(struct elsewhere_range)); + + (*ranges)->elsewhere = (uintptr_t)elsewhere; + (*ranges)->target = min_paddr; + (*ranges)->length = image_size; + + for (uint16_t i = 0; i < hdr->ph_num; i++) { + struct elf32_phdr *phdr = (void *)elf + (hdr->phoff + i * hdr->phdr_size); + + if (phdr->p_type != PT_LOAD || phdr->p_memsz == 0) + continue; + + // Sanity checks + if (phdr->p_filesz > phdr->p_memsz) { + panic(true, "elf: p_filesz > p_memsz"); + } + if ((uint64_t)phdr->p_offset + phdr->p_filesz > file_size) { + panic(true, "elf: p_offset + p_filesz exceeds file size"); + } + + memcpy(elsewhere + (phdr->p_paddr - min_paddr), elf + phdr->p_offset, phdr->p_filesz); + + if (!entry_adjusted + && *entry_point >= phdr->p_vaddr + && *entry_point < (phdr->p_vaddr + phdr->p_memsz)) { + *entry_point -= phdr->p_vaddr; + *entry_point += phdr->p_paddr; + entry_adjusted = true; + } + } + + return true; +} + +bool elf64_load_elsewhere(uint8_t *elf, size_t file_size, uint64_t *entry_point, + struct elsewhere_range **ranges) { + struct elf64_hdr *hdr = (void *)elf; + + elf64_validate(hdr); + + if (hdr->type != ET_EXEC && hdr->type != ET_DYN) { + panic(true, "elf: ELF file not of type ET_EXEC nor ET_DYN"); + } + + *entry_point = hdr->entry; + bool entry_adjusted = false; + + if (hdr->phdr_size < sizeof(struct elf64_phdr)) { + panic(true, "elf: phdr_size < sizeof(struct elf64_phdr)"); + } + + uint64_t phdr_table_size64 = (uint64_t)hdr->ph_num * hdr->phdr_size; + if (hdr->phoff > file_size || phdr_table_size64 > file_size - hdr->phoff) { + panic(true, "elf: Program header table extends beyond file bounds"); + } + + size_t image_size = 0; + uint64_t min_paddr = (uint64_t)-1; + uint64_t max_paddr = 0; + for (uint16_t i = 0; i < hdr->ph_num; i++) { + struct elf64_phdr *phdr = (void *)elf + (hdr->phoff + i * hdr->phdr_size); + + if (phdr->p_type != PT_LOAD || phdr->p_memsz == 0) + continue; + + if (phdr->p_paddr < min_paddr) { + min_paddr = phdr->p_paddr; + } + + uint64_t top; + if (__builtin_add_overflow(phdr->p_paddr, phdr->p_memsz, &top)) { + panic(true, "elf: p_paddr + p_memsz overflow"); + } + if (top > max_paddr) { + max_paddr = top; + } + } + image_size = max_paddr - min_paddr; + + void *elsewhere = ext_mem_alloc(image_size); + + *ranges = ext_mem_alloc(sizeof(struct elsewhere_range)); + + (*ranges)->elsewhere = (uintptr_t)elsewhere; + (*ranges)->target = min_paddr; + (*ranges)->length = image_size; + + for (uint16_t i = 0; i < hdr->ph_num; i++) { + struct elf64_phdr *phdr = (void *)elf + (hdr->phoff + i * hdr->phdr_size); + + if (phdr->p_type != PT_LOAD || phdr->p_memsz == 0) + continue; + + // Sanity checks + if (phdr->p_filesz > phdr->p_memsz) { + panic(true, "elf: p_filesz > p_memsz"); + } + uint64_t offset_end; + if (__builtin_add_overflow(phdr->p_offset, phdr->p_filesz, &offset_end) + || offset_end > file_size) { + panic(true, "elf: p_offset + p_filesz exceeds file size"); + } + + memcpy(elsewhere + (phdr->p_paddr - min_paddr), elf + phdr->p_offset, phdr->p_filesz); + + if (!entry_adjusted + && *entry_point >= phdr->p_vaddr + && *entry_point < (phdr->p_vaddr + phdr->p_memsz)) { + *entry_point -= phdr->p_vaddr; + *entry_point += phdr->p_paddr; + entry_adjusted = true; + } + } + + return true; +} diff --git a/limine/common/lib/elf.h b/limine/common/lib/elf.h new file mode 100644 index 0000000..e8d5b28 --- /dev/null +++ b/limine/common/lib/elf.h @@ -0,0 +1,87 @@ +#ifndef LIB__ELF_H__ +#define LIB__ELF_H__ + +#include +#include +#include +#include + +#define FIXED_HIGHER_HALF_OFFSET_64 ((uint64_t)0xffffffff80000000) + +#define ELF_PF_X 1 +#define ELF_PF_W 2 +#define ELF_PF_R 4 + +struct elf_section_hdr_info { + uint32_t section_entry_size; + uint32_t str_section_idx; + uint32_t num; + uint64_t section_offset; +}; + +int elf_bits(uint8_t *elf); + +struct elf_section_hdr_info elf64_section_hdr_info(uint8_t *elf); +struct elf_section_hdr_info elf32_section_hdr_info(uint8_t *elf); + +bool elf64_load_section(uint8_t *elf, size_t file_size, void *buffer, const char *name, size_t limit, uint64_t slide); +bool elf64_load(uint8_t *elf, size_t file_size, uint64_t *entry_point, uint64_t *_slide, uint32_t alloc_type, bool kaslr, struct mem_range **ranges, uint64_t *ranges_count, uint64_t *physical_base, uint64_t *virtual_base, uint64_t *image_size, uint64_t *image_size_before_bss, bool *is_reloc); + +bool elf32_load_elsewhere(uint8_t *elf, size_t file_size, uint64_t *entry_point, + struct elsewhere_range **ranges); +bool elf64_load_elsewhere(uint8_t *elf, size_t file_size, uint64_t *entry_point, + struct elsewhere_range **ranges); + +struct elf64_hdr { + uint8_t ident[16]; + uint16_t type; + uint16_t machine; + uint32_t version; + uint64_t entry; + uint64_t phoff; + uint64_t shoff; + uint32_t flags; + uint16_t hdr_size; + uint16_t phdr_size; + uint16_t ph_num; + uint16_t shdr_size; + uint16_t sh_num; + uint16_t shstrndx; +}; + +struct elf64_shdr { + uint32_t sh_name; + uint32_t sh_type; + uint64_t sh_flags; + uint64_t sh_addr; + uint64_t sh_offset; + uint64_t sh_size; + uint32_t sh_link; + uint32_t sh_info; + uint64_t sh_addralign; + uint64_t sh_entsize; +}; + +struct elf32_shdr { + uint32_t sh_name; + uint32_t sh_type; + uint32_t sh_flags; + uint32_t sh_addr; + uint32_t sh_offset; + uint32_t sh_size; + uint32_t sh_link; + uint32_t sh_info; + uint32_t sh_addralign; + uint32_t sh_entsize; +}; + +struct elf64_sym { + uint32_t st_name; + uint8_t st_info; + uint8_t st_other; + uint16_t st_shndx; + uint64_t st_value; + uint64_t st_size; +}; + +#endif diff --git a/limine/common/lib/elsewhere.c b/limine/common/lib/elsewhere.c new file mode 100644 index 0000000..67d63f8 --- /dev/null +++ b/limine/common/lib/elsewhere.c @@ -0,0 +1,100 @@ +#include +#include +#include +#include +#include +#include + +static bool elsewhere_overlap_check(uint64_t base1, uint64_t top1, + uint64_t base2, uint64_t top2) { + return (base1 < top2 && base2 < top1); +} + +bool elsewhere_append( + bool flexible_target, + struct elsewhere_range *ranges, uint64_t *ranges_count, + void *elsewhere, uint64_t *target, size_t t_length) { + // original target of -1 means "allocate after top of all ranges" + // flexible target is ignored + flexible_target = true; + if (*target == (uint64_t)-1) { + uint64_t top = 0; + + for (size_t i = 0; i < *ranges_count; i++) { + uint64_t r_top = ranges[i].target + ranges[i].length; + + if (top < r_top) { + top = r_top; + } + } + + *target = ALIGN_UP(top, 4096); + } + + uint64_t max_retries = 0x10000; + +retry: + if (max_retries-- == 0) { + return false; + } + + for (size_t i = 0; i < *ranges_count; i++) { + uint64_t t_top = *target + t_length; + + // Ensure allocation stays within 32-bit address space. + if (t_top > 0x100000000) { + return false; + } + + // Does it overlap with other elsewhere ranges targets? + { + uint64_t base = ranges[i].target; + uint64_t length = ranges[i].length; + uint64_t top = base + length; + + if (elsewhere_overlap_check(base, top, *target, t_top)) { + if (!flexible_target) { + return false; + } + *target = ALIGN_UP(top, 4096); + goto retry; + } + } + + // Does it overlap with other elsewhere ranges sources? + { + uint64_t base = ranges[i].elsewhere; + uint64_t length = ranges[i].length; + uint64_t top = base + length; + + if (elsewhere_overlap_check(base, top, *target, t_top)) { + if (!flexible_target) { + return false; + } + *target += 0x1000; + goto retry; + } + } + + // Make sure it is memory that actually exists. + if (!memmap_alloc_range(*target, t_length, MEMMAP_BOOTLOADER_RECLAIMABLE, + MEMMAP_USABLE, false, true, false)) { + if (!memmap_alloc_range(*target, t_length, MEMMAP_BOOTLOADER_RECLAIMABLE, + MEMMAP_BOOTLOADER_RECLAIMABLE, false, true, false)) { + if (!flexible_target) { + return false; + } + *target += 0x1000; + goto retry; + } + } + } + + // Add the elsewhere range + ranges[*ranges_count].elsewhere = (uintptr_t)elsewhere; + ranges[*ranges_count].target = *target; + ranges[*ranges_count].length = t_length; + *ranges_count += 1; + + return true; +} diff --git a/limine/common/lib/elsewhere.h b/limine/common/lib/elsewhere.h new file mode 100644 index 0000000..99e1432 --- /dev/null +++ b/limine/common/lib/elsewhere.h @@ -0,0 +1,19 @@ +#ifndef LIB__ELSEWHERE_H__ +#define LIB__ELSEWHERE_H__ + +#include +#include +#include + +struct elsewhere_range { + uint64_t elsewhere; + uint64_t target; + uint64_t length; +}; + +bool elsewhere_append( + bool flexible_target, + struct elsewhere_range *ranges, uint64_t *ranges_count, + void *elsewhere, uint64_t *target, size_t t_length); + +#endif diff --git a/limine/common/lib/fb.c b/limine/common/lib/fb.c new file mode 100644 index 0000000..05096b9 --- /dev/null +++ b/limine/common/lib/fb.c @@ -0,0 +1,65 @@ +#include +#include +#include +#include +#include +#include +#include + +struct fb_info *fb_fbs; +size_t fb_fbs_count = 0; + +void fb_init(struct fb_info **ret, size_t *_fbs_count, + uint64_t target_width, uint64_t target_height, uint16_t target_bpp) { +#if defined (BIOS) + *ret = ext_mem_alloc(sizeof(struct fb_info)); + if (init_vbe(*ret, target_width, target_height, target_bpp)) { + *_fbs_count = 1; + + (*ret)->edid = get_edid_info(); + size_t mode_count; + (*ret)->mode_list = vbe_get_mode_list(&mode_count); + (*ret)->mode_count = mode_count; + } else { + *_fbs_count = 0; + pmm_free(*ret, sizeof(struct fb_info)); + } +#elif defined (UEFI) + init_gop(ret, _fbs_count, target_width, target_height, target_bpp); +#endif + + fb_fbs = *ret; + fb_fbs_count = *_fbs_count; +} + +void fb_clear(struct fb_info *fb) { + for (size_t y = 0; y < fb->framebuffer_height; y++) { + switch (fb->framebuffer_bpp) { + case 32: { + uint32_t *fbp = (void *)(uintptr_t)fb->framebuffer_addr; + size_t row = (y * fb->framebuffer_pitch) / 4; + for (size_t x = 0; x < fb->framebuffer_width; x++) { + fbp[row + x] = 0; + } + break; + } + case 16: { + uint16_t *fbp = (void *)(uintptr_t)fb->framebuffer_addr; + size_t row = (y * fb->framebuffer_pitch) / 2; + for (size_t x = 0; x < fb->framebuffer_width; x++) { + fbp[row + x] = 0; + } + break; + } + default: { + uint8_t *fbp = (void *)(uintptr_t)fb->framebuffer_addr; + size_t row = y * fb->framebuffer_pitch; + size_t row_bytes = fb->framebuffer_width * (fb->framebuffer_bpp / 8); + for (size_t x = 0; x < row_bytes; x++) { + fbp[row + x] = 0; + } + break; + } + } + } +} diff --git a/limine/common/lib/fb.h b/limine/common/lib/fb.h new file mode 100644 index 0000000..fea4e28 --- /dev/null +++ b/limine/common/lib/fb.h @@ -0,0 +1,43 @@ +#ifndef LIB__FB_H__ +#define LIB__FB_H__ + +#include +#include +#include + +struct resolution { + uint64_t width; + uint64_t height; + uint16_t bpp; +}; + +struct fb_info { + uint64_t framebuffer_pitch; + uint64_t framebuffer_width; + uint64_t framebuffer_height; + uint16_t framebuffer_bpp; + uint8_t memory_model; + uint8_t red_mask_size; + uint8_t red_mask_shift; + uint8_t green_mask_size; + uint8_t green_mask_shift; + uint8_t blue_mask_size; + uint8_t blue_mask_shift; + + uint64_t framebuffer_addr; + + struct edid_info_struct *edid; + + uint64_t mode_count; + struct fb_info *mode_list; +}; + +extern struct fb_info *fb_fbs; +extern size_t fb_fbs_count; + +void fb_init(struct fb_info **ret, size_t *_fbs_count, + uint64_t target_width, uint64_t target_height, uint16_t target_bpp); + +void fb_clear(struct fb_info *fb); + +#endif diff --git a/limine/common/lib/fdt.c b/limine/common/lib/fdt.c new file mode 100644 index 0000000..9e3e115 --- /dev/null +++ b/limine/common/lib/fdt.c @@ -0,0 +1,44 @@ +#if !defined(__i386__) && !defined(__x86_64__) + +#include +#include +#include + +static int fdt_get_or_add_chosen_node(void *fdt) { + int offset = fdt_subnode_offset(fdt, 0, "chosen"); + + if (offset == -FDT_ERR_NOTFOUND) { + offset = fdt_add_subnode(fdt, 0, "chosen"); + } + + return offset; +} + +int fdt_set_chosen_string(void *fdt, const char *name, const char *value) { + int chosen_offset = fdt_get_or_add_chosen_node(fdt); + if (chosen_offset < 0) { + return chosen_offset; + } + + return fdt_setprop_string(fdt, chosen_offset, name, value); +} + +int fdt_set_chosen_uint64(void *fdt, const char *name, uint64_t value) { + int chosen_offset = fdt_get_or_add_chosen_node(fdt); + if (chosen_offset < 0) { + return chosen_offset; + } + + return fdt_setprop_u64(fdt, chosen_offset, name, value); +} + +int fdt_set_chosen_uint32(void *fdt, const char *name, uint32_t value) { + int chosen_offset = fdt_get_or_add_chosen_node(fdt); + if (chosen_offset < 0) { + return chosen_offset; + } + + return fdt_setprop_u32(fdt, chosen_offset, name, value); +} + +#endif diff --git a/limine/common/lib/fdt.h b/limine/common/lib/fdt.h new file mode 100644 index 0000000..ba7a9a3 --- /dev/null +++ b/limine/common/lib/fdt.h @@ -0,0 +1,15 @@ +#ifndef LIB__FDT_H__ +#define LIB__FDT_H__ + +#if !defined(__x86_64__) && !defined(__i386__) + +#include +#include + +int fdt_set_chosen_string(void *fdt, const char *name, const char *value); +int fdt_set_chosen_uint64(void *fdt, const char *name, uint64_t value); +int fdt_set_chosen_uint32(void *fdt, const char *name, uint32_t value); + +#endif + +#endif // LIB__FDT_H__ diff --git a/limine/common/lib/getchar.c b/limine/common/lib/getchar.c new file mode 100644 index 0000000..b6c375b --- /dev/null +++ b/limine/common/lib/getchar.c @@ -0,0 +1,365 @@ +#include +#include +#include +#include +#include +#include +#include +#if defined (BIOS) +# include +#elif defined (UEFI) +# include +#endif +#include +#include + +int getchar(void) { + for (;;) { + int ret = pit_sleep_and_quit_on_keypress(65535); + if (ret != 0) { + return ret; + } + } +} + +int getchar_internal(uint8_t scancode, uint8_t ascii, uint32_t shift_state) { + switch (scancode) { +#if defined (BIOS) + case 0x44: + return GETCHAR_F10; + case 0x4b: + return GETCHAR_CURSOR_LEFT; + case 0x4d: + return GETCHAR_CURSOR_RIGHT; + case 0x48: + return GETCHAR_CURSOR_UP; + case 0x50: + return GETCHAR_CURSOR_DOWN; + case 0x53: + return GETCHAR_DELETE; + case 0x4f: + return GETCHAR_END; + case 0x47: + return GETCHAR_HOME; + case 0x49: + return GETCHAR_PGUP; + case 0x51: + return GETCHAR_PGDOWN; + case 0x01: + return GETCHAR_ESCAPE; +#elif defined (UEFI) + case SCAN_F10: + return GETCHAR_F10; + case SCAN_LEFT: + return GETCHAR_CURSOR_LEFT; + case SCAN_RIGHT: + return GETCHAR_CURSOR_RIGHT; + case SCAN_UP: + return GETCHAR_CURSOR_UP; + case SCAN_DOWN: + return GETCHAR_CURSOR_DOWN; + case SCAN_DELETE: + return GETCHAR_DELETE; + case SCAN_END: + return GETCHAR_END; + case SCAN_HOME: + return GETCHAR_HOME; + case SCAN_PAGE_UP: + return GETCHAR_PGUP; + case SCAN_PAGE_DOWN: + return GETCHAR_PGDOWN; + case SCAN_ESC: + return GETCHAR_ESCAPE; +#endif + } + switch (ascii) { + case '\n': + case '\r': + return '\n'; + case '\b': + return '\b'; + case '\t': + return '\t'; + } + + if (shift_state & (GETCHAR_LCTRL | GETCHAR_RCTRL)) { + switch (ascii) { + case 'a': return GETCHAR_HOME; + case 'e': return GETCHAR_END; + case 'p': return GETCHAR_CURSOR_UP; + case 'n': return GETCHAR_CURSOR_DOWN; + case 'b': return GETCHAR_CURSOR_LEFT; + case 'f': return GETCHAR_CURSOR_RIGHT; + default: break; + } + } + + // Guard against non-printable values + if (ascii < 0x20 || ascii > 0x7e) { + return -1; + } + return ascii; +} + +#if defined (BIOS) +int _pit_sleep_and_quit_on_keypress(uint32_t ticks); + +static int input_sequence(void) { + int val = 0; + + for (;;) { + int ret = -1; + size_t retries = 0; + + while (ret == -1 && retries < 1000000) { + ret = serial_in(); + retries++; + } + if (ret == -1) { + return 0; + } + + switch (ret) { + case 'A': + return GETCHAR_CURSOR_UP; + case 'B': + return GETCHAR_CURSOR_DOWN; + case 'C': + return GETCHAR_CURSOR_RIGHT; + case 'D': + return GETCHAR_CURSOR_LEFT; + case 'F': + return GETCHAR_END; + case 'H': + return GETCHAR_HOME; + } + + if (ret > '9' || ret < '0') { + break; + } + + val *= 10; + val += ret - '0'; + } + + switch (val) { + case 3: + return GETCHAR_DELETE; + case 5: + return GETCHAR_PGUP; + case 6: + return GETCHAR_PGDOWN; + case 21: + return GETCHAR_F10; + } + + return 0; +} + +int pit_sleep_and_quit_on_keypress(int seconds) { + if (!serial) { + return _pit_sleep_and_quit_on_keypress(seconds * 18); + } + + for (int i = 0; i < seconds * 18; i++) { + int ret = _pit_sleep_and_quit_on_keypress(1); + + if (ret != 0) { + return ret; + } + + ret = serial_in(); + + if (ret != -1) { +again: + switch (ret) { + case '\r': + return '\n'; + case 0x1b: + stall(10); + ret = serial_in(); + if (ret == -1) { + return GETCHAR_ESCAPE; + } + if (ret == '[') { + return input_sequence(); + } + goto again; + case 0x7f: + return '\b'; + } + + return ret; + } + } + + return 0; +} +#endif + +#if defined (UEFI) +static int input_sequence(bool ext, + EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *exproto, + EFI_SIMPLE_TEXT_IN_PROTOCOL *sproto) { + EFI_STATUS status; + EFI_KEY_DATA kd; + + int val = 0; + + for (;;) { + if (ext == false) { + status = sproto->ReadKeyStroke(sproto, &kd.Key); + } else { + status = exproto->ReadKeyStrokeEx(exproto, &kd); + } + + if (status != EFI_SUCCESS) { + return 0; + } + + switch (kd.Key.UnicodeChar) { + case 'A': + return GETCHAR_CURSOR_UP; + case 'B': + return GETCHAR_CURSOR_DOWN; + case 'C': + return GETCHAR_CURSOR_RIGHT; + case 'D': + return GETCHAR_CURSOR_LEFT; + case 'F': + return GETCHAR_END; + case 'H': + return GETCHAR_HOME; + } + + if (kd.Key.UnicodeChar > '9' || kd.Key.UnicodeChar < '0') { + break; + } + + val *= 10; + val += kd.Key.UnicodeChar - '0'; + } + + switch (val) { + case 3: + return GETCHAR_DELETE; + case 5: + return GETCHAR_PGUP; + case 6: + return GETCHAR_PGDOWN; + case 21: + return GETCHAR_F10; + } + + return 0; +} + +int pit_sleep_and_quit_on_keypress(int seconds) { + EFI_KEY_DATA kd; + + UINTN which; + + EFI_EVENT events[2]; + + EFI_GUID exproto_guid = EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL_GUID; + EFI_GUID sproto_guid = EFI_SIMPLE_TEXT_INPUT_PROTOCOL_GUID; + EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *exproto = NULL; + EFI_SIMPLE_TEXT_IN_PROTOCOL *sproto = NULL; + + bool use_sproto = false; + + if (gBS->HandleProtocol(gST->ConsoleInHandle, &exproto_guid, (void **)&exproto) != EFI_SUCCESS) { + if (gBS->HandleProtocol(gST->ConsoleInHandle, &sproto_guid, (void **)&sproto) != EFI_SUCCESS) { + if (gST->ConIn != NULL) { + sproto = gST->ConIn; + } else { + panic(false, "Your input device doesn't have an input protocol!"); + } + } + + events[0] = sproto->WaitForKey; + + use_sproto = true; + } else { + events[0] = exproto->WaitForKeyEx; + } + + gBS->CreateEvent(EVT_TIMER, TPL_CALLBACK, NULL, NULL, &events[1]); + + gBS->SetTimer(events[1], TimerRelative, (uint64_t)10000000 * seconds); + +again: + memset(&kd, 0, sizeof(EFI_KEY_DATA)); + + gBS->WaitForEvent(2, events, &which); + + if (which == 1) { + gBS->CloseEvent(events[1]); + return 0; + } + + EFI_STATUS status; + if (use_sproto) { + status = sproto->ReadKeyStroke(sproto, &kd.Key); + } else { + status = exproto->ReadKeyStrokeEx(exproto, &kd); + } + + if (status != EFI_SUCCESS) { + goto again; + } + + if ((kd.KeyState.KeyShiftState & EFI_SHIFT_STATE_VALID) == 0) { + kd.KeyState.KeyShiftState = 0; + } + + if (serial == true && kd.Key.ScanCode == 0x08) { + gBS->CloseEvent(events[1]); + return '\b'; + } + + if (kd.Key.ScanCode == SCAN_ESC) { + gBS->CloseEvent(events[1]); + + gBS->CreateEvent(EVT_TIMER, TPL_CALLBACK, NULL, NULL, &events[1]); + + gBS->SetTimer(events[1], TimerRelative, 100000); + + gBS->WaitForEvent(2, events, &which); + + if (which == 1) { + gBS->CloseEvent(events[1]); + return GETCHAR_ESCAPE; + } + + if (use_sproto) { + status = sproto->ReadKeyStroke(sproto, &kd.Key); + } else { + status = exproto->ReadKeyStrokeEx(exproto, &kd); + } + + gBS->CloseEvent(events[1]); + gBS->CreateEvent(EVT_TIMER, TPL_CALLBACK, NULL, NULL, &events[1]); + gBS->SetTimer(events[1], TimerRelative, (uint64_t)10000000 * seconds); + + if (status != EFI_SUCCESS) { + goto again; + } + + if (kd.Key.UnicodeChar == '[') { + gBS->CloseEvent(events[1]); + return input_sequence(!use_sproto, exproto, sproto); + } + } + + int ret = getchar_internal(kd.Key.ScanCode, kd.Key.UnicodeChar, + kd.KeyState.KeyShiftState); + + if (ret == -1) { + goto again; + } + + gBS->CloseEvent(events[1]); + return ret; +} +#endif diff --git a/limine/common/lib/getchar.h b/limine/common/lib/getchar.h new file mode 100644 index 0000000..4bbb46d --- /dev/null +++ b/limine/common/lib/getchar.h @@ -0,0 +1,28 @@ +#ifndef LIB__GETCHAR_H__ +#define LIB__GETCHAR_H__ + +#include + +#define GETCHAR_CURSOR_LEFT (-10) +#define GETCHAR_CURSOR_RIGHT (-11) +#define GETCHAR_CURSOR_UP (-12) +#define GETCHAR_CURSOR_DOWN (-13) +#define GETCHAR_DELETE (-14) +#define GETCHAR_END (-15) +#define GETCHAR_HOME (-16) +#define GETCHAR_PGUP (-17) +#define GETCHAR_PGDOWN (-18) +#define GETCHAR_F10 (-19) +#define GETCHAR_ESCAPE (-20) + +#if defined (BIOS) +# define GETCHAR_RCTRL 0x4 +# define GETCHAR_LCTRL GETCHAR_RCTRL +#elif defined (UEFI) +# define GETCHAR_RCTRL EFI_RIGHT_CONTROL_PRESSED +# define GETCHAR_LCTRL EFI_LEFT_CONTROL_PRESSED +#endif + +int getchar(void); + +#endif diff --git a/limine/common/lib/gterm.c b/limine/common/lib/gterm.c new file mode 100644 index 0000000..d44b64a --- /dev/null +++ b/limine/common/lib/gterm.c @@ -0,0 +1,907 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Builtin font originally taken from: +// https://github.com/viler-int10h/vga-text-mode-fonts/raw/master/FONTS/PC-OTHER/TOSH-SAT.F16 +static const uint8_t builtin_font[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x3c, 0x42, 0x81, 0x81, 0xa5, 0xa5, 0x81, 0x81, 0xa5, 0x99, 0x81, 0x42, 0x3c, 0x00, 0x00, + 0x00, 0x3c, 0x7e, 0xff, 0xff, 0xdb, 0xdb, 0xff, 0xff, 0xdb, 0xe7, 0xff, 0x7e, 0x3c, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x6c, 0xfe, 0xfe, 0xfe, 0x7c, 0x7c, 0x38, 0x38, 0x10, 0x10, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x10, 0x38, 0x38, 0x7c, 0x7c, 0xfe, 0x7c, 0x7c, 0x38, 0x38, 0x10, 0x10, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x18, 0x3c, 0x3c, 0xdb, 0xff, 0xff, 0xdb, 0x18, 0x18, 0x3c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x18, 0x3c, 0x7e, 0xff, 0xff, 0xff, 0x66, 0x18, 0x18, 0x3c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x78, 0x78, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe7, 0xc3, 0xc3, 0xe7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0xcc, 0x84, 0x84, 0xcc, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xc3, 0x99, 0xbd, 0xbd, 0x99, 0xc3, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x1e, 0x0e, 0x1e, 0x32, 0x78, 0xcc, 0xcc, 0xcc, 0x78, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x78, 0xcc, 0xcc, 0xcc, 0x78, 0x30, 0xfc, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x10, 0x18, 0x1c, 0x1e, 0x16, 0x12, 0x10, 0x10, 0x70, 0xf0, 0xe0, 0x00, 0x00, 0x00, + 0x00, 0x30, 0x38, 0x2c, 0x26, 0x32, 0x3a, 0x2e, 0x26, 0x22, 0x62, 0xe2, 0xc6, 0x0e, 0x0c, 0x00, + 0x00, 0x00, 0x00, 0x18, 0x18, 0xdb, 0x3c, 0xe7, 0x3c, 0xdb, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xfc, 0xf8, 0xf0, 0xe0, 0xc0, 0x80, 0x00, 0x00, + 0x00, 0x02, 0x06, 0x0e, 0x1e, 0x3e, 0x7e, 0xfe, 0x7e, 0x3e, 0x1e, 0x0e, 0x06, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x30, 0x78, 0xfc, 0x30, 0x30, 0x30, 0x30, 0x30, 0xfc, 0x78, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0x00, 0xcc, 0xcc, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7f, 0xdb, 0xdb, 0xdb, 0xdb, 0x7b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7c, 0xc6, 0x60, 0x38, 0x6c, 0xc6, 0xc6, 0x6c, 0x38, 0x0c, 0xc6, 0x7c, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xfe, 0xfe, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x30, 0x78, 0xfc, 0x30, 0x30, 0x30, 0x30, 0x30, 0xfc, 0x78, 0x30, 0xfc, 0x00, 0x00, + 0x00, 0x00, 0x30, 0x78, 0xfc, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0xfc, 0x78, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x0c, 0xfe, 0xfe, 0x0c, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x60, 0xfe, 0xfe, 0x60, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xc0, 0xc0, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x66, 0xff, 0xff, 0x66, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x38, 0x38, 0x7c, 0x7c, 0xfe, 0xfe, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xfe, 0xfe, 0x7c, 0x7c, 0x38, 0x38, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x30, 0x78, 0x78, 0x78, 0x78, 0x30, 0x30, 0x30, 0x00, 0x30, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x6c, 0x6c, 0x6c, 0xfe, 0x6c, 0x6c, 0x6c, 0xfe, 0x6c, 0x6c, 0x6c, 0x00, 0x00, 0x00, + 0x00, 0x18, 0x18, 0x7c, 0xc6, 0xc0, 0xc0, 0x7c, 0x06, 0x06, 0xc6, 0x7c, 0x18, 0x18, 0x00, 0x00, + 0x00, 0x00, 0xc6, 0xc6, 0x0c, 0x0c, 0x18, 0x38, 0x30, 0x60, 0x60, 0xc6, 0xc6, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x38, 0x6c, 0x6c, 0x38, 0x30, 0x76, 0xde, 0xcc, 0xcc, 0xde, 0x76, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x18, 0x18, 0x18, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x18, 0x30, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x30, 0x18, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x60, 0x30, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x30, 0x60, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x6c, 0x38, 0xfe, 0x38, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x7e, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x30, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x06, 0x06, 0x0c, 0x0c, 0x18, 0x38, 0x30, 0x60, 0x60, 0xc0, 0xc0, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xd6, 0xd6, 0xd6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x18, 0x38, 0x78, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7e, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7c, 0xc6, 0x06, 0x06, 0x0c, 0x18, 0x30, 0x60, 0xc0, 0xc0, 0xfe, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7c, 0xc6, 0x06, 0x06, 0x3c, 0x06, 0x06, 0x06, 0x06, 0xc6, 0x7c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x04, 0x0c, 0x1c, 0x3c, 0x6c, 0xcc, 0xfe, 0x0c, 0x0c, 0x0c, 0x0c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xfe, 0xc0, 0xc0, 0xc0, 0xfc, 0x06, 0x06, 0x06, 0x06, 0xc6, 0x7c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x3c, 0x60, 0xc0, 0xc0, 0xfc, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xfe, 0xc6, 0x06, 0x06, 0x0c, 0x18, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0x7e, 0x06, 0x06, 0x06, 0x0c, 0x78, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x18, 0x18, 0x30, 0x00, 0x00, + 0x00, 0x00, 0x06, 0x0c, 0x18, 0x30, 0x60, 0xc0, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x00, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xc0, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x0c, 0x18, 0x30, 0x60, 0xc0, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7c, 0xc6, 0xc6, 0x06, 0x0c, 0x18, 0x30, 0x30, 0x00, 0x30, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xde, 0xde, 0xde, 0xde, 0xc0, 0xc0, 0x7e, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x38, 0x6c, 0xc6, 0xc6, 0xc6, 0xc6, 0xfe, 0xc6, 0xc6, 0xc6, 0xc6, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xfc, 0xc6, 0xc6, 0xc6, 0xfc, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xfc, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7c, 0xc6, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc6, 0x7c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xf8, 0xcc, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xcc, 0xf8, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xfe, 0xc0, 0xc0, 0xc0, 0xfc, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xfe, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xfe, 0xc0, 0xc0, 0xc0, 0xfc, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7c, 0xc6, 0xc0, 0xc0, 0xc0, 0xde, 0xc6, 0xc6, 0xc6, 0xc6, 0x7e, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xfe, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xfc, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0xfc, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x1e, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0xcc, 0xcc, 0x78, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xc6, 0xc6, 0xcc, 0xd8, 0xf0, 0xe0, 0xf0, 0xd8, 0xcc, 0xc6, 0xc6, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xfe, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xc6, 0xc6, 0xee, 0xfe, 0xd6, 0xd6, 0xd6, 0xc6, 0xc6, 0xc6, 0xc6, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xc6, 0xc6, 0xe6, 0xe6, 0xf6, 0xde, 0xce, 0xce, 0xc6, 0xc6, 0xc6, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xfc, 0xc6, 0xc6, 0xc6, 0xc6, 0xfc, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xf6, 0xda, 0x6c, 0x06, 0x00, 0x00, + 0x00, 0x00, 0xfc, 0xc6, 0xc6, 0xc6, 0xc6, 0xfc, 0xd8, 0xcc, 0xcc, 0xc6, 0xc6, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7c, 0xc6, 0xc0, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x06, 0xc6, 0x7c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x6c, 0x38, 0x10, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0xd6, 0xd6, 0xd6, 0xd6, 0xfe, 0x6c, 0x6c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xc6, 0xc6, 0xc6, 0x6c, 0x38, 0x38, 0x38, 0x6c, 0xc6, 0xc6, 0xc6, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0x78, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xfe, 0x06, 0x06, 0x0c, 0x18, 0x30, 0x60, 0xc0, 0xc0, 0xc0, 0xfe, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x78, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x78, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xc0, 0xc0, 0x60, 0x60, 0x30, 0x38, 0x18, 0x0c, 0x0c, 0x06, 0x06, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x78, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x78, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x10, 0x38, 0x6c, 0xc6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, + 0x00, 0x00, 0x18, 0x18, 0x18, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x06, 0x06, 0x7e, 0xc6, 0xc6, 0xc6, 0x7e, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xc0, 0xc0, 0xc0, 0xdc, 0xe6, 0xc6, 0xc6, 0xc6, 0xc6, 0xe6, 0xdc, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0xc6, 0xc0, 0xc0, 0xc0, 0xc0, 0xc6, 0x7c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x06, 0x06, 0x06, 0x76, 0xce, 0xc6, 0xc6, 0xc6, 0xc6, 0xce, 0x76, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0xc6, 0xc6, 0xfe, 0xc0, 0xc0, 0xc0, 0x7e, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x1c, 0x36, 0x30, 0x30, 0xfc, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0xce, 0xc6, 0xc6, 0xc6, 0xce, 0x76, 0x06, 0xc6, 0x7c, 0x00, + 0x00, 0x00, 0xc0, 0xc0, 0xc0, 0xdc, 0xe6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x18, 0x00, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x06, 0x00, 0x00, 0x1e, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0xc6, 0xc6, 0x7c, 0x00, + 0x00, 0x00, 0xc0, 0xc0, 0xc0, 0xc6, 0xcc, 0xd8, 0xf0, 0xf0, 0xd8, 0xcc, 0xc6, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xec, 0xfe, 0xd6, 0xd6, 0xd6, 0xd6, 0xc6, 0xc6, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xdc, 0xe6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xdc, 0xe6, 0xc6, 0xc6, 0xc6, 0xe6, 0xdc, 0xc0, 0xc0, 0xc0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0xce, 0xc6, 0xc6, 0xc6, 0xce, 0x76, 0x06, 0x06, 0x06, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xdc, 0xe6, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0xc6, 0xc0, 0x70, 0x1c, 0x06, 0xc6, 0x7c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x30, 0x30, 0x30, 0xfe, 0x30, 0x30, 0x30, 0x30, 0x30, 0x36, 0x1c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x6c, 0x38, 0x10, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xc6, 0xc6, 0xd6, 0xd6, 0xd6, 0xd6, 0xfe, 0x6c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xc6, 0xc6, 0x6c, 0x38, 0x38, 0x6c, 0xc6, 0xc6, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xce, 0x76, 0x06, 0xc6, 0x7c, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x06, 0x0c, 0x18, 0x30, 0x60, 0xc0, 0xfe, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x1c, 0x30, 0x30, 0x30, 0x30, 0xe0, 0x30, 0x30, 0x30, 0x30, 0x1c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xe0, 0x30, 0x30, 0x30, 0x30, 0x1c, 0x30, 0x30, 0x30, 0x30, 0xe0, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x76, 0xdc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x10, 0x10, 0x38, 0x38, 0x6c, 0x6c, 0xc6, 0xc6, 0xfe, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x3c, 0x66, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0x66, 0x3c, 0x18, 0xcc, 0x78, 0x00, + 0x00, 0x00, 0x6c, 0x6c, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, + 0x00, 0x0c, 0x18, 0x30, 0x00, 0x7c, 0xc6, 0xc6, 0xfe, 0xc0, 0xc0, 0xc0, 0x7e, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x38, 0x6c, 0x00, 0x7c, 0x06, 0x06, 0x7e, 0xc6, 0xc6, 0xc6, 0x7e, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x6c, 0x6c, 0x00, 0x7c, 0x06, 0x06, 0x7e, 0xc6, 0xc6, 0xc6, 0x7e, 0x00, 0x00, 0x00, + 0x00, 0x60, 0x30, 0x18, 0x00, 0x7c, 0x06, 0x06, 0x7e, 0xc6, 0xc6, 0xc6, 0x7e, 0x00, 0x00, 0x00, + 0x00, 0x38, 0x6c, 0x38, 0x00, 0x7c, 0x06, 0x06, 0x7e, 0xc6, 0xc6, 0xc6, 0x7e, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0xc6, 0xc0, 0xc0, 0xc0, 0xc0, 0xc6, 0x7c, 0x18, 0x0c, 0x38, + 0x00, 0x10, 0x38, 0x6c, 0x00, 0x7c, 0xc6, 0xc6, 0xfe, 0xc0, 0xc0, 0xc0, 0x7e, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x6c, 0x6c, 0x00, 0x7c, 0xc6, 0xc6, 0xfe, 0xc0, 0xc0, 0xc0, 0x7e, 0x00, 0x00, 0x00, + 0x00, 0x60, 0x30, 0x18, 0x00, 0x7c, 0xc6, 0xc6, 0xfe, 0xc0, 0xc0, 0xc0, 0x7e, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x6c, 0x6c, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x38, 0x6c, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00, 0x00, 0x00, + 0x00, 0x60, 0x30, 0x18, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00, 0x00, 0x00, + 0xc6, 0xc6, 0x10, 0x38, 0x6c, 0xc6, 0xc6, 0xc6, 0xfe, 0xc6, 0xc6, 0xc6, 0xc6, 0x00, 0x00, 0x00, + 0x38, 0x6c, 0x38, 0x00, 0x38, 0x6c, 0xc6, 0xc6, 0xc6, 0xfe, 0xc6, 0xc6, 0xc6, 0x00, 0x00, 0x00, + 0x18, 0x30, 0x60, 0x00, 0xfe, 0xc0, 0xc0, 0xfc, 0xc0, 0xc0, 0xc0, 0xc0, 0xfe, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xec, 0x36, 0x36, 0x76, 0xde, 0xd8, 0xd8, 0x6e, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x1e, 0x3c, 0x6c, 0xcc, 0xcc, 0xfe, 0xcc, 0xcc, 0xcc, 0xcc, 0xce, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x38, 0x6c, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x6c, 0x6c, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, + 0x00, 0x60, 0x30, 0x18, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x38, 0x6c, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, + 0x00, 0x60, 0x30, 0x18, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x6c, 0x6c, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xce, 0x76, 0x06, 0xc6, 0x7c, 0x00, + 0x6c, 0x6c, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, + 0x6c, 0x6c, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x30, 0x30, 0x78, 0xcc, 0xc0, 0xc0, 0xcc, 0x78, 0x30, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x38, 0x6c, 0x60, 0x60, 0x60, 0xf8, 0x60, 0x60, 0x60, 0xe6, 0xfc, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xcc, 0xcc, 0xcc, 0x78, 0x30, 0xfc, 0x30, 0xfc, 0x30, 0x30, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xf8, 0xcc, 0xcc, 0xf8, 0xc4, 0xcc, 0xde, 0xcc, 0xcc, 0xcc, 0xc6, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x0e, 0x1b, 0x18, 0x18, 0x18, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0xd8, 0x70, 0x00, + 0x00, 0x0c, 0x18, 0x30, 0x00, 0x7c, 0x06, 0x06, 0x7e, 0xc6, 0xc6, 0xc6, 0x7e, 0x00, 0x00, 0x00, + 0x00, 0x0c, 0x18, 0x30, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00, 0x00, 0x00, + 0x00, 0x0c, 0x18, 0x30, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, + 0x00, 0x0c, 0x18, 0x30, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, + 0x00, 0x76, 0xdc, 0x00, 0x00, 0xdc, 0xe6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x00, 0x00, 0x00, + 0x76, 0xdc, 0x00, 0xc6, 0xc6, 0xe6, 0xf6, 0xfe, 0xde, 0xce, 0xc6, 0xc6, 0xc6, 0x00, 0x00, 0x00, + 0x00, 0x78, 0xd8, 0xd8, 0x6c, 0x00, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x38, 0x6c, 0x6c, 0x38, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x18, 0x18, 0x00, 0x18, 0x18, 0x30, 0x60, 0xc0, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xc0, 0xc0, 0xc0, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x06, 0x06, 0x06, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xc0, 0xc2, 0xc6, 0xcc, 0xd8, 0x30, 0x60, 0xdc, 0x86, 0x0c, 0x18, 0x3e, 0x00, 0x00, + 0x00, 0x00, 0xc0, 0xc2, 0xc6, 0xcc, 0xd8, 0x30, 0x66, 0xce, 0x9e, 0x3e, 0x06, 0x06, 0x00, 0x00, + 0x00, 0x00, 0x30, 0x30, 0x00, 0x30, 0x30, 0x30, 0x78, 0x78, 0x78, 0x78, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x6c, 0xd8, 0x6c, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xd8, 0x6c, 0x36, 0x6c, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x22, 0x88, 0x22, 0x88, 0x22, 0x88, 0x22, 0x88, 0x22, 0x88, 0x22, 0x88, 0x22, 0x88, 0x22, 0x88, + 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, + 0xdd, 0x77, 0xdd, 0x77, 0xdd, 0x77, 0xdd, 0x77, 0xdd, 0x77, 0xdd, 0x77, 0xdd, 0x77, 0xdd, 0x77, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xf8, 0xf8, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0xf8, 0xf8, 0x18, 0x18, 0xf8, 0xf8, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0xf6, 0xf6, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xfe, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0xf8, 0x18, 0x18, 0xf8, 0xf8, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x36, 0x36, 0x36, 0x36, 0x36, 0xf6, 0xf6, 0x06, 0x06, 0xf6, 0xf6, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xfe, 0x06, 0x06, 0xf6, 0xf6, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0xf6, 0xf6, 0x06, 0x06, 0xfe, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0xfe, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x18, 0x18, 0x18, 0x18, 0x18, 0xf8, 0xf8, 0x18, 0x18, 0xf8, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0xf8, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1f, 0x1f, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xff, 0xff, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x1f, 0x1f, 0x18, 0x18, 0x1f, 0x1f, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x37, 0x37, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x37, 0x37, 0x30, 0x30, 0x3f, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x3f, 0x30, 0x30, 0x37, 0x37, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0xf7, 0xf7, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xf7, 0xf7, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x37, 0x37, 0x30, 0x30, 0x37, 0x37, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x36, 0x36, 0x36, 0x36, 0x36, 0xf7, 0xf7, 0x00, 0x00, 0xf7, 0xf7, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x18, 0x18, 0x18, 0x18, 0x18, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x3f, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x1f, 0x1f, 0x18, 0x18, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x18, 0x18, 0x1f, 0x1f, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x3f, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0xff, 0xff, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x18, 0x18, 0x18, 0x18, 0x18, 0xff, 0xff, 0x18, 0x18, 0xff, 0xff, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xf8, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0xd6, 0xdc, 0xc8, 0xc8, 0xdc, 0xd6, 0x76, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x78, 0xcc, 0xcc, 0xcc, 0xd8, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xd8, 0xc0, 0xc0, 0x00, + 0x00, 0x00, 0xfe, 0xc6, 0xc6, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0xfe, 0x24, 0x24, 0x24, 0x24, 0x66, 0xc6, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xfe, 0xfe, 0xc2, 0x60, 0x30, 0x18, 0x30, 0x60, 0xc2, 0xfe, 0xfe, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0xc8, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0x78, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x76, 0x6c, 0x60, 0xc0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0xfc, 0x98, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xfc, 0x30, 0x30, 0x78, 0xcc, 0xcc, 0xcc, 0x78, 0x30, 0x30, 0xfc, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x38, 0x6c, 0xc6, 0xc6, 0xc6, 0xfe, 0xc6, 0xc6, 0xc6, 0x6c, 0x38, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x38, 0x6c, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x6c, 0x6c, 0x6c, 0xee, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x78, 0xcc, 0x60, 0x30, 0x78, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0x78, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0xbb, 0x99, 0x99, 0xdd, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x06, 0x3c, 0x6c, 0xce, 0xd6, 0xd6, 0xe6, 0x6c, 0x78, 0xc0, 0x80, 0x00, 0x00, + 0x00, 0x00, 0x1e, 0x30, 0x60, 0xc0, 0xc0, 0xfe, 0xc0, 0xc0, 0x60, 0x30, 0x1e, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x38, 0x6c, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 0xfe, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x30, 0xfc, 0x30, 0x30, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x60, 0x30, 0x18, 0x0c, 0x18, 0x30, 0x60, 0x00, 0xfc, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x18, 0x30, 0x60, 0xc0, 0x60, 0x30, 0x18, 0x00, 0xfc, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x1c, 0x36, 0x36, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xd8, 0xd8, 0x70, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x30, 0x30, 0x00, 0xfc, 0x00, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0xdc, 0x00, 0x76, 0xdc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x78, 0xcc, 0xcc, 0xcc, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x0f, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0xcc, 0x6c, 0x3c, 0x1c, 0x0c, 0x00, 0x00, + 0x00, 0xd8, 0xec, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x38, 0x6c, 0x0c, 0x18, 0x30, 0x60, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +static struct image *background; + +static size_t margin = 64; +static size_t margin_gradient = 4; + +static uint32_t default_bg, default_fg; +static uint32_t default_bg_bright, default_fg_bright; + +static size_t bg_canvas_size; +static uint32_t *bg_canvas; + +#define A(rgb) (uint8_t)(rgb >> 24) +#define R(rgb) (uint8_t)(rgb >> 16) +#define G(rgb) (uint8_t)(rgb >> 8) +#define B(rgb) (uint8_t)(rgb) +#define ARGB(a, r, g, b) (((a) << 24) | (((r) & 0xFF) << 16) | (((g) & 0xFF) << 8) | ((b) & 0xFF)) + +static inline uint32_t colour_blend(uint32_t fg, uint32_t bg) { + unsigned alpha = 255 - A(fg); + unsigned inv_alpha = A(fg) + 1; + + uint8_t r = (uint8_t)((alpha * R(fg) + inv_alpha * R(bg)) / 256); + uint8_t g = (uint8_t)((alpha * G(fg) + inv_alpha * G(bg)) / 256); + uint8_t b = (uint8_t)((alpha * B(fg) + inv_alpha * B(bg)) / 256); + + return ARGB(0, r, g, b); +} + +static uint32_t blend_gradient_from_box(struct fb_info *fb, size_t x, size_t y, uint32_t bg_px, uint32_t hex) { + size_t distance, x_distance, y_distance; + size_t gradient_stop_x = fb->framebuffer_width - margin; + size_t gradient_stop_y = fb->framebuffer_height - margin; + + if (x < margin) + x_distance = margin - x; + else + x_distance = x - gradient_stop_x; + + if (y < margin) + y_distance = margin - y; + else + y_distance = y - gradient_stop_y; + + if (x >= margin && x < gradient_stop_x) { + distance = y_distance; + } else if (y >= margin && y < gradient_stop_y) { + distance = x_distance; + } else { + distance = sqrt((uint64_t)x_distance * (uint64_t)x_distance + + (uint64_t)y_distance * (uint64_t)y_distance); + } + + if (distance > margin_gradient) + return bg_px; + + uint8_t gradient_step = (0xff - A(hex)) / margin_gradient; + uint8_t new_alpha = A(hex) + gradient_step * distance; + + return colour_blend((hex & 0xffffff) | (new_alpha << 24), bg_px); +} + +typedef size_t fixedp6; // the last 6 bits are the fixed point part +static size_t fixedp6_to_int(fixedp6 value) { return value / 64; } +static fixedp6 int_to_fixedp6(size_t value) { return value * 64; } + +// Draw rect at coordinates, copying from the image to the fb and canvas, applying fn on every pixel +__attribute__((always_inline)) static inline void genloop(struct fb_info *fb, size_t xstart, size_t xend, size_t ystart, size_t yend, uint32_t (*blend)(struct fb_info *fb, size_t x, size_t y, uint32_t orig)) { + uint8_t *img = background->img; + const size_t img_width = background->img_width, img_height = background->img_height, img_pitch = background->pitch, colsize = background->bpp / 8; + + if (xstart > xend) { + size_t tmp = xstart; + xstart = xend; + xend = tmp; + } + if (ystart > yend) { + size_t tmp = ystart; + ystart = yend; + yend = tmp; + } + + switch (background->type) { + case IMAGE_TILED: + for (size_t y = ystart; y < yend; y++) { + size_t image_y = y % img_height, image_x = xstart % img_width; + const size_t off = img_pitch * image_y; + size_t canvas_off = fb->framebuffer_width * y; + for (size_t x = xstart; x < xend; x++) { + uint32_t img_pixel = *(uint32_t*)(img + image_x * colsize + off); + uint32_t i = blend(fb, x, y, img_pixel); + bg_canvas[canvas_off + x] = i; + if (++image_x == img_width) image_x = 0; // image_x = x % img_width, but modulo is too expensive + } + } + break; + + case IMAGE_CENTERED: + for (size_t y = ystart; y < yend; y++) { + int64_t image_y = (int64_t)y - background->y_displacement; + size_t canvas_off = fb->framebuffer_width * y; + if (image_y < 0 || (uint64_t)image_y >= background->y_size) { /* external part */ + for (size_t x = xstart; x < xend; x++) { + uint32_t i = blend(fb, x, y, background->back_colour); + bg_canvas[canvas_off + x] = i; + } + } + else { /* internal part */ + const size_t off = img_pitch * (size_t)image_y; + for (size_t x = xstart; x < xend; x++) { + uint32_t pixel; + int64_t image_x = (int64_t)x - background->x_displacement; + if (image_x < 0 || (uint64_t)image_x >= background->x_size) { + pixel = background->back_colour; + } else { + pixel = *(uint32_t*)(img + (size_t)image_x * colsize + off); + } + uint32_t i = blend(fb, x, y, pixel); + bg_canvas[canvas_off + x] = i; + } + } + } + break; + // For every pixel, ratio = img_width / gterm_width, img_x = x * ratio, x = (xstart + i) + // hence x = xstart * ratio + i * ratio + // so you can set x = xstart * ratio, and increment by ratio at each iteration + case IMAGE_STRETCHED: + for (size_t y = ystart; y < yend; y++) { + size_t img_y = (y * img_height) / fb->framebuffer_height; // calculate Y with full precision + size_t off = img_pitch * img_y; + size_t canvas_off = fb->framebuffer_width * y; + + size_t ratio = int_to_fixedp6(img_width) / fb->framebuffer_width; + fixedp6 img_x = ratio * xstart; + for (size_t x = xstart; x < xend; x++) { + uint32_t img_pixel = *(uint32_t*)(img + fixedp6_to_int(img_x) * colsize + off); + uint32_t i = blend(fb, x, y, img_pixel); + bg_canvas[canvas_off + x] = i; + img_x += ratio; + } + } + break; + } +} + +static uint32_t blend_external(struct fb_info *fb, size_t x, size_t y, uint32_t orig) { (void)fb; (void)x; (void)y; return orig; } +static uint32_t blend_internal(struct fb_info *fb, size_t x, size_t y, uint32_t orig) { (void)fb; (void)x; (void)y; return colour_blend(default_bg, orig); } +static uint32_t blend_margin(struct fb_info *fb, size_t x, size_t y, uint32_t orig) { return blend_gradient_from_box(fb, x, y, orig, default_bg); } + +static void loop_external(struct fb_info *fb, size_t xstart, size_t xend, size_t ystart, size_t yend) { genloop(fb, xstart, xend, ystart, yend, blend_external); } +static void loop_margin(struct fb_info *fb, size_t xstart, size_t xend, size_t ystart, size_t yend) { genloop(fb, xstart, xend, ystart, yend, blend_margin); } +static void loop_internal(struct fb_info *fb, size_t xstart, size_t xend, size_t ystart, size_t yend) { genloop(fb, xstart, xend, ystart, yend, blend_internal); } + +static void generate_canvas(struct fb_info *fb) { + if (background) { + // Free previous canvas if it exists + if (bg_canvas != NULL) { + pmm_free(bg_canvas, bg_canvas_size); + } + bg_canvas_size = fb->framebuffer_width * fb->framebuffer_height * sizeof(uint32_t); + bg_canvas = ext_mem_alloc(bg_canvas_size); + + // Clamp margin to half the framebuffer dimensions to prevent underflow + size_t max_margin = fb->framebuffer_width / 2; + if (fb->framebuffer_height / 2 < max_margin) { + max_margin = fb->framebuffer_height / 2; + } + size_t effective_margin = margin > max_margin ? max_margin : margin; + size_t effective_margin_gradient = margin_gradient > effective_margin ? effective_margin : margin_gradient; + + int64_t margin_no_gradient = (int64_t)effective_margin - effective_margin_gradient; + + if (margin_no_gradient < 0) { + margin_no_gradient = 0; + } + + size_t scan_stop_x = fb->framebuffer_width - margin_no_gradient; + size_t scan_stop_y = fb->framebuffer_height - margin_no_gradient; + + loop_external(fb, 0, fb->framebuffer_width, 0, margin_no_gradient); + loop_external(fb, 0, fb->framebuffer_width, scan_stop_y, fb->framebuffer_height); + loop_external(fb, 0, margin_no_gradient, margin_no_gradient, scan_stop_y); + loop_external(fb, scan_stop_x, fb->framebuffer_width, margin_no_gradient, scan_stop_y); + + size_t gradient_stop_x = fb->framebuffer_width - effective_margin; + size_t gradient_stop_y = fb->framebuffer_height - effective_margin; + + if (effective_margin_gradient) { + loop_margin(fb, margin_no_gradient, scan_stop_x, margin_no_gradient, effective_margin); + loop_margin(fb, margin_no_gradient, scan_stop_x, gradient_stop_y, scan_stop_y); + loop_margin(fb, margin_no_gradient, effective_margin, effective_margin, gradient_stop_y); + loop_margin(fb, gradient_stop_x, scan_stop_x, effective_margin, gradient_stop_y); + } + + loop_internal(fb, effective_margin, gradient_stop_x, effective_margin, gradient_stop_y); + } else { + bg_canvas = NULL; + } +} + +#if defined (__riscv) +__attribute__((target("arch=+zicbom"))) +static void riscv_flush_callback(volatile void *base, size_t length) { + const size_t cbom_block_size = 0x40; + uintptr_t start = ALIGN_DOWN((uintptr_t)base, cbom_block_size); + uintptr_t end = ALIGN_UP((uintptr_t)(base + length), cbom_block_size); + for (uintptr_t ptr = start; ptr < end; ptr += cbom_block_size) { + asm volatile("cbo.flush (%0)" :: "r"(ptr) : "memory"); + } +} +static void riscv_flush_callback_nozicbom(volatile void *base, size_t length) { + (void)base; + (void)length; + + // Without Zicbom, there is no portable instruction to flush dirty cache lines. + // Read through a dedicated eviction buffer to create cache pressure and displace + // dirty framebuffer lines. 128 KB covers typical RISC-V L1 D-caches (32-64 KB). + static volatile uint8_t *eviction_buf = NULL; + #define EVICTION_BUF_SIZE (128 * 1024) + if (eviction_buf == NULL) { + eviction_buf = ext_mem_alloc(EVICTION_BUF_SIZE); + } + + volatile uint64_t *p = (volatile uint64_t *)eviction_buf; + for (size_t i = 0; i < EVICTION_BUF_SIZE / sizeof(uint64_t); i += (64 / sizeof(uint64_t))) { + (void)p[i]; + } + asm volatile ("fence rw, rw" ::: "memory"); +} +#elif defined (__aarch64__) +static void aarch64_flush_callback(volatile void *base, size_t length) { + clean_dcache_poc((uintptr_t)base, (uintptr_t)base + length); +} +#endif + +bool gterm_init(struct fb_info **_fbs, size_t *_fbs_count, + char *config, size_t width, size_t height) { + static struct fb_info *fbs; + static size_t fbs_count; + + static bool prev_valid = false; + static char *prev_config; + static size_t prev_width, prev_height; + + if (prev_valid && config == prev_config && width == prev_width && height == prev_height) { + *_fbs = fbs; + *_fbs_count = fbs_count; + reset_term(); + return true; + } + + prev_valid = false; + + if (quiet) { + term_notready(); + return false; + } + +#if defined (UEFI) + if (serial || COM_OUTPUT) { + term_fallback(); + return true; + } +#endif + + term_notready(); + + // We force bpp to 32 + fb_init(&fbs, &fbs_count, width, height, 32); + + if (_fbs != NULL) { + *_fbs = fbs; + } + if (_fbs_count != NULL) { + *_fbs_count = fbs_count; + } + + if (fbs_count == 0) { + return false; + } + + // default scheme + margin = 64; + margin_gradient = 4; + + int fb_rotation = FLANTERM_FB_ROTATE_0; + char *rotation_str = config_get_value(config, 0, "INTERFACE_ROTATION"); + if (rotation_str != NULL) { + int rotation_val = strtoui(rotation_str, NULL, 10); + switch (rotation_val) { + case 90: fb_rotation = FLANTERM_FB_ROTATE_90; break; + case 180: fb_rotation = FLANTERM_FB_ROTATE_180; break; + case 270: fb_rotation = FLANTERM_FB_ROTATE_270; break; + } + } + + uint32_t ansi_colours[8]; + + ansi_colours[0] = 0x00000000; // black + ansi_colours[1] = 0x00aa0000; // red + ansi_colours[2] = 0x0000aa00; // green + ansi_colours[3] = 0x00aa5500; // brown + ansi_colours[4] = 0x000000aa; // blue + ansi_colours[5] = 0x00aa00aa; // magenta + ansi_colours[6] = 0x0000aaaa; // cyan + ansi_colours[7] = 0x00aaaaaa; // grey + + char *colours = config_get_value(config, 0, "TERM_PALETTE"); + if (colours != NULL) { + const char *first = colours; + size_t i; + for (i = 0; i < 8; i++) { + const char *last; + uint32_t col = strtoui(first, &last, 16); + if (first == last) + break; + ansi_colours[i] = col & 0xffffff; + if (*last == 0) + break; + first = last + 1; + } + } + + uint32_t ansi_bright_colours[8]; + + ansi_bright_colours[0] = 0x00555555; // black + ansi_bright_colours[1] = 0x00ff5555; // red + ansi_bright_colours[2] = 0x0055ff55; // green + ansi_bright_colours[3] = 0x00ffff55; // brown + ansi_bright_colours[4] = 0x005555ff; // blue + ansi_bright_colours[5] = 0x00ff55ff; // magenta + ansi_bright_colours[6] = 0x0055ffff; // cyan + ansi_bright_colours[7] = 0x00ffffff; // grey + + char *bright_colours = config_get_value(config, 0, "TERM_PALETTE_BRIGHT"); + if (bright_colours != NULL) { + const char *first = bright_colours; + size_t i; + for (i = 0; i < 8; i++) { + const char *last; + uint32_t col = strtoui(first, &last, 16); + if (first == last) + break; + ansi_bright_colours[i] = col & 0xffffff; + if (*last == 0) + break; + first = last + 1; + } + } + + default_bg = 0x00000000; // background (black) + default_fg = 0x00aaaaaa; // foreground (grey) + + default_bg_bright = 0x00555555; // background (black) + default_fg_bright = 0x00ffffff; // foreground (grey) + + char *theme_background = config_get_value(config, 0, "TERM_BACKGROUND"); + if (theme_background != NULL) { + default_bg = strtoui(theme_background, NULL, 16); + } + + char *theme_foreground = config_get_value(config, 0, "TERM_FOREGROUND"); + if (theme_foreground != NULL) { + default_fg = strtoui(theme_foreground, NULL, 16) & 0xffffff; + } + + char *theme_background_bright = config_get_value(config, 0, "TERM_BACKGROUND_BRIGHT"); + if (theme_background_bright != NULL) { + default_bg_bright = strtoui(theme_background_bright, NULL, 16); + } + + char *theme_foreground_bright = config_get_value(config, 0, "TERM_FOREGROUND_BRIGHT"); + if (theme_foreground_bright != NULL) { + default_fg_bright = strtoui(theme_foreground_bright, NULL, 16); + } + + size_t wallpaper_count = 0; + while (config_get_value(config, wallpaper_count, "WALLPAPER") != NULL) + wallpaper_count++; + + background = NULL; + if (wallpaper_count > 0) { + char *background_path = config_get_value(config, rand32() % wallpaper_count, "WALLPAPER"); + if (background_path != NULL) { + struct file_handle *bg_file; + if ((bg_file = uri_open(background_path)) != NULL) { + background = image_open(bg_file); + fclose(bg_file); + } + } + } + + if (background == NULL) { + margin = 0; + margin_gradient = 0; + } else { + if (theme_background == NULL) { + default_bg = 0x80000000; + } + } + + char *theme_margin = config_get_value(config, 0, "TERM_MARGIN"); + if (theme_margin != NULL) { + margin = strtoui(theme_margin, NULL, 10); + } + + char *theme_margin_gradient = config_get_value(config, 0, "TERM_MARGIN_GRADIENT"); + if (theme_margin_gradient != NULL) { + margin_gradient = strtoui(theme_margin_gradient, NULL, 10); + } + + if (margin_gradient > margin) { + margin_gradient = margin; + } + + size_t font_width = 8; + size_t font_height = 16; + size_t font_size = (font_width * font_height * FLANTERM_FB_FONT_GLYPHS) / 8; + +#define FONT_MAX 16384 + uint8_t *font = ext_mem_alloc(FONT_MAX); + + memcpy(font, builtin_font, 4096); + + size_t tmp_font_width, tmp_font_height; + + char *menu_font_size = config_get_value(config, 0, "TERM_FONT_SIZE"); + if (menu_font_size != NULL) { + if (!parse_resolution(&tmp_font_width, &tmp_font_height, NULL, menu_font_size)) { + print("Could not parse TERM_FONT_SIZE. Using default font.\n"); + goto no_load_font; + } + + if (tmp_font_width != 8) { + print("Font width must be 8, got %u. Using default font.\n", tmp_font_width); + goto no_load_font; + } + + size_t tmp_font_size = (tmp_font_width * tmp_font_height * FLANTERM_FB_FONT_GLYPHS) / 8; + + if (tmp_font_size > FONT_MAX) { + print("Font would be too large (%U bytes, %u bytes allowed). Not loading.\n", (uint64_t)tmp_font_size, FONT_MAX); + goto no_load_font; + } + + font_size = tmp_font_size; + } + + char *menu_font = config_get_value(config, 0, "TERM_FONT"); + if (menu_font != NULL) { + struct file_handle *f; + if ((f = uri_open(menu_font)) == NULL) { + print("menu: Could not open font file.\n"); + } else { + if (font_size > f->size) { + print("Font size too large for provided font file. Not loading.\n"); + fclose(f); + goto no_load_font; + } + fread(f, font, 0, font_size); + if (menu_font_size != NULL) { + font_width = tmp_font_width; + font_height = tmp_font_height; + } + fclose(f); + } + } + +no_load_font:; + size_t font_spacing = 1; + char *font_spacing_str = config_get_value(config, 0, "TERM_FONT_SPACING"); + if (font_spacing_str != NULL) { + font_spacing = strtoui(font_spacing_str, NULL, 10); + } + + size_t font_scale_x = 1; + size_t font_scale_y = 1; + bool font_scale_is_default = true; + + char *menu_font_scale = config_get_value(config, 0, "TERM_FONT_SCALE"); + if (menu_font_scale != NULL) { + parse_resolution(&font_scale_x, &font_scale_y, NULL, menu_font_scale); + if (font_scale_x > 8 || font_scale_y > 8) { + font_scale_x = 1; + font_scale_y = 1; + } else { + font_scale_is_default = false; + } + } + + terms_i = 0; + terms = ext_mem_alloc(fbs_count * sizeof(void *)); + + for (size_t i = 0; i < fbs_count; i++) { + struct fb_info *fb = &fbs[i]; + + // Ensure that this framebuffer uses 32-bits per pixel. + if (fb->framebuffer_bpp != 32) { + continue; + } + + if (fb_rotation == FLANTERM_FB_ROTATE_90 || fb_rotation == FLANTERM_FB_ROTATE_270) { + uint64_t tmp = fb->framebuffer_width; + fb->framebuffer_width = fb->framebuffer_height; + fb->framebuffer_height = tmp; + } + + if (background != NULL) { + char *background_layout = config_get_value(config, 0, "WALLPAPER_STYLE"); + if (background_layout != NULL && strcmp(background_layout, "centered") == 0) { + char *background_colour = config_get_value(config, 0, "BACKDROP"); + if (background_colour == NULL) + background_colour = "0"; + uint32_t bg_col = strtoui(background_colour, NULL, 16); + image_make_centered(background, fb->framebuffer_width, fb->framebuffer_height, bg_col); + } else if (background_layout != NULL && strcmp(background_layout, "tiled") == 0) { + } else { + image_make_stretched(background, fb->framebuffer_width, fb->framebuffer_height); + } + } + + generate_canvas(fb); + + if (font_scale_is_default) { + font_scale_x = 1; + font_scale_y = 1; + if (fb->framebuffer_width >= (1920 + 1920 / 3) && fb->framebuffer_height >= (1080 + 1080 / 3)) { + font_scale_x = 2; + font_scale_y = 2; + } + if (fb->framebuffer_width >= (3840 + 3840 / 3) && fb->framebuffer_height >= (2160 + 2160 / 3)) { + font_scale_x = 4; + font_scale_y = 4; + } + } + + if (fb_rotation == FLANTERM_FB_ROTATE_90 || fb_rotation == FLANTERM_FB_ROTATE_270) { + uint64_t tmp = fb->framebuffer_width; + fb->framebuffer_width = fb->framebuffer_height; + fb->framebuffer_height = tmp; + } + + terms[terms_i] = flanterm_fb_init(ext_mem_alloc_size_t, + pmm_free_size_t, + (void *)(uintptr_t)fb->framebuffer_addr, + fb->framebuffer_width, fb->framebuffer_height, fb->framebuffer_pitch, + fb->red_mask_size, fb->red_mask_shift, + fb->green_mask_size, fb->green_mask_shift, + fb->blue_mask_size, fb->blue_mask_shift, + bg_canvas, + ansi_colours, ansi_bright_colours, + &default_bg, &default_fg, + &default_bg_bright, &default_fg_bright, + font, font_width, font_height, font_spacing, + font_scale_x, font_scale_y, + margin, fb_rotation); + + if (terms[terms_i] != NULL) { + terms_i++; + } + + if (bg_canvas != NULL) { + pmm_free(bg_canvas, bg_canvas_size); + bg_canvas = NULL; + } + } + + pmm_free(font, FONT_MAX); + + if (background != NULL) { + image_close(background); + background = NULL; + } + + if (terms_i == 0) { + pmm_free(terms, fbs_count * sizeof(void *)); + return false; + } + + for (size_t i = 0; i < terms_i; i++) { + struct flanterm_context *term = terms[i]; + + if (serial) { + term->cols = term->cols > 80 ? 80 : term->cols; + term->rows = term->rows > 24 ? 24 : term->rows; + } + } + + size_t min_cols = (size_t)-1; + size_t min_rows = (size_t)-1; + + for (size_t i = 0; i < terms_i; i++) { + struct flanterm_context *term = terms[i]; + + if (term->cols < min_cols) { + min_cols = term->cols; + } + + if (term->rows < min_rows) { + min_rows = term->rows; + } + } + + for (size_t i = 0; i < terms_i; i++) { + struct flanterm_context *term = terms[i]; + + term->cols = min_cols; + term->rows = min_rows; + + flanterm_context_reinit(term); +#if defined (__riscv) + if (riscv_check_isa_extension("zicbom", NULL, NULL)) { + flanterm_fb_set_flush_callback(term, riscv_flush_callback); + } else { + flanterm_fb_set_flush_callback(term, riscv_flush_callback_nozicbom); + } +#elif defined (__aarch64__) + flanterm_fb_set_flush_callback(term, aarch64_flush_callback); +#endif + } + + term_backend = GTERM; + + prev_config = config; + prev_height = height; + prev_width = width; + prev_valid = true; + + return true; +} diff --git a/limine/common/lib/gterm.h b/limine/common/lib/gterm.h new file mode 100644 index 0000000..ec579dc --- /dev/null +++ b/limine/common/lib/gterm.h @@ -0,0 +1,11 @@ +#ifndef LIB__GTERM_H__ +#define LIB__GTERM_H__ + +#include +#include +#include + +bool gterm_init(struct fb_info **ret, size_t *_fbs_count, + char *config, size_t width, size_t height); + +#endif diff --git a/limine/common/lib/guid.c b/limine/common/lib/guid.c new file mode 100644 index 0000000..06730b0 --- /dev/null +++ b/limine/common/lib/guid.c @@ -0,0 +1,95 @@ +#include +#include +#include +#include +#include + +bool is_valid_guid(const char *s) { + for (size_t i = 0; ; i++) { + switch (i) { + case 8: + case 13: + case 18: + case 23: + if (s[i] != '-') + return false; + break; + case 36: + return s[i] == 0; + default: + if (digit_to_int(s[i]) == -1) + return false; + break; + } + } +} + +static void guid_convert_le_cluster(uint8_t *dest, const char *s, int len) { + size_t p = 0; + for (int i = len - 1; i >= 0; i--) { + int val = digit_to_int(s[i]); + + i % 2 ? (dest[p] = val) : (dest[p++] |= val << 4); + } +} + +static void guid_convert_be_cluster(uint8_t *dest, const char *s, int len) { + size_t p = 0; + for (int i = 0; i < len; i++) { + int val = digit_to_int(s[i]); + + i % 2 ? (dest[p++] |= val) : (dest[p] = val << 4); + } +} + +bool string_to_guid_be(struct guid *guid, const char *s) { + if (!is_valid_guid(s)) + return false; + + guid_convert_be_cluster((uint8_t *)guid + 0, s + 0, 8); + guid_convert_be_cluster((uint8_t *)guid + 4, s + 9, 4); + guid_convert_be_cluster((uint8_t *)guid + 6, s + 14, 4); + guid_convert_be_cluster((uint8_t *)guid + 8, s + 19, 4); + guid_convert_be_cluster((uint8_t *)guid + 10, s + 24, 12); + + return true; +} + +bool string_to_guid_mixed(struct guid *guid, const char *s) { + if (!is_valid_guid(s)) + return false; + + guid_convert_le_cluster((uint8_t *)guid + 0, s + 0, 8); + guid_convert_le_cluster((uint8_t *)guid + 4, s + 9, 4); + guid_convert_le_cluster((uint8_t *)guid + 6, s + 14, 4); + guid_convert_be_cluster((uint8_t *)guid + 8, s + 19, 4); + guid_convert_be_cluster((uint8_t *)guid + 10, s + 24, 12); + + return true; +} + +static void uint_to_hex(uint32_t num, char *dest, int len) { + const char digits[] = "0123456789abcdef"; + for (int i = 0; i < len; i++) { + dest[i] = digits[(num >> ((len - 1 - i) * 4)) & 0xf]; + } +} + +void guid_to_string(const struct guid *guid, char *s) { + uint_to_hex(guid->a, s, 8); + s[8] = '-'; + uint_to_hex(guid->b, s + 9, 4); + s[13] = '-'; + uint_to_hex(guid->c, s + 14, 4); + s[18] = '-'; + uint_to_hex(guid->d[0], s + 19, 2); + uint_to_hex(guid->d[1], s + 21, 2); + s[23] = '-'; + uint_to_hex(guid->d[2], s + 24, 2); + uint_to_hex(guid->d[3], s + 26, 2); + uint_to_hex(guid->d[4], s + 28, 2); + uint_to_hex(guid->d[5], s + 30, 2); + uint_to_hex(guid->d[6], s + 32, 2); + uint_to_hex(guid->d[7], s + 34, 2); + s[36] = 0; +} diff --git a/limine/common/lib/guid.h b/limine/common/lib/guid.h new file mode 100644 index 0000000..82b41c0 --- /dev/null +++ b/limine/common/lib/guid.h @@ -0,0 +1,20 @@ +#ifndef LIB__GUID_H__ +#define LIB__GUID_H__ + +#include +#include + +struct guid { + uint32_t a; + uint16_t b; + uint16_t c; + uint8_t d[8]; +}; + +bool is_valid_guid(const char *s); +bool string_to_guid_be(struct guid *guid, const char *s); +bool string_to_guid_mixed(struct guid *guid, const char *s); +// Assumption: s must be big enough to fit 36 characters and a null byte +void guid_to_string(const struct guid *guid, char *s); + +#endif diff --git a/limine/common/lib/image.c b/limine/common/lib/image.c new file mode 100644 index 0000000..ab9c33a --- /dev/null +++ b/limine/common/lib/image.c @@ -0,0 +1,65 @@ +#include +#include +#include +#include +#include +#include +#include + +void image_make_centered(struct image *image, int frame_x_size, int frame_y_size, uint32_t back_colour) { + image->type = IMAGE_CENTERED; + + image->x_displacement = (int64_t)frame_x_size / 2 - (int64_t)image->x_size / 2; + image->y_displacement = (int64_t)frame_y_size / 2 - (int64_t)image->y_size / 2; + image->back_colour = back_colour; +} + + +void image_make_stretched(struct image *image, int new_x_size, int new_y_size) { + image->type = IMAGE_STRETCHED; + + image->x_size = new_x_size; + image->y_size = new_y_size; +} + +struct image *image_open(struct file_handle *file) { + struct image *image = ext_mem_alloc(sizeof(struct image)); + + image->type = IMAGE_TILED; + + void *src = ext_mem_alloc(file->size); + + fread(file, src, 0, file->size); + + int x, y, bpp; + + image->img = stbi_load_from_memory(src, file->size, &x, &y, &bpp, 4); + + pmm_free(src, file->size); + + if (image->img == NULL) { + pmm_free(image, sizeof(struct image)); + return NULL; + } + + // Convert ABGR to XRGB + uint32_t *pptr = (void *)image->img; + size_t pixel_count = (size_t)x * (size_t)y; + for (size_t i = 0; i < pixel_count; i++) { + pptr[i] = (pptr[i] & 0x0000ff00) | ((pptr[i] & 0x00ff0000) >> 16) | ((pptr[i] & 0x000000ff) << 16); + } + + image->x_size = x; + image->y_size = y; + image->pitch = x * 4; + image->bpp = 32; + image->img_width = x; + image->img_height = y; + + return image; +} + +void image_close(struct image *image) { + stbi_image_free(image->img); + pmm_free(image, sizeof(struct image)); +} diff --git a/limine/common/lib/image.h b/limine/common/lib/image.h new file mode 100644 index 0000000..e5cc296 --- /dev/null +++ b/limine/common/lib/image.h @@ -0,0 +1,32 @@ +#ifndef LIB__IMAGE_H__ +#define LIB__IMAGE_H__ + +#include +#include + +struct image { + size_t x_size; + size_t y_size; + int type; + uint8_t *img; + int bpp; + int pitch; + size_t img_width; // x_size = scaled size, img_width = bitmap size + size_t img_height; + int64_t x_displacement; + int64_t y_displacement; + uint32_t back_colour; +}; + +enum { + IMAGE_TILED, + IMAGE_CENTERED, + IMAGE_STRETCHED +}; + +void image_make_centered(struct image *image, int frame_x_size, int frame_y_size, uint32_t back_colour); +void image_make_stretched(struct image *image, int new_x_size, int new_y_size); +struct image *image_open(struct file_handle *file); +void image_close(struct image *image); + +#endif diff --git a/limine/common/lib/libc.c b/limine/common/lib/libc.c new file mode 100644 index 0000000..d0a3de2 --- /dev/null +++ b/limine/common/lib/libc.c @@ -0,0 +1,131 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +// Slightly adapted strtoul() implementation from FreeBSD. +// https://github.com/freebsd/freebsd-src/blob/de1aa3dab23c06fec962a14da3e7b4755c5880cf/lib/libc/stdlib/strtoul.c +unsigned long strtoul(const char *nptr, char **endptr, int base) { + const char *s; + unsigned long acc; + char c; + unsigned long cutoff; + int neg, any, cutlim; + + s = nptr; + do { + c = *s++; + } while (isspace((unsigned char)c)); + if (c == '-') { + neg = 1; + c = *s++; + } else { + neg = 0; + if (c == '+') + c = *s++; + } + if ((base == 0 || base == 16) && + c == '0' && (*s == 'x' || *s == 'X') && + ((s[1] >= '0' && s[1] <= '9') || + (s[1] >= 'A' && s[1] <= 'F') || + (s[1] >= 'a' && s[1] <= 'f'))) { + c = s[1]; + s += 2; + base = 16; + } + if (base == 0) + base = c == '0' ? 8 : 10; + acc = any = 0; + if (base < 2 || base > 36) + goto noconv; + + cutoff = ULONG_MAX / base; + cutlim = ULONG_MAX % base; + for ( ; ; c = *s++) { + if (c >= '0' && c <= '9') + c -= '0'; + else if (c >= 'A' && c <= 'Z') + c -= 'A' - 10; + else if (c >= 'a' && c <= 'z') + c -= 'a' - 10; + else + break; + if (c >= base) + break; + if (any < 0 || acc > cutoff || (acc == cutoff && c > cutlim)) + any = -1; + else { + any = 1; + acc *= base; + acc += c; + } + } + if (any < 0) { + acc = ULONG_MAX; + //errno = ERANGE; + } else if (!any) { +noconv: + ;//errno = EINVAL; + } else if (neg) + acc = -acc; + if (endptr != NULL) + *endptr = (char *)(any ? s - 1 : nptr); + return (acc); +} + +size_t strnlen(const char *str, size_t maxlen) { + size_t len; + + for (len = 0; len < maxlen && str[len]; len++); + + return len; +} + +void *memchr(const void *ptr, int ch, size_t n) { + uint8_t *p = (uint8_t *)ptr; + + for (size_t i = 0; i < n; i++) { + if (p[i] == ch) { + return (void *)ptr + i; + } + } + + return NULL; +} + +char *strchr(const char *str, int ch) { + for (size_t i = 0; ; i++) { + if (str[i] == (char)ch) { + return (char *)str + i; + } + if (str[i] == '\0') { + return NULL; + } + } +} + +char *strrchr(const char *str, int ch) { + char *p = NULL; + + for (size_t i = 0; ; i++) { + if (str[i] == (char)ch) { + p = (char *)str + i; + } + if (str[i] == '\0') { + break; + } + } + + return p; +} + +char *strdup(const char *s) { + size_t len = strlen(s) + 1; + char *buf = ext_mem_alloc(len); + memcpy(buf, s, len); + return buf; +} diff --git a/limine/common/lib/libc.h b/limine/common/lib/libc.h new file mode 100644 index 0000000..803ffd1 --- /dev/null +++ b/limine/common/lib/libc.h @@ -0,0 +1,36 @@ +#ifndef LIB__LIBC_H__ +#define LIB__LIBC_H__ + +#include +#include + +bool isprint(int c); +bool isspace(int c); +bool isalpha(int c); +bool isdigit(int c); + +int toupper(int c); +int tolower(int c); + +int abs(int i); + +void *memset(void *, int, size_t); +void *memcpy(void *restrict, const void *restrict, size_t); +int memcmp(const void *, const void *, size_t); +void *memmove(void *, const void *, size_t); +void *memchr(const void *, int, size_t); + +char *strcpy(char *, const char *); +char *strncpy(char *, const char *, size_t); +char *strchr(const char *, int); +char *strrchr(const char *, int); +size_t strlen(const char *); +size_t strnlen(const char *, size_t); +int strcmp(const char *, const char *); +int strcasecmp(const char *, const char *); +int strncmp(const char *, const char *, size_t); +int strncasecmp(const char *, const char *, size_t); +int inet_pton(const char *src, void *dst); +char *strdup(const char *); + +#endif diff --git a/limine/common/lib/libc.s2.c b/limine/common/lib/libc.s2.c new file mode 100644 index 0000000..ec2ca8c --- /dev/null +++ b/limine/common/lib/libc.s2.c @@ -0,0 +1,149 @@ +#include +#include +#include +#include +#include + +bool isprint(int c) { + return c >= ' ' && c <= '~'; +} + +bool isspace(int c) { + return (c >= '\t' && c <= 0xD) || c == ' '; +} + +bool isalpha(int c) { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); +} + +bool isdigit(int c) { + return c >= '0' && c <= '9'; +} + +int toupper(int c) { + if (c >= 'a' && c <= 'z') { + return c - 0x20; + } + return c; +} + +int tolower(int c) { + if (c >= 'A' && c <= 'Z') { + return c + 0x20; + } + return c; +} + +int abs(int i) { + return i < 0 ? -i : i; +} + +char *strcpy(char *dest, const char *src) { + size_t i; + + for (i = 0; src[i]; i++) + dest[i] = src[i]; + + dest[i] = 0; + + return dest; +} + +char *strncpy(char *dest, const char *src, size_t n) { + size_t i; + + for (i = 0; i < n && src[i]; i++) + dest[i] = src[i]; + for ( ; i < n; i++) + dest[i] = 0; + + return dest; +} + +int strcmp(const char *s1, const char *s2) { + for (size_t i = 0; ; i++) { + unsigned char c1 = ((unsigned char *)s1)[i], c2 = ((unsigned char *)s2)[i]; + if (c1 != c2) { + return c1 < c2 ? -1 : 1; + } + if (c1 == 0) { + return 0; + } + } +} + +int strcasecmp(const char *s1, const char *s2) { + for (size_t i = 0; ; i++) { + unsigned char c1 = ((unsigned char *)s1)[i], c2 = ((unsigned char *)s2)[i]; + if (tolower(c1) != tolower(c2)) { + return c1 < c2 ? -1 : 1; + } + if (c1 == 0) { + return 0; + } + } +} + +int strncmp(const char *s1, const char *s2, size_t n) { + for (size_t i = 0; i < n; i++) { + unsigned char c1 = ((unsigned char *)s1)[i], c2 = ((unsigned char *)s2)[i]; + if (c1 != c2) { + return c1 < c2 ? -1 : 1; + } + if (c1 == 0) { + return 0; + } + } + + return 0; +} + +int strncasecmp(const char *s1, const char *s2, size_t n) { + for (size_t i = 0; i < n; i++) { + unsigned char c1 = ((unsigned char *)s1)[i], c2 = ((unsigned char *)s2)[i]; + if (tolower(c1) != tolower(c2)) { + return c1 < c2 ? -1 : 1; + } + if (c1 == 0) { + return 0; + } + } + + return 0; +} + +size_t strlen(const char *str) { + size_t len; + + for (len = 0; str[len]; len++); + + return len; +} + +int inet_pton(const char *src, void *dst) { + uint8_t array[4]; + const char *current = src; + + for (int i = 0; i < 4; i++) { + const char *newcur; + uint64_t value = strtoui(current, &newcur, 10); + if (current == newcur) + return -1; + current = newcur; + if (i < 3) { + // Expect '.' delimiter between octets + if (*current != '.') + return -1; + current++; + } else { + // After last octet, string must end + if (*current != 0) + return -1; + } + if (value > 255) + return -1; + array[i] = value; + } + memcpy(dst, array, 4); + return 0; +} diff --git a/limine/common/lib/macros.aarch64_asm.h b/limine/common/lib/macros.aarch64_asm.h new file mode 100644 index 0000000..d59ef06 --- /dev/null +++ b/limine/common/lib/macros.aarch64_asm.h @@ -0,0 +1,52 @@ +// Branch to \el1 if in EL1, or to \el2 if in EL2 +// Uses \reg, halts if not in EL1 or EL2 +.macro PICK_EL reg, el1, el2 + mrs \reg, currentel + and \reg, \reg, #0b1100 + + cmp \reg, #0b0100 // EL1? + b.eq \el1 + cmp \reg, #0b1000 // EL2? + b.eq \el2 + + // Halt otherwise + msr daifset, #0b1111 +99: + wfi + b 99b +.endm + + +// Zero out all general purpose registers apart from X0 +.macro ZERO_REGS_EXCEPT_X0 + mov x1, xzr + mov x2, xzr + mov x3, xzr + mov x4, xzr + mov x5, xzr + mov x6, xzr + mov x7, xzr + mov x8, xzr + mov x9, xzr + mov x10, xzr + mov x11, xzr + mov x12, xzr + mov x13, xzr + mov x14, xzr + mov x15, xzr + mov x16, xzr + mov x17, xzr + mov x18, xzr + mov x19, xzr + mov x20, xzr + mov x21, xzr + mov x22, xzr + mov x23, xzr + mov x24, xzr + mov x25, xzr + mov x26, xzr + mov x27, xzr + mov x28, xzr + mov x29, xzr + mov x30, xzr +.endm diff --git a/limine/common/lib/memory.s2.c b/limine/common/lib/memory.s2.c new file mode 100644 index 0000000..a5353b2 --- /dev/null +++ b/limine/common/lib/memory.s2.c @@ -0,0 +1,53 @@ +#include +#include + +void *memcpy(void *restrict dest, const void *restrict src, size_t n) { + uint8_t *restrict pdest = (uint8_t *restrict)dest; + const uint8_t *restrict psrc = (const uint8_t *restrict)src; + + for (size_t i = 0; i < n; i++) { + pdest[i] = psrc[i]; + } + + return dest; +} + +void *memset(void *s, int c, size_t n) { + uint8_t *p = (uint8_t *)s; + + for (size_t i = 0; i < n; i++) { + p[i] = (uint8_t)c; + } + + return s; +} + +void *memmove(void *dest, const void *src, size_t n) { + uint8_t *pdest = (uint8_t *)dest; + const uint8_t *psrc = (const uint8_t *)src; + + if ((uintptr_t)src > (uintptr_t)dest) { + for (size_t i = 0; i < n; i++) { + pdest[i] = psrc[i]; + } + } else if ((uintptr_t)src < (uintptr_t)dest) { + for (size_t i = n; i > 0; i--) { + pdest[i-1] = psrc[i-1]; + } + } + + return dest; +} + +int memcmp(const void *s1, const void *s2, size_t n) { + const uint8_t *p1 = (const uint8_t *)s1; + const uint8_t *p2 = (const uint8_t *)s2; + + for (size_t i = 0; i < n; i++) { + if (p1[i] != p2[i]) { + return p1[i] < p2[i] ? -1 : 1; + } + } + + return 0; +} diff --git a/limine/common/lib/misc.c b/limine/common/lib/misc.c new file mode 100644 index 0000000..bbe6239 --- /dev/null +++ b/limine/common/lib/misc.c @@ -0,0 +1,417 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined (UEFI) +EFI_SYSTEM_TABLE *gST; +EFI_BOOT_SERVICES *gBS; +EFI_RUNTIME_SERVICES *gRT; +EFI_HANDLE efi_image_handle; +EFI_MEMORY_DESCRIPTOR *efi_mmap = NULL; +UINTN efi_mmap_size = 0, efi_desc_size = 0, efi_mmap_key = 0; +UINT32 efi_desc_ver = 0; +#endif + +bool editor_enabled = true; +bool help_hidden = false; + +uint64_t usec_at_bootloader_entry; + +#if defined (UEFI) +bool is_efi_serial_present(void) { + EFI_STATUS status; + EFI_SERIAL_IO_PROTOCOL *serial_io = NULL; + EFI_GUID serial_io_guid = EFI_SERIAL_IO_PROTOCOL_GUID; + + status = gBS->LocateProtocol(&serial_io_guid, NULL, (void **)&serial_io); + if (status) { + return false; + } + + if (serial_io == NULL) { + return false; + } + + UINT32 control; + status = serial_io->GetControl(serial_io, &control); + if (status) { + return false; + } + + return true; +} +#endif + +bool parse_resolution(size_t *width, size_t *height, size_t *bpp, const char *buf) { + size_t res[3] = {0}; + + const char *first = buf; + for (size_t i = 0; i < 3; i++) { + const char *last; + size_t x = strtoui(first, &last, 10); + if (first == last) + break; + res[i] = x; + if (*last == 0) + break; + first = last + 1; + } + + if (res[0] == 0 || res[1] == 0) + return false; + + if (res[2] == 0) + res[2] = 32; + + *width = res[0], *height = res[1]; + if (bpp != NULL) + *bpp = res[2]; + + return true; +} + +// This integer sqrt implementation has been adapted from: +// https://stackoverflow.com/questions/1100090/looking-for-an-efficient-integer-square-root-algorithm-for-arm-thumb2 +uint64_t sqrt(uint64_t a_nInput) { + uint64_t op = a_nInput; + uint64_t res = 0; + uint64_t one = (uint64_t)1 << 62; + + // "one" starts at the highest power of four <= than the argument. + while (one > op) { + one >>= 2; + } + + while (one != 0) { + if (op >= res + one) { + op = op - (res + one); + res = res + 2 * one; + } + res >>= 1; + one >>= 2; + } + + return res; +} + +size_t get_trailing_zeros(uint64_t val) { + for (size_t i = 0; i < 64; i++) { + if ((val & 1) != 0) { + return i; + } + val >>= 1; + } + return 64; +} + +void *get_device_tree_blob(const char *config, size_t extra_size) { + int ret; + + size_t size = 0; + void *dtb = NULL; + + { + char *dtb_path = NULL; + bool soft_panic; + if (config != NULL) { + dtb_path = config_get_value(config, 0, "dtb_path"); + soft_panic = true; + } + if (dtb_path == NULL) { + dtb_path = config_get_value(NULL, 0, "global_dtb"); + soft_panic = false; + } + if (dtb_path != NULL) { + struct file_handle *dtb_file; + if ((dtb_file = uri_open(dtb_path)) == NULL) + panic(soft_panic, "dtb: Failed to open device tree blob with path `%#`. Is the path correct?", dtb_path); + + dtb = freadall(dtb_file, MEMMAP_BOOTLOADER_RECLAIMABLE); + size = dtb_file->size; + fclose(dtb_file); + printv("dtb: loaded dtb at %p from file `%#`\n", dtb, dtb_path); + } + } + +#if defined (UEFI) + if (!dtb) { + EFI_GUID dtb_guid = EFI_DTB_TABLE_GUID; + for (size_t i = 0; i < gST->NumberOfTableEntries; i++) { + EFI_CONFIGURATION_TABLE *cur_table = &gST->ConfigurationTable[i]; + if (memcmp(&cur_table->VendorGuid, &dtb_guid, sizeof(EFI_GUID))) + continue; + size = fdt_totalsize(cur_table->VendorTable); + dtb = ext_mem_alloc(size); + ret = fdt_open_into(cur_table->VendorTable, dtb, size); + if (ret < 0) { + panic(true, "dtb: failed to resize new DTB"); + } + printv("dtb: found dtb at %p via EFI\n", cur_table->VendorTable); + break; + } + } +#endif + + if (extra_size == 0) { + return dtb; + } + + if (dtb) { + printv("dtb: dtb has size %X\n", (uint64_t)size); + + void *new_tab = ext_mem_alloc(size + extra_size); + + ret = fdt_open_into(dtb, new_tab, size + extra_size); + if (ret < 0) { + panic(true, "dtb: failed to resize new DTB"); + } + + pmm_free(dtb, size); + return new_tab; + } + + dtb = ext_mem_alloc(extra_size); + + ret = fdt_create_empty_tree(dtb, extra_size); + if (ret < 0) { + panic(true, "dtb: failed to create a device tree blob: '%s'", fdt_strerror(ret)); + } + + ret = fdt_setprop_u32(dtb, 0, "#address-cells", 2); + if (ret < 0) { + panic(true, "dtb: failed to set #address-cells: '%s'", fdt_strerror(ret)); + } + + ret = fdt_setprop_u32(dtb, 0, "#size-cells", 1); + if (ret < 0) { + panic(true, "dtb: failed to set #size-cells: '%s'", fdt_strerror(ret)); + } + + return dtb; +} + +#if defined (UEFI) + +#if defined (__riscv) + +RISCV_EFI_BOOT_PROTOCOL *get_riscv_boot_protocol(void) { + EFI_GUID boot_proto_guid = RISCV_EFI_BOOT_PROTOCOL_GUID; + RISCV_EFI_BOOT_PROTOCOL *proto; + + // LocateProtocol() is available from EFI version 1.1 + if (gBS->Hdr.Revision >= ((1 << 16) | 10)) { + if (gBS->LocateProtocol(&boot_proto_guid, NULL, (void **)&proto) == EFI_SUCCESS) { + return proto; + } + } + + UINTN bufsz = 0; + if (gBS->LocateHandle(ByProtocol, &boot_proto_guid, NULL, &bufsz, NULL) != EFI_BUFFER_TOO_SMALL) + return NULL; + + EFI_HANDLE *handles_buf = ext_mem_alloc(bufsz); + if (handles_buf == NULL) + return NULL; + + if (bufsz < sizeof(EFI_HANDLE)) + goto error; + + if (gBS->LocateHandle(ByProtocol, &boot_proto_guid, NULL, &bufsz, handles_buf) != EFI_SUCCESS) + goto error; + + if (gBS->HandleProtocol(handles_buf[0], &boot_proto_guid, (void **)&proto) != EFI_SUCCESS) + goto error; + + pmm_free(handles_buf, bufsz); + return proto; + +error: + pmm_free(handles_buf, bufsz); + return NULL; +} + +#endif + +no_unwind bool efi_boot_services_exited = false; + +bool efi_exit_boot_services(void) { + EFI_STATUS status; + + EFI_MEMORY_DESCRIPTOR tmp_mmap[1]; + efi_mmap_size = sizeof(tmp_mmap); + + gBS->GetMemoryMap(&efi_mmap_size, tmp_mmap, &efi_mmap_key, &efi_desc_size, &efi_desc_ver); + + efi_mmap_size += 4096; + + status = gBS->FreePool(efi_mmap); + if (status) { + goto fail; + } + + status = gBS->AllocatePool(EfiLoaderData, efi_mmap_size, (void **)&efi_mmap); + if (status) { + goto fail; + } + + EFI_MEMORY_DESCRIPTOR *efi_copy; + status = gBS->AllocatePool(EfiLoaderData, efi_mmap_size * 2, (void **)&efi_copy); + if (status) { + goto fail; + } + + bli_on_boot(); + + const size_t EFI_COPY_MAX_ENTRIES = (efi_mmap_size * 2) / efi_desc_size; + + size_t retries = 0; + +retry: + status = gBS->GetMemoryMap(&efi_mmap_size, efi_mmap, &efi_mmap_key, &efi_desc_size, &efi_desc_ver); + if (retries == 0 && status) { + goto fail; + } + + // Be gone, UEFI! + status = gBS->ExitBootServices(efi_image_handle, efi_mmap_key); + if (status) { + if (retries == 128) { + goto fail; + } + retries++; + goto retry; + } + +#if defined(__x86_64__) || defined(__i386__) + asm volatile ("cli" ::: "memory"); +#elif defined (__aarch64__) + asm volatile ("msr daifset, #15" ::: "memory"); +#elif defined (__riscv) + asm volatile ("csrci sstatus, 0x2" ::: "memory"); +#elif defined (__loongarch64) + asm volatile ("csrxchg $r0, %0, 0x0" :: "r" (0x4) : "memory"); +#else +#error Unknown architecture +#endif + + // Go through new EFI memmap and free up bootloader entries + size_t entry_count = efi_mmap_size / efi_desc_size; + + size_t efi_copy_i = 0; + + for (size_t i = 0; i < entry_count; i++) { + EFI_MEMORY_DESCRIPTOR *orig_entry = (void *)efi_mmap + i * efi_desc_size; + EFI_MEMORY_DESCRIPTOR *new_entry = (void *)efi_copy + efi_copy_i * efi_desc_size; + + if (orig_entry->NumberOfPages == 0) { + continue; + } + + memcpy(new_entry, orig_entry, efi_desc_size); + + uint64_t base = orig_entry->PhysicalStart; + uint64_t length = orig_entry->NumberOfPages * 4096; + uint64_t top = base + length; + + // Find for a match in the untouched memory map + for (size_t j = 0; j < untouched_memmap_entries; j++) { + if (untouched_memmap[j].type != MEMMAP_USABLE) + continue; + + if (top > untouched_memmap[j].base && top <= untouched_memmap[j].base + untouched_memmap[j].length) { + if (untouched_memmap[j].base < base) { + new_entry->NumberOfPages = (base - untouched_memmap[j].base) / 4096; + + efi_copy_i++; + if (efi_copy_i == EFI_COPY_MAX_ENTRIES) { + panic(false, "efi: New memory map exhausted"); + } + new_entry = (void *)efi_copy + efi_copy_i * efi_desc_size; + memcpy(new_entry, orig_entry, efi_desc_size); + + new_entry->NumberOfPages -= (base - untouched_memmap[j].base) / 4096; + new_entry->PhysicalStart = base; + new_entry->VirtualStart = 0; + + length = new_entry->NumberOfPages * 4096; + top = base + length; + } + + if (untouched_memmap[j].base > base) { + new_entry->NumberOfPages = (untouched_memmap[j].base - base) / 4096; + + efi_copy_i++; + if (efi_copy_i == EFI_COPY_MAX_ENTRIES) { + panic(false, "efi: New memory map exhausted"); + } + new_entry = (void *)efi_copy + efi_copy_i * efi_desc_size; + memcpy(new_entry, orig_entry, efi_desc_size); + + new_entry->NumberOfPages -= (untouched_memmap[j].base - base) / 4096; + new_entry->PhysicalStart = untouched_memmap[j].base; + new_entry->VirtualStart = 0; + + base = new_entry->PhysicalStart; + length = new_entry->NumberOfPages * 4096; + top = base + length; + } + + if (length < untouched_memmap[j].length) { + panic(false, "efi: Memory map corruption"); + } + + new_entry->Type = EfiConventionalMemory; + + if (length == untouched_memmap[j].length) { + // It's a perfect match! + break; + } + + new_entry->NumberOfPages = untouched_memmap[j].length / 4096; + + efi_copy_i++; + if (efi_copy_i == EFI_COPY_MAX_ENTRIES) { + panic(false, "efi: New memory map exhausted"); + } + new_entry = (void *)efi_copy + efi_copy_i * efi_desc_size; + memcpy(new_entry, orig_entry, efi_desc_size); + + new_entry->NumberOfPages = (length - untouched_memmap[j].length) / 4096; + new_entry->PhysicalStart = base + untouched_memmap[j].length; + new_entry->VirtualStart = 0; + + break; + } + } + + efi_copy_i++; + if (efi_copy_i == EFI_COPY_MAX_ENTRIES) { + panic(false, "efi: New memory map exhausted"); + } + } + + efi_mmap = efi_copy; + efi_mmap_size = efi_copy_i * efi_desc_size; + + efi_boot_services_exited = true; + + printv("efi: Exited boot services.\n"); + + return true; + +fail: + panic(false, "efi: Failed to exit boot services"); +} + +#endif diff --git a/limine/common/lib/misc.h b/limine/common/lib/misc.h new file mode 100644 index 0000000..9123bb0 --- /dev/null +++ b/limine/common/lib/misc.h @@ -0,0 +1,130 @@ +#ifndef LIB__MISC_H__ +#define LIB__MISC_H__ + +#include +#include +#include +#include +#include +#include +#include +#if defined (UEFI) +# include +# if defined (__riscv) +# include +# endif +#endif + +#if defined (UEFI) +extern EFI_SYSTEM_TABLE *gST; +extern EFI_BOOT_SERVICES *gBS; +extern EFI_RUNTIME_SERVICES *gRT; +extern EFI_HANDLE efi_image_handle; +extern EFI_MEMORY_DESCRIPTOR *efi_mmap; +extern UINTN efi_mmap_size, efi_desc_size, efi_mmap_key; +extern UINT32 efi_desc_ver; + +extern bool efi_boot_services_exited; +bool efi_exit_boot_services(void); + +bool is_efi_serial_present(void); +#endif + +void *get_device_tree_blob(const char *config, size_t extra_size); + +extern struct volume *boot_volume; + +#if defined (BIOS) +extern bool stage3_loaded; +#endif + +extern bool quiet, serial, editor_enabled, help_hidden, hash_mismatch_panic; + +extern uint64_t usec_at_bootloader_entry; + +bool parse_resolution(size_t *width, size_t *height, size_t *bpp, const char *buf); + +bool get_absolute_path(char *path_ptr, const char *path, const char *pwd, size_t size); + +uint64_t sqrt(uint64_t a_nInput); +size_t get_trailing_zeros(uint64_t val); + +int digit_to_int(char c); +uint8_t bcd_to_int(uint8_t val); +uint8_t int_to_bcd(uint8_t val); + +noreturn void panic(bool allow_menu, const char *fmt, ...); + +int pit_sleep_and_quit_on_keypress(int seconds); + +uint64_t strtoui(const char *s, const char **end, int base); + +#define MIN(a, b) ({ \ + __auto_type MIN_a = (a); \ + __auto_type MIN_b = (b); \ + MIN_a > MIN_b ? MIN_b : MIN_a; \ +}) + +#define MAX(a, b) ({ \ + __auto_type MAX_a = (a); \ + __auto_type MAX_b = (b); \ + MAX_a > MAX_b ? MAX_a : MAX_b; \ +}) + +#define DIV_ROUNDUP(a, b) ({ \ + __auto_type DIV_ROUNDUP_a = (a); \ + __auto_type DIV_ROUNDUP_b = (b); \ + (DIV_ROUNDUP_a + (DIV_ROUNDUP_b - 1)) / DIV_ROUNDUP_b; \ +}) + +#define ALIGN_UP(x, a) ({ \ + __auto_type ALIGN_UP_value = (x); \ + __auto_type ALIGN_UP_align = (a); \ + ALIGN_UP_value = DIV_ROUNDUP(ALIGN_UP_value, ALIGN_UP_align) * ALIGN_UP_align; \ + ALIGN_UP_value; \ +}) + +#define ALIGN_DOWN(x, a) ({ \ + __auto_type ALIGN_DOWN_value = (x); \ + __auto_type ALIGN_DOWN_align = (a); \ + ALIGN_DOWN_value = (ALIGN_DOWN_value / ALIGN_DOWN_align) * ALIGN_DOWN_align; \ + ALIGN_DOWN_value; \ +}) + +#define SIZEOF_ARRAY(array) (sizeof(array) / sizeof(array[0])) + +typedef char symbol[]; + +noreturn void stage3_common(void); + +#if defined (__x86_64__) || defined (__i386__) +noreturn void common_spinup(void *fnptr, int args, ...); +#elif defined (__aarch64__) +noreturn void enter_in_el1(uint64_t entry, uint64_t sp, uint64_t sctlr, + uint64_t mair, uint64_t tcr, uint64_t ttbr0, + uint64_t ttbr1, uint64_t target_x0); +#elif defined (__riscv) +noreturn void riscv_spinup(uint64_t entry, uint64_t sp, uint64_t satp, uint64_t direct_map_offset); +#if defined (UEFI) +RISCV_EFI_BOOT_PROTOCOL *get_riscv_boot_protocol(void); +#endif +#elif defined (__loongarch64) +noreturn void loongarch_spinup(uint64_t entry, uint64_t sp, uint64_t pgdl, + uint64_t pgdh, uint64_t direct_map_offset); +#else +#error Unknown architecture +#endif + +#define no_unwind __attribute__((section(".no_unwind"))) + +#define MEM_RANGE_X 1 +#define MEM_RANGE_W 2 +#define MEM_RANGE_R 4 + +struct mem_range { + uint64_t base; + uint64_t length; + uint64_t permissions; +}; + +#endif diff --git a/limine/common/lib/misc.s2.c b/limine/common/lib/misc.s2.c new file mode 100644 index 0000000..78cdb88 --- /dev/null +++ b/limine/common/lib/misc.s2.c @@ -0,0 +1,129 @@ +#include +#include +#include +#include + +bool verbose = false; +bool quiet = false; +bool serial = false; +bool hash_mismatch_panic = false; + +uint8_t bcd_to_int(uint8_t val) { + return (val & 0x0f) + ((val & 0xf0) >> 4) * 10; +} +uint8_t int_to_bcd(uint8_t val) { + return (val % 10) | (val / 10) << 4; +} + +int digit_to_int(char c) { + if (c >= 'a' && c <= 'f') { + return (c - 'a') + 10; + } + if (c >= 'A' && c <= 'F') { + return (c - 'A') + 10; + } + if (c >= '0' && c <= '9'){ + return c - '0'; + } + + return -1; +} + +uint64_t strtoui(const char *s, const char **end, int base) { + uint64_t n = 0; + for (size_t i = 0; ; i++) { + int d = digit_to_int(s[i]); + if (d == -1 || d >= base) { + if (end != NULL) + *end = &s[i]; + break; + } + uint64_t mul_result; + if (__builtin_mul_overflow(n, (uint64_t)base, &mul_result)) { + if (end != NULL) + *end = &s[i]; + return UINT64_MAX; + } + if (__builtin_add_overflow(mul_result, (uint64_t)d, &n)) { + if (end != NULL) + *end = &s[i]; + return UINT64_MAX; + } + } + return n; +} + +bool get_absolute_path(char *path_ptr, const char *path, const char *pwd, size_t size) { + char *orig_ptr = path_ptr; + char *end_ptr = path_ptr + size - 1; + + if (size == 0) return false; + + if (!*path) { + size_t pwd_len = strlen(pwd); + if (pwd_len >= size) return false; + memcpy(path_ptr, pwd, pwd_len + 1); + return true; + } + + if (*path != '/') { + size_t pwd_len = strlen(pwd); + if (pwd_len >= size) return false; + memcpy(path_ptr, pwd, pwd_len + 1); + path_ptr += pwd_len; + } else { + *path_ptr = '/'; + path_ptr++; + path++; + } + + goto first_run; + + for (;;) { + switch (*path) { + case '/': + path++; +first_run: + if (*path == '/') continue; + if ((!strncmp(path, ".\0", 2)) + || (!strncmp(path, "./\0", 3))) { + goto term; + } + if ((!strncmp(path, "..\0", 3)) + || (!strncmp(path, "../\0", 4))) { + while (*path_ptr != '/') path_ptr--; + if (path_ptr == orig_ptr) path_ptr++; + goto term; + } + if (!strncmp(path, "../", 3)) { + while (*path_ptr != '/') path_ptr--; + if (path_ptr == orig_ptr) path_ptr++; + path += 2; + *path_ptr = 0; + continue; + } + if (!strncmp(path, "./", 2)) { + path += 1; + continue; + } + if (((path_ptr - 1) != orig_ptr) && (*(path_ptr - 1) != '/')) { + if (path_ptr >= end_ptr) return false; + *path_ptr = '/'; + path_ptr++; + } + continue; + case '\0': +term: + if ((*(path_ptr - 1) == '/') && ((path_ptr - 1) != orig_ptr)) + path_ptr--; + *path_ptr = 0; + return true; + default: + if (path_ptr >= end_ptr) return false; + *path_ptr = *path; + path++; + path_ptr++; + continue; + } + } +} diff --git a/limine/common/lib/panic.s2.c b/limine/common/lib/panic.s2.c new file mode 100644 index 0000000..817b651 --- /dev/null +++ b/limine/common/lib/panic.s2.c @@ -0,0 +1,89 @@ +#include +#include +#include +#include +#include +#include +#if defined (UEFI) +# include +#endif +#include +#include +#include +#include +#include +#include + +noreturn void panic(bool allow_menu, const char *fmt, ...) { + va_list args; + + va_start(args, fmt); + + quiet = false; + + if ( +#if defined (BIOS) + stage3_loaded == true && +#endif + term_backend == _NOT_READY) { + term_fallback(); + } + + if ( +#if defined (BIOS) + stage3_loaded == true && +#endif + term_backend != FALLBACK) { + print("\033[31mPANIC\033[37;1m\033[0m: "); + } else { + print("PANIC: "); + } + vprint(fmt, args); + + va_end(args); + + print("\n"); + print_stacktrace(NULL); + + if ( +#if defined (BIOS) + stage3_loaded == true && +#elif defined (UEFI) + efi_boot_services_exited == false && +#endif + allow_menu == true) { + print("Press a key to return to %s.", booting_from_editor ? "editor" : "menu"); + + getchar(); + + // This fixes a crash + term_notready(); + + menu(false); +/* + fb_clear(&fbinfo); + + // release all uefi memory and return to firmware + pmm_release_uefi_mem(); + gBS->Exit(efi_image_handle, EFI_ABORTED, 0, NULL); +*/ + } else { +#if defined (BIOS) + print("Press CTRL+ALT+DEL to reboot."); + rm_hcf(); +#elif defined (UEFI) + print("System halted."); + for (;;) { +#if defined (__x86_64__) || defined (__i386__) + asm ("hlt"); +#elif defined (__aarch64__) || defined (__riscv) + asm ("wfi"); +#elif defined (__loongarch64) + asm ("idle 0"); +#else +#error Unknown architecture +#endif + } +#endif + } +} diff --git a/limine/common/lib/part.h b/limine/common/lib/part.h new file mode 100644 index 0000000..f09a293 --- /dev/null +++ b/limine/common/lib/part.h @@ -0,0 +1,110 @@ +#ifndef LIB__PART_H__ +#define LIB__PART_H__ + +#include +#include +#include +#include +#if defined (UEFI) +# include +# include +#endif + +#define NO_PARTITION (-1) +#define INVALID_TABLE (-2) +#define END_OF_TABLE (-3) + +struct volume { +#if defined (UEFI) + EFI_HANDLE efi_handle; + + // Block storage + EFI_HANDLE efi_part_handle; + EFI_BLOCK_IO *block_io; + + // PXE + EFI_PXE_BASE_CODE_PROTOCOL *pxe_base_code; + + bool unique_sector_valid; + uint8_t unique_sector_b2b[BLAKE2B_OUT_BYTES]; +#elif defined (BIOS) + int drive; +#endif + + size_t fastest_xfer_size; + + int index; + + bool is_optical; + bool pxe; + + int partition; + int sector_size; + struct volume *backing_dev; + + int max_partition; + + int cache_status; + uint8_t *cache; + uint64_t cached_block; + + uint64_t first_sect; + uint64_t sect_count; + + bool guid_valid; + struct guid guid; + bool part_guid_valid; + struct guid part_guid; + bool fslabel_valid; + char *fslabel; +}; + +bool is_valid_mbr(struct volume *volume); + +extern struct volume **volume_index; +extern size_t volume_index_i; + +bool gpt_get_guid(struct guid *guid, struct volume *volume); +uint32_t mbr_get_id(struct volume *volume); + +int part_get(struct volume *part, struct volume *volume, int partition); + +struct volume *volume_get_by_guid(struct guid *guid); +struct volume *volume_get_by_fslabel(char *fslabel); +struct volume *volume_get_by_coord(bool optical, int drive, int partition); +#if defined (BIOS) +struct volume *volume_get_by_bios_drive(int drive); +#endif + +bool volume_read(struct volume *part, void *buffer, uint64_t loc, uint64_t count); + +#define volume_iterate_parts(_VOLUME_, _BODY_) do { \ + struct volume *_VOLUME = _VOLUME_; \ + if (_VOLUME->pxe) { \ + do { \ + struct volume *_PART = _VOLUME; \ + _BODY_ \ + } while (0); \ + } else { \ + while (_VOLUME->backing_dev != NULL) { \ + _VOLUME = _VOLUME->backing_dev; \ + } \ + \ + int _PART_CNT = -1; \ + for (size_t _PARTNO = 0; ; _PARTNO++) { \ + if (_PART_CNT > _VOLUME->max_partition) \ + break; \ + \ + struct volume *_PART = volume_get_by_coord(_VOLUME->is_optical, \ + _VOLUME->index, _PARTNO); \ + if (_PART == NULL) \ + continue; \ + \ + _PART_CNT++; \ + \ + _BODY_ \ + } \ + } \ +} while (0) + +#endif diff --git a/limine/common/lib/part.s2.c b/limine/common/lib/part.s2.c new file mode 100644 index 0000000..422ec89 --- /dev/null +++ b/limine/common/lib/part.s2.c @@ -0,0 +1,645 @@ +#include +#include +#include +#include +#if defined (BIOS) +# include +#endif +#include +#include +#include +#include +#include + +enum { + CACHE_NOT_READY = 0, + CACHE_READY +}; + +static bool cache_block(struct volume *volume, uint64_t block) { + if (volume->cache_status == CACHE_READY && block == volume->cached_block) + return true; + + volume->cache_status = CACHE_NOT_READY; + + if (volume->cache == NULL) + volume->cache = + ext_mem_alloc(volume->fastest_xfer_size * volume->sector_size); + + if (volume->first_sect % (volume->sector_size / 512)) { + return false; + } + + uint64_t first_sect = volume->first_sect / (volume->sector_size / 512); + + uint64_t xfer_size = volume->fastest_xfer_size; + + // Check for overflow in sector offset calculation + uint64_t block_offset; + if (__builtin_mul_overflow(block, volume->fastest_xfer_size, &block_offset)) { + return false; + } + uint64_t read_sector; + if (__builtin_add_overflow(first_sect, block_offset, &read_sector)) { + return false; + } + + // Clamp xfer_size to remaining sectors in volume + if (volume->sect_count != (uint64_t)-1) { + uint64_t volume_sectors = volume->sect_count / (volume->sector_size / 512); + uint64_t end_sector; + if (__builtin_add_overflow(first_sect, volume_sectors, &end_sector)) { + end_sector = UINT64_MAX; + } + if (read_sector >= end_sector) { + return false; + } + uint64_t remaining = end_sector - read_sector; + if (xfer_size > remaining) { + xfer_size = remaining; + } + } + + int ret = disk_read_sectors(volume, volume->cache, read_sector, xfer_size); + if (ret != DISK_SUCCESS) { + return false; + } + + volume->cache_status = CACHE_READY; + volume->cached_block = block; + + return true; +} + +bool volume_read(struct volume *volume, void *buffer, uint64_t loc, uint64_t count) { + if (volume->pxe) { + panic(false, "Attempted volume_read() on pxe"); + } + + if (volume->sect_count != (uint64_t)-1) { + // sect_count is always in 512-byte sectors for both whole disks and partitions + uint64_t part_size; + if (__builtin_mul_overflow(volume->sect_count, (uint64_t)512, &part_size)) { + return false; + } + if (loc >= part_size || count > part_size - loc) { + return false; + } + } + + uint64_t block_size = volume->fastest_xfer_size * volume->sector_size; + + uint64_t progress = 0; + while (progress < count) { + uint64_t block = (loc + progress) / block_size; + + if (!cache_block(volume, block)) + return false; + + uint64_t chunk = count - progress; + uint64_t offset = (loc + progress) % block_size; + if (chunk > block_size - offset) + chunk = block_size - offset; + + memcpy(buffer + progress, &volume->cache[offset], chunk); + progress += chunk; + } + + return true; +} + +struct gpt_table_header { + // the head + char signature[8]; + uint32_t revision; + uint32_t header_size; + uint32_t crc32; + uint32_t _reserved0; + + // the partitioning info + uint64_t my_lba; + uint64_t alternate_lba; + uint64_t first_usable_lba; + uint64_t last_usable_lba; + + // the guid + struct guid disk_guid; + + // entries related + uint64_t partition_entry_lba; + uint32_t number_of_partition_entries; + uint32_t size_of_partition_entry; + uint32_t partition_entry_array_crc32; +} __attribute__((packed)); + +struct gpt_entry { + struct guid partition_type_guid; + + struct guid unique_partition_guid; + + uint64_t starting_lba; + uint64_t ending_lba; + + uint64_t attributes; + + uint16_t partition_name[36]; +} __attribute__((packed)); + +bool gpt_get_guid(struct guid *guid, struct volume *volume) { + struct gpt_table_header header = {0}; + + int lb_guesses[] = { + 512, + 4096 + }; + int lb_size = -1; + + for (size_t i = 0; i < SIZEOF_ARRAY(lb_guesses); i++) { + // read header, located after the first block + if (!volume_read(volume, &header, lb_guesses[i] * 1, sizeof(header))) + continue; + + // check the header + // 'EFI PART' + if (strncmp(header.signature, "EFI PART", 8)) + continue; + + lb_size = lb_guesses[i]; + break; + } + + if (lb_size == -1) { + return false; + } + + if (header.revision != 0x00010000) + return false; + + *guid = header.disk_guid; + + return true; +} + +static int gpt_get_part(struct volume *ret, struct volume *volume, int partition) { + struct gpt_table_header header = {0}; + + int lb_guesses[] = { + 512, + 4096 + }; + int lb_size = -1; + + for (size_t i = 0; i < SIZEOF_ARRAY(lb_guesses); i++) { + // read header, located after the first block + if (!volume_read(volume, &header, lb_guesses[i] * 1, sizeof(header))) + continue; + + // check the header + // 'EFI PART' + if (strncmp(header.signature, "EFI PART", 8)) + continue; + + lb_size = lb_guesses[i]; + break; + } + + if (lb_size == -1) { + return INVALID_TABLE; + } + + if (header.revision != 0x00010000) + return INVALID_TABLE; + + // parse the entries if reached here + if ((uint32_t)partition >= header.number_of_partition_entries) + return END_OF_TABLE; + + // Validate partition entry size (must be at least as large as our struct) + uint32_t entry_size = header.size_of_partition_entry; + if (entry_size < sizeof(struct gpt_entry)) { + return INVALID_TABLE; + } + + // Check for potential integer overflow in offset calculation + uint64_t entry_offset; + if (__builtin_mul_overflow((uint64_t)header.partition_entry_lba, (uint64_t)lb_size, &entry_offset)) { + return INVALID_TABLE; // Multiplication overflow + } + // Use actual entry size from header for offset calculation + uint64_t partition_offset = (uint64_t)partition * entry_size; + if (__builtin_add_overflow(entry_offset, partition_offset, &entry_offset)) { + return INVALID_TABLE; // Addition overflow would occur + } + + struct gpt_entry entry = {0}; + if (!volume_read(volume, &entry, entry_offset, sizeof(entry))) { + return END_OF_TABLE; + } + + struct guid empty_guid = {0}; + if (!memcmp(&entry.unique_partition_guid, &empty_guid, sizeof(struct guid))) + return NO_PARTITION; + + // Validate that ending_lba >= starting_lba to prevent underflow + if (entry.ending_lba < entry.starting_lba) { + return NO_PARTITION; // Invalid partition geometry + } + + // Calculate sector multiplier for lb_size conversion + uint64_t sect_multiplier = lb_size / 512; + + // Check for overflow in first_sect calculation + uint64_t first_sect_result; + if (__builtin_mul_overflow(entry.starting_lba, sect_multiplier, &first_sect_result)) { + return NO_PARTITION; // Overflow in first_sect + } + + // Check for overflow in sect_count calculation + // First compute partition size in logical blocks + // Check if +1 would overflow (ending_lba == UINT64_MAX) + uint64_t partition_size = entry.ending_lba - entry.starting_lba; + if (partition_size == UINT64_MAX) { + return NO_PARTITION; // Partition size +1 would overflow + } + uint64_t partition_blocks = partition_size + 1; + uint64_t sect_count_result; + if (__builtin_mul_overflow(partition_blocks, sect_multiplier, §_count_result)) { + return NO_PARTITION; // Overflow in sect_count + } + +#if defined (UEFI) + ret->efi_handle = volume->efi_handle; + ret->block_io = volume->block_io; +#elif defined (BIOS) + ret->drive = volume->drive; +#endif + ret->fastest_xfer_size = volume->fastest_xfer_size; + ret->index = volume->index; + ret->is_optical = volume->is_optical; + ret->partition = partition + 1; + ret->sector_size = volume->sector_size; + ret->first_sect = first_sect_result; + ret->sect_count = sect_count_result; + ret->backing_dev = volume; + + struct guid guid; + if (!fs_get_guid(&guid, ret)) { + ret->guid_valid = false; + } else { + ret->guid_valid = true; + ret->guid = guid; + } + + char *fslabel = fs_get_label(ret); + if (fslabel == NULL) { + ret->fslabel_valid = false; + } else { + ret->fslabel_valid = true; + ret->fslabel = fslabel; + } + + ret->part_guid_valid = true; + ret->part_guid = entry.unique_partition_guid; + + return 0; +} + +struct mbr_entry { + uint8_t status; + uint8_t chs_first_sect[3]; + uint8_t type; + uint8_t chs_last_sect[3]; + uint32_t first_sect; + uint32_t sect_count; +} __attribute__((packed)); + +bool is_valid_mbr(struct volume *volume) { + // Check if actually valid mbr + uint16_t hint = 0; + + if (!volume_read(volume, &hint, 446, sizeof(uint8_t))) + return false; + if ((uint8_t)hint != 0x00 && (uint8_t)hint != 0x80) + return false; + if (!volume_read(volume, &hint, 462, sizeof(uint8_t))) + return false; + if ((uint8_t)hint != 0x00 && (uint8_t)hint != 0x80) + return false; + if (!volume_read(volume, &hint, 478, sizeof(uint8_t))) + return false; + if ((uint8_t)hint != 0x00 && (uint8_t)hint != 0x80) + return false; + if (!volume_read(volume, &hint, 494, sizeof(uint8_t))) + return false; + if ((uint8_t)hint != 0x00 && (uint8_t)hint != 0x80) + return false; + + char hintc[64]; + if (!volume_read(volume, hintc, 3, 4)) + return false; + if (memcmp(hintc, "NTFS", 4) == 0) + return false; + if (!volume_read(volume, hintc, 54, 3)) + return false; + if (memcmp(hintc, "FAT", 3) == 0) + return false; + if (!volume_read(volume, hintc, 82, 3)) + return false; + if (memcmp(hintc, "FAT", 3) == 0) + return false; + if (!volume_read(volume, hintc, 3, 5)) + return false; + if (memcmp(hintc, "FAT32", 5) == 0) + return false; + if (!volume_read(volume, &hint, 1080, sizeof(uint16_t))) + return false; + if (hint == 0xef53) + return false; + + return true; +} + +uint32_t mbr_get_id(struct volume *volume) { + if (!is_valid_mbr(volume)) { + return 0; + } + + uint32_t ret; + if (!volume_read(volume, &ret, 0x1b8, sizeof(uint32_t))) { + return 0; + } + + return ret; +} + +// Maximum number of logical partitions to prevent infinite loops from circular EBR chains +#define MAX_LOGICAL_PARTITIONS 256 + +static int mbr_get_logical_part(struct volume *ret, struct volume *extended_part, + int partition) { + struct mbr_entry entry; + + // Limit partition index to prevent excessive iteration + if (partition >= MAX_LOGICAL_PARTITIONS) { + return END_OF_TABLE; + } + + uint64_t ebr_sector = 0; + uint64_t prev_ebr_sector = 0; + + for (int i = 0; i < partition; i++) { + uint64_t entry_offset = ebr_sector * 512 + 0x1ce; + + if (!volume_read(extended_part, &entry, entry_offset, sizeof(struct mbr_entry))) { + return END_OF_TABLE; + } + + if (entry.type != 0x0f && entry.type != 0x05) { + return END_OF_TABLE; + } + + prev_ebr_sector = ebr_sector; + ebr_sector = entry.first_sect; + + // Detect circular chain: if new sector points to 0 or backwards, it's invalid + // (EBR sectors should always increase within the extended partition) + if (ebr_sector == 0 || (i > 0 && ebr_sector <= prev_ebr_sector)) { + return END_OF_TABLE; // Circular or corrupted EBR chain + } + + // Also check that ebr_sector is within the extended partition bounds + if (ebr_sector >= extended_part->sect_count) { + return END_OF_TABLE; // EBR points outside extended partition + } + } + + uint64_t entry_offset = ebr_sector * 512 + 0x1be; + + if (!volume_read(extended_part, &entry, entry_offset, sizeof(struct mbr_entry))) { + return END_OF_TABLE; + } + + if (entry.type == 0) + return NO_PARTITION; + + // Validate sect_count is non-zero + if (entry.sect_count == 0) { + return NO_PARTITION; + } + + // Check for overflow in first_sect calculation + uint64_t first_sect_64; + if (__builtin_add_overflow(extended_part->first_sect, ebr_sector, &first_sect_64)) { + return NO_PARTITION; // Addition overflow + } + if (__builtin_add_overflow(first_sect_64, (uint64_t)entry.first_sect, &first_sect_64)) { + return NO_PARTITION; // Addition overflow + } + uint64_t partition_end; + if (__builtin_add_overflow(first_sect_64, (uint64_t)entry.sect_count, &partition_end)) { + return NO_PARTITION; // Partition would overflow + } + +#if defined (UEFI) + ret->efi_handle = extended_part->efi_handle; + ret->block_io = extended_part->block_io; +#elif defined (BIOS) + ret->drive = extended_part->drive; +#endif + ret->fastest_xfer_size = extended_part->fastest_xfer_size; + ret->index = extended_part->index; + ret->is_optical = extended_part->is_optical; + ret->partition = partition + 4 + 1; + ret->sector_size = extended_part->sector_size; + ret->first_sect = first_sect_64; + ret->sect_count = entry.sect_count; + ret->backing_dev = extended_part->backing_dev; + + struct guid guid; + if (!fs_get_guid(&guid, ret)) { + ret->guid_valid = false; + } else { + ret->guid_valid = true; + ret->guid = guid; + } + + char *fslabel = fs_get_label(ret); + if (fslabel == NULL) { + ret->fslabel_valid = false; + } else { + ret->fslabel_valid = true; + ret->fslabel = fslabel; + } + + ret->part_guid_valid = false; + + return 0; +} + +static int mbr_get_part(struct volume *ret, struct volume *volume, int partition) { + if (!is_valid_mbr(volume)) { + return INVALID_TABLE; + } + + struct mbr_entry entry; + + if (partition > 3) { + for (int i = 0; i < 4; i++) { + uint64_t entry_offset = 0x1be + sizeof(struct mbr_entry) * i; + + if (!volume_read(volume, &entry, entry_offset, sizeof(struct mbr_entry))) { + continue; + } + + if (entry.type != 0x0f && entry.type != 0x05) + continue; + + // Validate extended partition has non-zero size + if (entry.sect_count == 0) { + continue; + } + + struct volume extended_part = {0}; + +#if defined (UEFI) + extended_part.efi_handle = volume->efi_handle; + extended_part.block_io = volume->block_io; +#elif defined (BIOS) + extended_part.drive = volume->drive; +#endif + extended_part.fastest_xfer_size = volume->fastest_xfer_size; + extended_part.index = volume->index; + extended_part.is_optical = volume->is_optical; + extended_part.partition = i + 1; + extended_part.sector_size = volume->sector_size; + extended_part.first_sect = entry.first_sect; + extended_part.sect_count = entry.sect_count; + extended_part.backing_dev = volume; + + return mbr_get_logical_part(ret, &extended_part, partition - 4); + } + + return END_OF_TABLE; + } + + uint64_t entry_offset = 0x1be + sizeof(struct mbr_entry) * partition; + + if (!volume_read(volume, &entry, entry_offset, sizeof(struct mbr_entry))) { + return END_OF_TABLE; + } + + if (entry.type == 0) + return NO_PARTITION; + + // Validate sect_count is non-zero + if (entry.sect_count == 0) { + return NO_PARTITION; + } + +#if defined (UEFI) + ret->efi_handle = volume->efi_handle; + ret->block_io = volume->block_io; +#elif defined (BIOS) + ret->drive = volume->drive; +#endif + ret->fastest_xfer_size = volume->fastest_xfer_size; + ret->index = volume->index; + ret->is_optical = volume->is_optical; + ret->partition = partition + 1; + ret->sector_size = volume->sector_size; + ret->first_sect = entry.first_sect; + ret->sect_count = entry.sect_count; + ret->backing_dev = volume; + + struct guid guid; + if (!fs_get_guid(&guid, ret)) { + ret->guid_valid = false; + } else { + ret->guid_valid = true; + ret->guid = guid; + } + + char *fslabel = fs_get_label(ret); + if (fslabel == NULL) { + ret->fslabel_valid = false; + } else { + ret->fslabel_valid = true; + ret->fslabel = fslabel; + } + + ret->part_guid_valid = false; + + return 0; +} + +int part_get(struct volume *part, struct volume *volume, int partition) { + int ret; + + // Validate partition index is non-negative + if (partition < 0) { + return NO_PARTITION; + } + + ret = gpt_get_part(part, volume, partition); + if (ret != INVALID_TABLE) + return ret; + + ret = mbr_get_part(part, volume, partition); + if (ret != INVALID_TABLE) + return ret; + + return INVALID_TABLE; +} + +struct volume **volume_index = NULL; +size_t volume_index_i = 0; + +struct volume *volume_get_by_guid(struct guid *guid) { + for (size_t i = 0; i < volume_index_i; i++) { + if (volume_index[i]->guid_valid + && memcmp(&volume_index[i]->guid, guid, 16) == 0) { + return volume_index[i]; + } + if (volume_index[i]->part_guid_valid + && memcmp(&volume_index[i]->part_guid, guid, 16) == 0) { + return volume_index[i]; + } + } + + return NULL; +} + +struct volume *volume_get_by_fslabel(char *fslabel) { + for (size_t i = 0; i < volume_index_i; i++) { + if (volume_index[i]->fslabel_valid + && strcmp(volume_index[i]->fslabel, fslabel) == 0) { + return volume_index[i]; + } + } + + return NULL; +} + +struct volume *volume_get_by_coord(bool optical, int drive, int partition) { + for (size_t i = 0; i < volume_index_i; i++) { + if (volume_index[i]->index == drive + && volume_index[i]->is_optical == optical + && volume_index[i]->partition == partition) { + return volume_index[i]; + } + } + + return NULL; +} + +#if defined (BIOS) +struct volume *volume_get_by_bios_drive(int drive) { + for (size_t i = 0; i < volume_index_i; i++) { + if (volume_index[i]->drive == drive) { + return volume_index[i]; + } + } + + return NULL; +} +#endif diff --git a/limine/common/lib/pe.c b/limine/common/lib/pe.c new file mode 100644 index 0000000..c5e52a6 --- /dev/null +++ b/limine/common/lib/pe.c @@ -0,0 +1,496 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#define FIXED_HIGHER_HALF_OFFSET_64 ((uint64_t)0xffffffff80000000) + +#define IMAGE_DOS_SIGNATURE 0x5a4d + +typedef struct _IMAGE_DOS_HEADER { + uint16_t e_magic; + uint16_t e_cblp; + uint16_t e_cp; + uint16_t e_crlc; + uint16_t e_cparhdr; + uint16_t e_minalloc; + uint16_t e_maxalloc; + uint16_t e_ss; + uint16_t e_sp; + uint16_t e_csum; + uint16_t e_ip; + uint16_t e_cs; + uint16_t e_lfarlc; + uint16_t e_ovno; + uint16_t e_res[4]; + uint16_t e_oemid; + uint16_t e_oeminfo; + uint16_t e_res2[10]; + uint32_t e_lfanew; +} IMAGE_DOS_HEADER; + +#define IMAGE_FILE_MACHINE_I386 0x14c +#define IMAGE_FILE_MACHINE_AMD64 0x8664 +#define IMAGE_FILE_MACHINE_ARM64 0xaa64 +#define IMAGE_FILE_MACHINE_RISCV64 0x5064 +#define IMAGE_FILE_MACHINE_LOONGARCH64 0x6264 + +#define IMAGE_FILE_RELOCS_STRIPPED 1 +#define IMAGE_FILE_EXECUTABLE_IMAGE 2 + +typedef struct { + uint16_t Machine; + uint16_t NumberOfSections; + uint32_t TimeDateStamp; + uint32_t PointerToSymbolTable; + uint32_t NumberOfSymbols; + uint16_t SizeOfOptionalHeader; + uint16_t Characteristics; +} IMAGE_FILE_HEADER; + +typedef struct { + uint32_t VirtualAddress; + uint32_t Size; +} IMAGE_DATA_DIRECTORY; + +#define IMAGE_NT_OPTIONAL_HDR32_MAGIC 0x10b +#define IMAGE_NT_OPTIONAL_HDR64_MAGIC 0x20b + +#define IMAGE_DIRECTORY_ENTRY_EXPORT 0 +#define IMAGE_DIRECTORY_ENTRY_IMPORT 1 +#define IMAGE_DIRECTORY_ENTRY_RESOURCE 2 +#define IMAGE_DIRECTORY_ENTRY_EXCEPTION 3 +#define IMAGE_DIRECTORY_ENTRY_SECURITY 4 +#define IMAGE_DIRECTORY_ENTRY_BASERELOC 5 +#define IMAGE_DIRECTORY_ENTRY_DEBUG 6 +#define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE 7 +#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8 +#define IMAGE_DIRECTORY_ENTRY_TLS 9 +#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 10 +#define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT 11 +#define IMAGE_DIRECTORY_ENTRY_IAT 12 +#define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT 13 +#define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14 + +typedef struct { + uint16_t Magic; + uint8_t MajorLinkerVersion; + uint8_t MinorLinkerVersion; + uint32_t SizeOfCode; + uint32_t SizeOfInitializedData; + uint32_t SizeOfUninitializedData; + uint32_t AddressOfEntryPoint; + uint32_t BaseOfCode; + uint64_t ImageBase; + uint32_t SectionAlignment; + uint32_t FileAlignment; + uint16_t MajorOperatingSystemVersion; + uint16_t MinorOperatingSystemVersion; + uint16_t MajorImageVersion; + uint16_t MinorImageVersion; + uint16_t MajorSubsystemVersion; + uint16_t MinorSubsystemVersion; + uint32_t Win32VersionValue; + uint32_t SizeOfImage; + uint32_t SizeOfHeaders; + uint32_t CheckSum; + uint16_t Subsystem; + uint16_t DllCharacteristics; + uint64_t SizeOfStackReserve; + uint64_t SizeOfStackCommit; + uint64_t SizeOfHeapReserve; + uint64_t SizeOfHeapCommit; + uint32_t LoaderFlags; + uint32_t NumberOfRvaAndSizes; + IMAGE_DATA_DIRECTORY DataDirectory[16]; +} IMAGE_OPTIONAL_HEADER64; + +#define IMAGE_NT_SIGNATURE 0x4550 + +typedef struct { + uint32_t Signature; + IMAGE_FILE_HEADER FileHeader; + IMAGE_OPTIONAL_HEADER64 OptionalHeader; +} IMAGE_NT_HEADERS64; + +#define IMAGE_SCN_MEM_EXECUTE 0x20000000 +#define IMAGE_SCN_MEM_READ 0x40000000 +#define IMAGE_SCN_MEM_WRITE 0x80000000 + +typedef struct { + char Name[8]; + uint32_t VirtualSize; + uint32_t VirtualAddress; + uint32_t SizeOfRawData; + uint32_t PointerToRawData; + uint32_t PointerToRelocations; + uint32_t PointerToLinenumbers; + uint16_t NumberOfRelocations; + uint16_t NumberOfLinenumbers; + uint32_t Characteristics; +} IMAGE_SECTION_HEADER; + +typedef struct { + union { + uint32_t Characteristics; + uint32_t OriginalFirstThunk; + }; + uint32_t TimeDateStamp; + uint32_t ForwarderChain; + uint32_t Name; + uint32_t FirstThunk; +} IMAGE_IMPORT_DESCRIPTOR; + +#define IMAGE_REL_BASED_ABSOLUTE 0 +#define IMAGE_REL_BASED_HIGHLOW 3 +#define IMAGE_REL_BASED_DIR64 10 + +typedef struct { + uint32_t VirtualAddress; + uint32_t SizeOfBlock; +} IMAGE_BASE_RELOCATION_BLOCK; + +static void pe64_validate(uint8_t *image, size_t file_size) { + IMAGE_DOS_HEADER *dos_hdr = (IMAGE_DOS_HEADER *)image; + + if (file_size < sizeof(IMAGE_DOS_HEADER)) { + panic(true, "pe: File too small for DOS header"); + } + + if (dos_hdr->e_magic != IMAGE_DOS_SIGNATURE) { + panic(true, "pe: Not a valid PE file"); + } + + if (file_size < sizeof(IMAGE_NT_HEADERS64)) { + panic(true, "pe: File too small for NT headers"); + } + + if (dos_hdr->e_lfanew > file_size - sizeof(IMAGE_NT_HEADERS64)) { + panic(true, "pe: e_lfanew offset out of bounds"); + } + + IMAGE_NT_HEADERS64 *nt_hdrs = (IMAGE_NT_HEADERS64 *)(image + dos_hdr->e_lfanew); + + if (nt_hdrs->Signature != IMAGE_NT_SIGNATURE) { + panic(true, "pe: Not a valid PE file"); + } + + if (nt_hdrs->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC) { + panic(true, "pe: Not a valid PE32+ file"); + } + +#if defined(__x86_64__) || defined(__i386__) + if (nt_hdrs->FileHeader.Machine != IMAGE_FILE_MACHINE_AMD64) { + panic(true, "pe: Not an x86-64 PE file"); + } +#elif defined(__aarch64__) + if (nt_hdrs->FileHeader.Machine != IMAGE_FILE_MACHINE_ARM64) { + panic(true, "pe: Not an ARM64 PE file"); + } +#elif defined (__riscv) && (__riscv_xlen == 64) + if (nt_hdrs->FileHeader.Machine != IMAGE_FILE_MACHINE_RISCV64) { + panic(true, "pe: Not a RISC-V PE file"); + } +#elif defined (__loongarch__) && (__loongarch_grlen == 64) + if (nt_hdrs->FileHeader.Machine != IMAGE_FILE_MACHINE_LOONGARCH64) { + panic(true, "pe: Not a loongarch64 PE file"); + } +#else +#error Unknown architecture +#endif +} + +int pe_bits(uint8_t *image, size_t image_size) { + if (image_size < sizeof(IMAGE_DOS_HEADER)) { + return -1; + } + + IMAGE_DOS_HEADER *dos_hdr = (IMAGE_DOS_HEADER *)image; + + if (dos_hdr->e_magic != IMAGE_DOS_SIGNATURE) { + return -1; + } + + if (image_size < sizeof(IMAGE_NT_HEADERS64)) { + return -1; + } + + if ((size_t)dos_hdr->e_lfanew > image_size - sizeof(IMAGE_NT_HEADERS64)) { + return -1; + } + + IMAGE_NT_HEADERS64 *nt_hdrs = (IMAGE_NT_HEADERS64 *)(image + dos_hdr->e_lfanew); + + if (nt_hdrs->Signature != IMAGE_NT_SIGNATURE) { + return -1; + } + + switch (nt_hdrs->FileHeader.Machine) { + case IMAGE_FILE_MACHINE_I386: + return 32; + case IMAGE_FILE_MACHINE_AMD64: + case IMAGE_FILE_MACHINE_ARM64: + case IMAGE_FILE_MACHINE_RISCV64: + case IMAGE_FILE_MACHINE_LOONGARCH64: + return 64; + } + + return -1; +} + +bool pe64_load(uint8_t *image, size_t file_size, uint64_t *entry_point, uint64_t *_slide, uint32_t alloc_type, bool kaslr, struct mem_range **_ranges, uint64_t *_ranges_count, uint64_t *physical_base, uint64_t *virtual_base, uint64_t *_image_size, uint64_t *image_size_before_bss, bool *_is_reloc) { + pe64_validate(image, file_size); + + IMAGE_DOS_HEADER *dos_hdr = (IMAGE_DOS_HEADER *)image; + IMAGE_NT_HEADERS64 *nt_hdrs = (IMAGE_NT_HEADERS64 *)(image + dos_hdr->e_lfanew); + + // Validate SizeOfOptionalHeader doesn't cause sections pointer to go out of bounds + size_t sections_offset = dos_hdr->e_lfanew + sizeof(uint32_t) + sizeof(IMAGE_FILE_HEADER) + nt_hdrs->FileHeader.SizeOfOptionalHeader; + size_t sections_end = sections_offset + (size_t)nt_hdrs->FileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER); + if (sections_end > file_size) { + panic(true, "pe: Section headers extend beyond file bounds"); + } + + IMAGE_SECTION_HEADER *sections = (IMAGE_SECTION_HEADER *)((uintptr_t)&nt_hdrs->OptionalHeader + nt_hdrs->FileHeader.SizeOfOptionalHeader); + + bool is_reloc = true; + + if (nt_hdrs->FileHeader.Characteristics & IMAGE_FILE_RELOCS_STRIPPED) { + is_reloc = false; + } + + if (_is_reloc) { + *_is_reloc = is_reloc; + } + + uint64_t image_base = nt_hdrs->OptionalHeader.ImageBase; + uint64_t image_size = nt_hdrs->OptionalHeader.SizeOfImage; + uint64_t alignment = nt_hdrs->OptionalHeader.SectionAlignment; + + if (alignment > 1 && (alignment & (alignment - 1)) != 0) { + panic(true, "pe: SectionAlignment is not a power of 2"); + } + + bool lower_to_higher = false; + + if (image_base < FIXED_HIGHER_HALF_OFFSET_64) { + if (!is_reloc) { + panic(true, "pe: Lower half images are not allowed"); + } + + lower_to_higher = true; + } + + uint64_t slide = 0; + size_t try_count = 0; + size_t max_simulated_tries = 0x10000; + + if (lower_to_higher) { + slide = FIXED_HIGHER_HALF_OFFSET_64 - image_base; + } + + *physical_base = (uintptr_t)ext_mem_alloc_type_aligned(image_size, alloc_type, alignment); + *virtual_base = image_base; + + // Validate SizeOfHeaders doesn't exceed file size or image size + if (nt_hdrs->OptionalHeader.SizeOfHeaders > file_size + || nt_hdrs->OptionalHeader.SizeOfHeaders > image_size) { + panic(true, "pe: SizeOfHeaders exceeds file or image size"); + } + + memcpy((void *)(uintptr_t)*physical_base, image, nt_hdrs->OptionalHeader.SizeOfHeaders); + + if (_image_size) { + *_image_size = image_size; + } + + if (is_reloc && kaslr) { +again: + slide = (rand32() & ~(alignment - 1)) + (lower_to_higher ? FIXED_HIGHER_HALF_OFFSET_64 - image_base : 0); + + if (*virtual_base + slide + image_size < 0xffffffff80000000 /* this comparison relies on overflow */) { + if (++try_count == max_simulated_tries) { + panic(true, "pe: Image wants to load too high"); + } + goto again; + } + } + + for (size_t i = 0; i < nt_hdrs->FileHeader.NumberOfSections; i++) { + IMAGE_SECTION_HEADER *section = §ions[i]; + + uintptr_t section_base = *physical_base + section->VirtualAddress; + uint32_t section_raw_size = section->VirtualSize < section->SizeOfRawData ? section->VirtualSize : section->SizeOfRawData; + + // Validate section doesn't write past the image buffer + if ((uint64_t)section->VirtualAddress + section_raw_size > image_size) { + panic(true, "pe: Section %U exceeds image bounds", (uint64_t)i); + } + + // Validate section data doesn't exceed file bounds + if ((uint64_t)section->PointerToRawData + section_raw_size > file_size) { + panic(true, "pe: Section %U data extends beyond file bounds", (uint64_t)i); + } + + memcpy((void *)section_base, image + section->PointerToRawData, section_raw_size); + } + + if (nt_hdrs->OptionalHeader.NumberOfRvaAndSizes < IMAGE_DIRECTORY_ENTRY_BASERELOC + 1) { + panic(true, "pe: NumberOfRvaAndSizes too small for import/reloc directories"); + } + + IMAGE_DATA_DIRECTORY *import_dir = &nt_hdrs->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]; + IMAGE_DATA_DIRECTORY *reloc_dir = &nt_hdrs->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC]; + + if (import_dir->Size != 0) { + if (import_dir->VirtualAddress >= image_size || + sizeof(IMAGE_IMPORT_DESCRIPTOR) > image_size - import_dir->VirtualAddress) { + panic(true, "pe: Import directory VirtualAddress out of bounds"); + } + IMAGE_IMPORT_DESCRIPTOR *import_desc = (IMAGE_IMPORT_DESCRIPTOR *)((uintptr_t)*physical_base + import_dir->VirtualAddress); + + if (import_desc->Name != 0) { + panic(true, "pe: Kernel must not have any imports"); + } + } + + if (reloc_dir->VirtualAddress != 0) { + if (reloc_dir->VirtualAddress >= image_size || + reloc_dir->Size > image_size - reloc_dir->VirtualAddress) { + panic(true, "pe: Relocation directory VirtualAddress out of bounds"); + } + size_t reloc_block_offset = 0; + + while (reloc_dir->Size - reloc_block_offset >= sizeof(IMAGE_BASE_RELOCATION_BLOCK)) { + IMAGE_BASE_RELOCATION_BLOCK *block = (IMAGE_BASE_RELOCATION_BLOCK *)((uintptr_t)*physical_base + reloc_dir->VirtualAddress + reloc_block_offset); + + // Validate SizeOfBlock to prevent infinite loop (if 0) and underflow (if too small) + if (block->SizeOfBlock < sizeof(IMAGE_BASE_RELOCATION_BLOCK)) { + panic(true, "pe: Invalid relocation block size"); + } + + if (block->SizeOfBlock > reloc_dir->Size - reloc_block_offset) { + panic(true, "pe: Relocation block size exceeds directory"); + } + + if (block->VirtualAddress >= image_size) { + panic(true, "pe: Relocation block VirtualAddress out of bounds"); + } + + uintptr_t block_base = *physical_base + block->VirtualAddress; + size_t entries = (block->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION_BLOCK)) / sizeof(uint16_t); + uint16_t *relocs = (uint16_t *)(block + 1); + + for (size_t i = 0; i < entries; i++) { + uint16_t type = relocs[i] >> 12; + uint16_t offset = relocs[i] & 0xfff; + + if (type == IMAGE_REL_BASED_ABSOLUTE) { + continue; + } + + size_t write_size; + switch (type) { + case IMAGE_REL_BASED_HIGHLOW: + if (lower_to_higher) { + panic(true, "pe: 32-bit relocations are incompatible with higher-half loading"); + } + write_size = 4; + break; + case IMAGE_REL_BASED_DIR64: write_size = 8; break; + default: + panic(true, "pe: Unsupported relocation type %u", type); + __builtin_unreachable(); + } + + if ((uint64_t)block->VirtualAddress + offset + write_size > image_size) { + panic(true, "pe: Relocation offset out of bounds"); + } + + switch (type) { + case IMAGE_REL_BASED_HIGHLOW: + *(uint32_t *)(block_base + offset) += slide; + break; + case IMAGE_REL_BASED_DIR64: + *(uint64_t *)(block_base + offset) += slide; + break; + } + } + + reloc_block_offset += block->SizeOfBlock; + } + } + + if (image_size_before_bss) { + *image_size_before_bss = image_size; + } + + *virtual_base += slide; + *entry_point = *virtual_base + nt_hdrs->OptionalHeader.AddressOfEntryPoint; + + if (_slide) { + *_slide = slide; + } + + if (_ranges && _ranges_count) { + size_t range_count = 0; + + bool headers_within_section = false; + + for (size_t i = 0; i < nt_hdrs->FileHeader.NumberOfSections; i++) { + IMAGE_SECTION_HEADER *section = §ions[i]; + + if (section->VirtualAddress == 0) { + headers_within_section = true; + } + + range_count++; + } + + if (!headers_within_section) { + range_count++; + } + + struct mem_range *ranges = ext_mem_alloc(range_count * sizeof(struct mem_range)); + + *_ranges = ranges; + *_ranges_count = range_count; + + size_t range_index = 0; + + if (!headers_within_section) { + struct mem_range *range = &ranges[range_index++]; + range->base = *virtual_base; + range->length = ALIGN_UP(nt_hdrs->OptionalHeader.SizeOfHeaders, 0x1000); + range->permissions = MEM_RANGE_R; + } + + for (size_t i = 0; i < nt_hdrs->FileHeader.NumberOfSections; i++) { + IMAGE_SECTION_HEADER *section = §ions[i]; + + uintptr_t misalign = section->VirtualAddress % alignment; + + struct mem_range *range = &ranges[range_index++]; + range->base = *virtual_base + ALIGN_DOWN(section->VirtualAddress, alignment); + range->length = ALIGN_UP(section->VirtualSize + misalign, alignment); + + if (section->Characteristics & IMAGE_SCN_MEM_EXECUTE) { + range->permissions |= MEM_RANGE_X; + } + + if (section->Characteristics & IMAGE_SCN_MEM_WRITE) { + range->permissions |= MEM_RANGE_W; + } + + if (section->Characteristics & IMAGE_SCN_MEM_READ) { + range->permissions |= MEM_RANGE_R; + } + } + } + + return true; +} diff --git a/limine/common/lib/pe.h b/limine/common/lib/pe.h new file mode 100644 index 0000000..7923916 --- /dev/null +++ b/limine/common/lib/pe.h @@ -0,0 +1,12 @@ +#ifndef LIB__PE_H__ +#define LIB__PE_H__ + +#include +#include +#include + +int pe_bits(uint8_t *image, size_t image_size); + +bool pe64_load(uint8_t *image, size_t file_size, uint64_t *entry_point, uint64_t *_slide, uint32_t alloc_type, bool kaslr, struct mem_range **ranges, uint64_t *ranges_count, uint64_t *physical_base, uint64_t *virtual_base, uint64_t *image_size, uint64_t *image_size_before_bss, bool *is_reloc); + +#endif diff --git a/limine/common/lib/print.h b/limine/common/lib/print.h new file mode 100644 index 0000000..a5e9a99 --- /dev/null +++ b/limine/common/lib/print.h @@ -0,0 +1,16 @@ +#ifndef LIB__PRINT_H__ +#define LIB__PRINT_H__ + +#include +#include + +extern bool verbose; + +void print(const char *fmt, ...); +void vprint(const char *fmt, va_list args); + +#define printv(FMT, ...) do { \ + if (verbose) print(FMT, ##__VA_ARGS__); \ +} while (0) + +#endif diff --git a/limine/common/lib/print.s2.c b/limine/common/lib/print.s2.c new file mode 100644 index 0000000..3e34e0e --- /dev/null +++ b/limine/common/lib/print.s2.c @@ -0,0 +1,235 @@ +#include +#include +#include +#include +#include +#include +#include +#if defined (BIOS) +#include +#endif +#include +#include + +#if defined (BIOS) +static void s2_print(const char *s, size_t len) { + for (size_t i = 0; i < len; i++) { + struct rm_regs r = {0}; + r.eax = 0x0e00 | s[i]; + rm_int(0x10, &r, &r); + } +} +#endif + +static const char *base_digits = "0123456789abcdef"; + +#define PRINT_BUF_MAX 4096 + +static void prn_char(char *print_buf, size_t *print_buf_i, char c) { + if (c == '\n') { + prn_char(print_buf, print_buf_i, '\r'); + } + + if (*print_buf_i < (PRINT_BUF_MAX - 1)) { + print_buf[(*print_buf_i)++] = c; + } + + print_buf[*print_buf_i] = 0; +} + +static void prn_str(char *print_buf, size_t *print_buf_i, const char *string) { + size_t i; + + for (i = 0; string[i]; i++) { + prn_char(print_buf, print_buf_i, string[i]); + } +} + +static void prn_nstr(char *print_buf, size_t *print_buf_i, const char *string, size_t len) { + size_t i; + + for (i = 0; i < len; i++) { + prn_char(print_buf, print_buf_i, string[i]); + } +} + +static void prn_i(char *print_buf, size_t *print_buf_i, int64_t x) { + int i; + char buf[21] = {0}; + + if (!x) { + prn_char(print_buf, print_buf_i, '0'); + return; + } + + int sign = x < 0; + uint64_t ux = sign ? (uint64_t)0 - (uint64_t)x : (uint64_t)x; + + for (i = 19; ux; i--) { + buf[i] = (ux % 10) + 0x30; + ux = ux / 10; + } + if (sign) + buf[i] = '-'; + else + i++; + + prn_str(print_buf, print_buf_i, buf + i); +} + +static void prn_ui(char *print_buf, size_t *print_buf_i, uint64_t x) { + int i; + char buf[21] = {0}; + + if (!x) { + prn_char(print_buf, print_buf_i, '0'); + return; + } + + for (i = 19; x; i--) { + buf[i] = (x % 10) + 0x30; + x = x / 10; + } + + i++; + prn_str(print_buf, print_buf_i, buf + i); +} + +static void prn_x(char *print_buf, size_t *print_buf_i, uint64_t x) { + int i; + char buf[17] = {0}; + + if (!x) { + prn_str(print_buf, print_buf_i, "0x0"); + return; + } + + for (i = 15; x; i--) { + buf[i] = base_digits[(x % 16)]; + x = x / 16; + } + + i++; + prn_str(print_buf, print_buf_i, "0x"); + prn_str(print_buf, print_buf_i, buf + i); +} + +void print(const char *fmt, ...) { + va_list args; + + va_start(args, fmt); + vprint(fmt, args); + va_end(args); +} + +static char print_buf[PRINT_BUF_MAX]; + +void vprint(const char *fmt, va_list args) { + size_t print_buf_i = 0; + + for (;;) { + while (*fmt && *fmt != '%') { + prn_char(print_buf, &print_buf_i, *fmt++); + } + + if (!*fmt++) + goto out; + + switch (*fmt++) { + case 's': { + char *str = (char *)va_arg(args, const char *); + if (!str) + prn_str(print_buf, &print_buf_i, "(null)"); + else + prn_str(print_buf, &print_buf_i, str); } + break; + case 'S': { + char *str = (char *)va_arg(args, const char *); + size_t str_len = va_arg(args, size_t); + if (!str) + prn_str(print_buf, &print_buf_i, "(null)"); + else + prn_nstr(print_buf, &print_buf_i, str, str_len); } + break; + case 'd': + prn_i(print_buf, &print_buf_i, (int64_t)va_arg(args, int32_t)); + break; + case 'u': + prn_ui(print_buf, &print_buf_i, (uint64_t)va_arg(args, uint32_t)); + break; + case 'x': + prn_x(print_buf, &print_buf_i, (uint64_t)va_arg(args, uint32_t)); + break; + case 'D': + prn_i(print_buf, &print_buf_i, va_arg(args, int64_t)); + break; + case 'U': + prn_ui(print_buf, &print_buf_i, va_arg(args, uint64_t)); + break; + case 'X': + prn_x(print_buf, &print_buf_i, va_arg(args, uint64_t)); + break; + case 'p': + prn_x(print_buf, &print_buf_i, va_arg(args, uintptr_t)); + break; + case 'c': { + char c = (char)va_arg(args, int); + prn_char(print_buf, &print_buf_i, c); } + break; + case '#': { + bool printed = false; + char *str = (char *)va_arg(args, const char *); + for (int i = (int)strlen(str) - 1; i >= 0; i--) { + if (str[i] != '#') { + continue; + } + + prn_nstr(print_buf, &print_buf_i, str, i); + printed = true; + break; + } + + if (!printed) { + prn_str(print_buf, &print_buf_i, str); + } + + break; + } + default: + prn_char(print_buf, &print_buf_i, '?'); + break; + } + } + +out: + if (!quiet) { +#if defined (BIOS) + if (stage3_loaded) { +#endif + FOR_TERM(flanterm_write(TERM, print_buf, print_buf_i)); +#if defined (BIOS) + } else { + s2_print(print_buf, print_buf_i); + } +#endif + } + + for (size_t i = 0; i < print_buf_i; i++) { +#if defined (__x86_64__) || defined (__i386__) + if (E9_OUTPUT) { + outb(0xe9, print_buf[i]); + } +#endif +#if defined (BIOS) + if (stage3_loaded && ((!quiet && serial) || COM_OUTPUT)) { + if (!isprint(print_buf[i])) { + switch (print_buf[i]) { + case '\r': case '\n': case '\e': break; + default: continue; + } + } + serial_out(print_buf[i]); + } +#endif + } +} diff --git a/limine/common/lib/rand.c b/limine/common/lib/rand.c new file mode 100644 index 0000000..6d34ce1 --- /dev/null +++ b/limine/common/lib/rand.c @@ -0,0 +1,89 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +// TODO: Find where this mersenne twister implementation is inspired from +// and properly credit the original author(s). + +static bool rand_initialised = false; + +#define n ((int)624) +#define m ((int)397) +#define matrix_a ((uint32_t)0x9908b0df) +#define msb ((uint32_t)0x80000000) +#define lsbs ((uint32_t)0x7fffffff) + +static uint32_t *status; +static int ctr; + +static void init_rand(void) { + uint32_t seed = ((uint32_t)0xc597060c * (uint32_t)rdtsc()) + * ((uint32_t)0xce86d624) + ^ ((uint32_t)0xee0da130 * (uint32_t)rdtsc()); + + // TODO(qookie): aarch64 also has an optional HW random number generator +#if defined (__x86_64__) || defined(__i386__) + uint32_t eax, ebx, ecx, edx; + + // Check for rdseed + if (cpuid(0x07, 0, &eax, &ebx, &ecx, &edx) && (ebx & (1 << 18))) { + seed *= (seed ^ rdseed(uint32_t)); + } else if (cpuid(0x01, 0, &eax, &ebx, &ecx, &edx) && (ecx & (1 << 30))) { + seed *= (seed ^ rdrand(uint32_t)); + } +#endif + + status = ext_mem_alloc(n * sizeof(uint32_t)); + + srand(seed); + + rand_initialised = true; +} + +void srand(uint32_t s) { + status[0] = s; + for (ctr = 1; ctr < n; ctr++) + status[ctr] = (1812433253 * (status[ctr - 1] ^ (status[ctr - 1] >> 30)) + ctr); +} + +uint32_t rand32(void) { + if (!rand_initialised) + init_rand(); + + const uint32_t mag01[2] = {0, matrix_a}; + + if (ctr >= n) { + for (int kk = 0; kk < n - m; kk++) { + uint32_t y = (status[kk] & msb) | (status[kk + 1] & lsbs); + status[kk] = status[kk + m] ^ (y >> 1) ^ mag01[y & 1]; + } + + for (int kk = n - m; kk < n - 1; kk++) { + uint32_t y = (status[kk] & msb) | (status[kk + 1] & lsbs); + status[kk] = status[kk + (m - n)] ^ (y >> 1) ^ mag01[y & 1]; + } + + uint32_t y = (status[n - 1] & msb) | (status[0] & lsbs); + status[n - 1] = status[m - 1] ^ (y >> 1) ^ mag01[y & 1]; + + ctr = 0; + } + + uint32_t res = status[ctr++]; + + res ^= (res >> 11); + res ^= (res << 7) & 0x9d2c5680; + res ^= (res << 15) & 0xefc60000; + res ^= (res >> 18); + + return res; +} + +uint64_t rand64(void) { + return (((uint64_t)rand32()) << 32) | (uint64_t)rand32(); +} diff --git a/limine/common/lib/rand.h b/limine/common/lib/rand.h new file mode 100644 index 0000000..ff259f5 --- /dev/null +++ b/limine/common/lib/rand.h @@ -0,0 +1,11 @@ +#ifndef LIB__RAND_H__ +#define LIB__RAND_H__ + +#include + +void srand(uint32_t s); + +uint32_t rand32(void); +uint64_t rand64(void); + +#endif diff --git a/limine/common/lib/real.h b/limine/common/lib/real.h new file mode 100644 index 0000000..564de08 --- /dev/null +++ b/limine/common/lib/real.h @@ -0,0 +1,34 @@ +#ifndef LIB__REAL_H__ +#define LIB__REAL_H__ + +#include +#include + +#define rm_seg(x) ((uint16_t)(((int)(x) & 0xffff0) >> 4)) +#define rm_off(x) ((uint16_t)(((int)(x) & 0x0000f) >> 0)) + +#define rm_desegment(seg, off) (((uint32_t)(seg) << 4) + (uint32_t)(off)) + +#define EFLAGS_CF (1 << 0) +#define EFLAGS_ZF (1 << 6) + +struct rm_regs { + uint16_t gs; + uint16_t fs; + uint16_t es; + uint16_t ds; + uint32_t eflags; + uint32_t ebp; + uint32_t edi; + uint32_t esi; + uint32_t edx; + uint32_t ecx; + uint32_t ebx; + uint32_t eax; +} __attribute__((packed)); + +void rm_int(uint8_t int_no, struct rm_regs *out_regs, struct rm_regs *in_regs); + +noreturn void rm_hcf(void); + +#endif diff --git a/limine/common/lib/real.s2.asm_bios_ia32 b/limine/common/lib/real.s2.asm_bios_ia32 new file mode 100644 index 0000000..15bfabd --- /dev/null +++ b/limine/common/lib/real.s2.asm_bios_ia32 @@ -0,0 +1,168 @@ +section .realmode + +global rm_hcf +rm_hcf: + ; Load BIOS IVT + lidt [.rm_idt] + + ; Jump to real mode + jmp 0x08:.bits16 + .bits16: + bits 16 + mov ax, 0x10 + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + mov ss, ax + mov eax, cr0 + btr ax, 0 + mov cr0, eax + jmp 0x00:.cszero + .cszero: + xor ax, ax + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + mov ss, ax + + sti + .hang: + hlt + jmp .hang + bits 32 + + .rm_idt: dw 0x3ff + dd 0 + +global rm_int +rm_int: + ; Self-modifying code: int $int_no + mov al, byte [esp+4] + mov byte [.int_no], al + + ; Save out_regs + mov eax, dword [esp+8] + mov dword [.out_regs], eax + + ; Save in_regs + mov eax, dword [esp+12] + mov dword [.in_regs], eax + + ; Save GDT in case BIOS overwrites it + sgdt [.gdt] + + ; Save IDT + sidt [.idt] + + ; Load BIOS IVT + lidt [.rm_idt] + + ; Save non-scratch GPRs + push ebx + push esi + push edi + push ebp + + ; Jump to real mode + jmp 0x08:.bits16 + .bits16: + bits 16 + mov ax, 0x10 + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + mov ss, ax + mov eax, cr0 + and al, 0xfe + mov cr0, eax + jmp 0x00:.cszero + .cszero: + xor ax, ax + mov ss, ax + + ; Load in_regs + mov dword [ss:.esp], esp + mov esp, dword [ss:.in_regs] + pop gs + pop fs + pop es + pop ds + popfd + pop ebp + pop edi + pop esi + pop edx + pop ecx + pop ebx + pop eax + mov esp, dword [ss:.esp] + + sti + + ; Indirect interrupt call + db 0xcd + .int_no: + db 0 + + cli + + ; Load out_regs + mov dword [ss:.esp], esp + mov esp, dword [ss:.out_regs] + lea esp, [esp + 10*4] + push eax + push ebx + push ecx + push edx + push esi + push edi + push ebp + pushfd + push ds + push es + push fs + push gs + mov esp, dword [ss:.esp] + + ; Restore GDT + o32 lgdt [ss:.gdt] + + ; Restore IDT + o32 lidt [ss:.idt] + + ; Jump back to pmode + mov eax, cr0 + or al, 1 + mov cr0, eax + jmp 0x18:.bits32 + .bits32: + bits 32 + mov ax, 0x20 + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + mov ss, ax + + ; Restore non-scratch GPRs + pop ebp + pop edi + pop esi + pop ebx + + ; Exit + ret + +align 16 + .esp: dd 0 + .out_regs: dd 0 + .in_regs: dd 0 + .gdt: dq 0 + .idt: dq 0 + .rm_idt: dw 0x3ff + dd 0 + +section .note.GNU-stack noalloc noexec nowrite progbits diff --git a/limine/common/lib/sleep.asm_bios_ia32 b/limine/common/lib/sleep.asm_bios_ia32 new file mode 100644 index 0000000..fdd120a --- /dev/null +++ b/limine/common/lib/sleep.asm_bios_ia32 @@ -0,0 +1,157 @@ +section .realmode + +int_08_ticks_counter: dd 0 +int_08_callback: dd 0 + +int_08_isr: + bits 16 + pushf + inc dword [cs:int_08_ticks_counter] + popf + jmp far [cs:int_08_callback] + bits 32 + +extern getchar_internal + +global _pit_sleep_and_quit_on_keypress +_pit_sleep_and_quit_on_keypress: + ; Hook int 0x08 + mov edx, dword [0x08*4] + mov dword [int_08_callback], edx + mov dword [0x08*4], int_08_isr + + ; pit_ticks in edx + mov edx, dword [esp+4] + + mov dword [int_08_ticks_counter], 0 + + ; Save GDT in case BIOS overwrites it + sgdt [.gdt] + + ; Save IDT + sidt [.idt] + + ; Load BIOS IVT + lidt [.rm_idt] + + ; Save non-scratch GPRs + push ebx + push esi + push edi + push ebp + + ; Jump to real mode + jmp 0x08:.bits16 + .bits16: + bits 16 + mov ax, 0x10 + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + mov ss, ax + mov eax, cr0 + and al, 0xfe + mov cr0, eax + jmp 0x00:.cszero + .cszero: + xor ax, ax + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + mov ss, ax + + sti + + mov byte [.mods], 0 + mov byte [.ascii], 0 + mov byte [.scan], 0 + + .loop: + cmp dword [int_08_ticks_counter], edx + je .done + + push ecx + push edx + mov ah, 0x01 + xor al, al + int 0x16 + pop edx + pop ecx + + jz .loop + + ; on keypress + xor ax, ax + int 0x16 + mov byte [.ascii], al + mov byte [.scan], ah + + mov ax, 0x0200 + int 0x16 + test al, 0x04 + jz .done + + ; ctrl handling + mov byte [.mods], 0x04 + add byte [.ascii], 0x60 + + .done: + cli + + ; Restore GDT + o32 lgdt [ss:.gdt] + + ; Restore IDT + o32 lidt [ss:.idt] + + ; Jump back to pmode + mov ebx, cr0 + or bl, 1 + mov cr0, ebx + jmp 0x18:.bits32 + .bits32: + bits 32 + mov bx, 0x20 + mov ds, bx + mov es, bx + mov fs, bx + mov gs, bx + mov ss, bx + + ; Restore non-scratch GPRs + pop ebp + pop edi + pop esi + pop ebx + + ; Dehook int 0x08 + mov edx, dword [int_08_callback] + mov dword [0x08*4], edx + + cmp byte [.scan], 0 + je .fail + + push dword [.mods] + push dword [.ascii] + push dword [.scan] + call getchar_internal + add esp, 3*4 + + ret + + .fail: + xor eax, eax + ret + + .gdt: dq 0 + .idt: dq 0 + .rm_idt: dw 0x3ff + dd 0 + + .mods: dd 0 + .ascii: dd 0 + .scan: dd 0 + +section .note.GNU-stack noalloc noexec nowrite progbits diff --git a/limine/common/lib/spinup.asm_aarch64 b/limine/common/lib/spinup.asm_aarch64 new file mode 100644 index 0000000..d683def --- /dev/null +++ b/limine/common/lib/spinup.asm_aarch64 @@ -0,0 +1,113 @@ +#include + +.section .text + +// noreturn void enter_in_el1(uint64_t entry, uint64_t sp, uint64_t sctlr, +// uint64_t mair, uint64_t tcr, uint64_t ttbr0, +// uint64_t ttbr1, uint64_t direct_map_offset) +// Potentially drop to EL1 from EL2 (and also disable trapping to EL2), then +// configure EL1 state and jump to kernel. + +.global enter_in_el1 +enter_in_el1: + msr spsel, #0 + mov sp, x1 + + PICK_EL x8, 0f, 2f +0: + // Switch to the new page tables + + // Point the EL1t handler to the continuation, such that after we page fault, + // execution continues and the kernel is entered. + adrp x8, 1f + add x8, x8, #:lo12:1f + add x8, x8, x7 + msr vbar_el1, x8 + isb + dsb sy + isb + + // Switch the page table registers + msr mair_el1, x3 + msr tcr_el1, x4 + msr ttbr0_el1, x5 + msr ttbr1_el1, x6 + msr sctlr_el1, x2 + isb + dsb sy + isb + + // Jump to the higher half mapping in case we didn't immediately crash + br x8 + +// Alignment required by VBAR register +.align 11 +1: + // Zero out VBAR to avoid confusion + msr vbar_el1, xzr + + // Enter kernel in EL1 + mov x8, #0x3c4 + msr spsr_el1, x8 + msr elr_el1, x0 + + mov x0, xzr + ZERO_REGS_EXCEPT_X0 + + eret + +2: + // Check HCR_EL2.E2H + mrs x8, hcr_el2 + tbnz x8, #34, 3f + + // Configure EL1 state (normal silicon) + msr mair_el1, x3 + msr tcr_el1, x4 + msr ttbr0_el1, x5 + msr ttbr1_el1, x6 + msr sctlr_el1, x2 + dsb sy + isb + b 4f + +3: + // Configure EL1 state (apple silicon) + msr s3_5_c10_c2_0, x3 // MAIR_EL12 + msr s3_5_c2_c0_2, x4 // TCR_EL12 + msr s3_5_c2_c0_0, x5 // TTBR0_EL12 + msr s3_5_c2_c0_1, x6 // TTBR1_EL12 + msr s3_5_c1_c0_0, x2 // SCTLR_EL12 + dsb sy + isb + +4: + + // Configure EL2-specific state for EL1 + + // Don't trap counters to EL2 + mrs x8, cnthctl_el2 + orr x8, x8, #3 + msr cnthctl_el2, x8 + msr cntvoff_el2, xzr + + // Enable AArch64 in EL1 + ldr x8, =0x80000002 + msr hcr_el2, x8 + + // Don't trap FP/SIMD to EL2 + mov x8, #0x33FF + msr cptr_el2, x8 + msr hstr_el2, xzr + + // Enter kernel in EL1 + mov x8, #0x3c4 + msr spsr_el2, x8 + msr elr_el2, x0 + + mov x0, xzr + ZERO_REGS_EXCEPT_X0 + + eret + +.section .note.GNU-stack,"",%progbits diff --git a/limine/common/lib/spinup.asm_bios_ia32 b/limine/common/lib/spinup.asm_bios_ia32 new file mode 100644 index 0000000..53bc298 --- /dev/null +++ b/limine/common/lib/spinup.asm_bios_ia32 @@ -0,0 +1,49 @@ +section .rodata + +invalid_idt: + dd 0, 0 + +section .text + +extern flush_irqs + +global common_spinup +bits 32 +common_spinup: + cli + + lidt [invalid_idt] + + call flush_irqs + + xor eax, eax + lldt ax + + ; We don't need the return address + add esp, 4 + + ; Get function address + pop edi + + ; We don't need the argument count + add esp, 4 + + mov eax, 0x00000011 + mov cr0, eax + + xor eax, eax + mov cr4, eax + mov cr3, eax + + ; Clear TSS busy bit and load TR with base 0, limit 0 + sub esp, 8 + sgdt [esp] + mov eax, [esp + 2] + add esp, 8 + mov byte [eax + 0x3d], 0x89 + mov ax, 0x38 + ltr ax + + call edi + +section .note.GNU-stack noalloc noexec nowrite progbits diff --git a/limine/common/lib/spinup.asm_loongarch64 b/limine/common/lib/spinup.asm_loongarch64 new file mode 100644 index 0000000..5793327 --- /dev/null +++ b/limine/common/lib/spinup.asm_loongarch64 @@ -0,0 +1,110 @@ +.section .text + +#define PAGE_SHIFT 12 +#define PT_SHIFT (PAGE_SHIFT - 3) +#define PT_BASE(level) (PAGE_SHIFT + PT_SHIFT * (level)) + +#define MAKE_PWCL(Dir2_width, Dir2_base, Dirl_width, Dirl_base, PTwidth, PTbase) \ + ((Dir2_width) << 25) | ((Dir2_base) << 20) | ((Dirl_width) << 15) | \ + ((Dirl_base) << 10) | ((PTwidth) << 5) | ((PTbase) << 0) + +#define MAKE_PWCH(Dir4_width, Dir4_base, Dir3_width, Dir3_base) \ + ((Dir4_width) << 18) | ((Dir4_base) << 12) | ((Dir3_width) << 6) | \ + ((Dir3_base) << 0) + +#define CSR_CRMD 0x00 +#define CSR_EENTRY 0xc +#define CSR_PGDL 0x19 +#define CSR_PGDH 0x1a +#define CSR_PGD 0x1b +#define CSR_PWCL 0x1c +#define CSR_PWCH 0x1d +#define CSR_STLBPS 0x1e +#define CSR_TLBRENTRY 0x88 +#define CSR_TLBRSAVE 0x8b +#define CSR_TLBREHI 0x8e +#define CSR_MERRENTRY 0x93 +#define CSR_DMW0 0x180 +#define CSR_DMW1 0x181 +#define CSR_DMW2 0x182 +#define CSR_DMW3 0x183 + +.global loongarch_spinup +loongarch_spinup: + li.d $t0, 0b010001 // MAT=01, PLV1..3=0, PLV0=1 + csrwr $t0, CSR_DMW0 + csrwr $zero, CSR_DMW1 + csrwr $zero, CSR_DMW2 + csrwr $zero, CSR_DMW3 + + li.d $t0, 0b010110000 // DATF=01, DATM=01, PG=1, DA=0, IE=0, PLV=00 + csrwr $t0, CSR_CRMD + + invtlb 0, $zero, $zero + li.d $t0, PAGE_SHIFT + csrwr $t0, CSR_STLBPS + csrwr $t0, CSR_TLBREHI + + csrwr $a2, CSR_PGDL + csrwr $a3, CSR_PGDH + + li.d $t0, MAKE_PWCL(PT_SHIFT, PT_BASE(2), PT_SHIFT, PT_BASE(1), PT_SHIFT, PT_BASE(0)) + csrwr $t0, CSR_PWCL + li.d $t0, MAKE_PWCH(0, 0, PT_SHIFT, PT_BASE(3)) + csrwr $t0, CSR_PWCH + + la $t0, loongarch_handle_refill + csrwr $t0, CSR_TLBRENTRY + + csrwr $zero, CSR_EENTRY + csrwr $zero, CSR_MERRENTRY + + move $t0, $a0 + move $sp, $a1 + + move $ra, $zero + move $tp, $zero + move $a0, $zero + move $a1, $zero + move $a2, $zero + move $a3, $zero + move $a4, $zero + move $a5, $zero + move $a6, $zero + move $a7, $zero + move $t1, $zero + move $t2, $zero + move $t3, $zero + move $t4, $zero + move $t5, $zero + move $t6, $zero + move $t7, $zero + move $t8, $zero + move $fp, $zero + move $s0, $zero + move $s1, $zero + move $s2, $zero + move $s3, $zero + move $s4, $zero + move $s5, $zero + move $s6, $zero + move $s7, $zero + move $s8, $zero + + jirl $zero, $t0, 0 + +.global loongarch_handle_refill +.align 12 +loongarch_handle_refill: + csrwr $t0, CSR_TLBRSAVE + csrrd $t0, CSR_PGD + lddir $t0, $t0, 3 + lddir $t0, $t0, 2 + lddir $t0, $t0, 1 + ldpte $t0, 0 + ldpte $t0, 1 + tlbfill + csrrd $t0, CSR_TLBRSAVE + ertn + +.section .note.GNU-stack,"",%progbits diff --git a/limine/common/lib/spinup.asm_riscv64 b/limine/common/lib/spinup.asm_riscv64 new file mode 100644 index 0000000..8e964ee --- /dev/null +++ b/limine/common/lib/spinup.asm_riscv64 @@ -0,0 +1,55 @@ + +.section .text + +.global riscv_spinup +riscv_spinup: +.option norelax + csrci sstatus, 0x2 + csrw sie, zero + + lla t0, 0f + add t0, t0, a3 + csrw stvec, t0 + csrw satp, a2 + sfence.vma + unimp +.align 4 +0: + csrw stvec, zero + + mv t0, a0 + mv sp, a1 + + mv a0, zero + mv a1, zero + mv a2, zero + mv a3, zero + mv a4, zero + mv a5, zero + mv a6, zero + mv a7, zero + mv s0, zero + mv s1, zero + mv s2, zero + mv s3, zero + mv s4, zero + mv s5, zero + mv s6, zero + mv s7, zero + mv s8, zero + mv s9, zero + mv s10, zero + mv s11, zero + mv t1, zero + mv t2, zero + mv t3, zero + mv t4, zero + mv t5, zero + mv t6, zero + mv tp, zero + mv gp, zero + mv ra, zero + + jr t0 + +.section .note.GNU-stack,"",%progbits diff --git a/limine/common/lib/spinup.asm_uefi_ia32 b/limine/common/lib/spinup.asm_uefi_ia32 new file mode 100644 index 0000000..397e4d8 --- /dev/null +++ b/limine/common/lib/spinup.asm_uefi_ia32 @@ -0,0 +1,74 @@ +extern _GLOBAL_OFFSET_TABLE_ + +extern gdt + +section .text + +extern flush_irqs + +global common_spinup +bits 32 +common_spinup: + cli + + push 0 + push 0 + lidt [esp] + add esp, 8 + + call .get_got + .get_got: + pop ebx + add ebx, _GLOBAL_OFFSET_TABLE_ + $$ - .get_got wrt ..gotpc + + lgdt [ebx + gdt wrt ..gotoff] + + push dword 0x18 + call .p1 + .p1: + pop eax + add eax, 6 + push eax + retfd + + .flush_cs: + mov eax, 0x20 + mov ds, eax + mov es, eax + mov fs, eax + mov gs, eax + mov ss, eax + + call flush_irqs + + xor eax, eax + lldt ax + + ; We don't need the return address + add esp, 4 + + ; Get function address + pop edi + + ; We don't need the argument count + add esp, 4 + + mov eax, 0x00000011 + mov cr0, eax + + xor eax, eax + mov cr4, eax + mov cr3, eax + + ; Clear TSS busy bit and load TR with base 0, limit 0 + sub esp, 8 + sgdt [esp] + mov eax, [esp + 2] + add esp, 8 + mov byte [eax + 0x3d], 0x89 + mov ax, 0x38 + ltr ax + + call edi + +section .note.GNU-stack noalloc noexec nowrite progbits diff --git a/limine/common/lib/spinup.asm_uefi_x86_64 b/limine/common/lib/spinup.asm_uefi_x86_64 new file mode 100644 index 0000000..a2bf7ec --- /dev/null +++ b/limine/common/lib/spinup.asm_uefi_x86_64 @@ -0,0 +1,111 @@ +section .rodata + +invalid_idt: + dq 0, 0 + +section .text + +extern flush_irqs + +%macro push32 1 + sub rsp, 4 + mov dword [rsp], %1 +%endmacro + +extern gdt + +global common_spinup +bits 64 +common_spinup: + cli + + lgdt [rel gdt] + lidt [rel invalid_idt] + + lea rbx, [rel .reload_cs] + + push 0x28 + push rbx + retfq +.reload_cs: + mov eax, 0x30 + mov ds, eax + mov es, eax + mov fs, eax + mov gs, eax + mov ss, eax + + push r8 + push r9 + push rcx + push rdx + push rsi + push rdi + call flush_irqs + pop rdi + pop rsi + pop rdx + pop rcx + pop r9 + pop r8 + + mov rbp, rsp + + sub esi, 4 + jle .no_stack_args + +.push_stack_args: + dec esi + mov eax, [rbp + 8 + rsi*8] + push32 eax + test esi, esi + jnz .push_stack_args + +.no_stack_args: + push32 r9d + push32 r8d + push32 ecx + push32 edx + + lea rbx, [rel .go_32] + + push 0x18 + push rbx + retfq + +bits 32 +.go_32: + mov eax, 0x20 + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + mov ss, ax + + xor eax, eax + lldt ax + + mov eax, 0x00000011 + mov cr0, eax + + mov ecx, 0xc0000080 + xor eax, eax + xor edx, edx + wrmsr + + xor eax, eax + mov cr4, eax + mov cr3, eax + + ; Clear TSS busy bit and load TR with base 0, limit 0 + sub esp, 8 + sgdt [esp] + mov eax, [esp + 2] + add esp, 8 + mov byte [eax + 0x3d], 0x89 + mov ax, 0x38 + ltr ax + + call edi + +section .note.GNU-stack noalloc noexec nowrite progbits diff --git a/limine/common/lib/stb_image.c b/limine/common/lib/stb_image.c new file mode 100644 index 0000000..419ab75 --- /dev/null +++ b/limine/common/lib/stb_image.c @@ -0,0 +1,6 @@ +#pragma GCC diagnostic ignored "-Wunused-parameter" +#pragma GCC diagnostic ignored "-Wunused-function" + +#define STB_IMAGE_IMPLEMENTATION + +#include diff --git a/limine/common/lib/term.c b/limine/common/lib/term.c new file mode 100644 index 0000000..cc9fc00 --- /dev/null +++ b/limine/common/lib/term.c @@ -0,0 +1,311 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined (BIOS) +int current_video_mode = -1; +#endif + +struct flanterm_context **terms = NULL; +size_t terms_i = 0; + +int term_backend = _NOT_READY; + +void term_notready(void) { + for (size_t i = 0; i < terms_i; i++) { + struct flanterm_context *term = terms[i]; + + term->deinit(term, pmm_free_size_t); + } + + pmm_free(terms, terms_i * sizeof(void *)); + + terms_i = 0; + terms = NULL; + + term_backend = _NOT_READY; +} + +// --- fallback --- + +#if defined (BIOS) +static void fallback_raw_putchar(struct flanterm_context *ctx, uint8_t c) { + (void)ctx; + struct rm_regs r = {0}; + r.eax = 0x0e00 | c; + rm_int(0x10, &r, &r); +} + +static void fallback_set_cursor_pos(struct flanterm_context *ctx, size_t x, size_t y); +static void fallback_get_cursor_pos(struct flanterm_context *ctx, size_t *x, size_t *y); + +static void fallback_clear(struct flanterm_context *ctx, bool move) { + (void)ctx; + size_t x, y; + fallback_get_cursor_pos(NULL, &x, &y); + struct rm_regs r = {0}; + rm_int(0x11, &r, &r); + switch ((r.eax >> 4) & 3) { + case 0: + r.eax = 3; + break; + case 1: + r.eax = 1; + break; + case 2: + r.eax = 3; + break; + case 3: + r.eax = 7; + break; + } + rm_int(0x10, &r, &r); + if (move) { + x = y = 0; + } + fallback_set_cursor_pos(NULL, x, y); +} + +static void fallback_set_cursor_pos(struct flanterm_context *ctx, size_t x, size_t y) { + (void)ctx; + struct rm_regs r = {0}; + r.eax = 0x0200; + r.ebx = 0; + r.edx = (y << 8) + x; + rm_int(0x10, &r, &r); +} + +static void fallback_get_cursor_pos(struct flanterm_context *ctx, size_t *x, size_t *y) { + (void)ctx; + struct rm_regs r = {0}; + r.eax = 0x0300; + r.ebx = 0; + rm_int(0x10, &r, &r); + *x = r.edx & 0xff; + *y = r.edx >> 8; +} + +static void fallback_scroll(struct flanterm_context *ctx) { + (void)ctx; + size_t x, y; + fallback_get_cursor_pos(NULL, &x, &y); + fallback_set_cursor_pos(NULL, ctx->cols - 1, ctx->rows - 1); + fallback_raw_putchar(NULL, ' '); + fallback_set_cursor_pos(NULL, x, y); +} + +#elif defined (UEFI) + +static size_t cursor_x = 0, cursor_y = 0; + +static void fallback_scroll(struct flanterm_context *ctx) { + (void)ctx; + UINTN uefi_x_size, uefi_y_size; + gST->ConOut->QueryMode(gST->ConOut, gST->ConOut->Mode->Mode, &uefi_x_size, &uefi_y_size); + gST->ConOut->SetCursorPosition(gST->ConOut, uefi_x_size - 1, uefi_y_size - 1); + CHAR16 string[2]; + string[0] = ' '; + string[1] = 0; + gST->ConOut->OutputString(gST->ConOut, string); + gST->ConOut->SetCursorPosition(gST->ConOut, cursor_x, cursor_y); +} + +static void fallback_raw_putchar(struct flanterm_context *ctx, uint8_t c) { + if (!ctx->scroll_enabled && cursor_x == ctx->cols - 1 && cursor_y == ctx->rows - 1) { + return; + } + gST->ConOut->EnableCursor(gST->ConOut, true); + CHAR16 string[2]; + string[0] = c; + string[1] = 0; + gST->ConOut->OutputString(gST->ConOut, string); + if (++cursor_x >= ctx->cols) { + cursor_x = 0; + if (++cursor_y >= ctx->rows) { + cursor_y--; + } + } + gST->ConOut->SetCursorPosition(gST->ConOut, cursor_x, cursor_y); +} + +static void fallback_clear(struct flanterm_context *ctx, bool move) { + (void)ctx; + gST->ConOut->ClearScreen(gST->ConOut); + if (move) { + cursor_x = cursor_y = 0; + } + gST->ConOut->SetCursorPosition(gST->ConOut, cursor_x, cursor_y); +} + +static void fallback_set_cursor_pos(struct flanterm_context *ctx, size_t x, size_t y) { + (void)ctx; + if (x >= ctx->cols || y >= ctx->rows) { + return; + } + gST->ConOut->SetCursorPosition(gST->ConOut, x, y); + cursor_x = x; + cursor_y = y; +} + +static void fallback_get_cursor_pos(struct flanterm_context *ctx, size_t *x, size_t *y) { + (void)ctx; + *x = cursor_x; + *y = cursor_y; +} + +static UINTN ansi_colours[] = { + EFI_BLACK, + EFI_RED, + EFI_GREEN, + EFI_YELLOW, + EFI_BLUE, + EFI_MAGENTA, + EFI_CYAN, + EFI_LIGHTGRAY +}; + +static UINTN conout_current_fg, conout_current_bg; + +static void fallback_set_text_fg(struct flanterm_context *ctx, size_t fg) { + (void)ctx; + conout_current_fg = ansi_colours[fg]; + gST->ConOut->SetAttribute(gST->ConOut, EFI_TEXT_ATTR(conout_current_fg, conout_current_bg)); +} + +static void fallback_set_text_bg(struct flanterm_context *ctx, size_t bg) { + (void)ctx; + conout_current_bg = ansi_colours[bg]; + gST->ConOut->SetAttribute(gST->ConOut, EFI_TEXT_ATTR(conout_current_fg, conout_current_bg)); +} + +static void fallback_set_text_fg_bright(struct flanterm_context *ctx, size_t fg) { + (void)ctx; + conout_current_fg = ansi_colours[fg] | EFI_BRIGHT; + gST->ConOut->SetAttribute(gST->ConOut, EFI_TEXT_ATTR(conout_current_fg, conout_current_bg)); +} + +static void fallback_set_text_bg_bright(struct flanterm_context *ctx, size_t bg) { + (void)ctx; + // bg does not support bright + conout_current_bg = ansi_colours[bg]; + gST->ConOut->SetAttribute(gST->ConOut, EFI_TEXT_ATTR(conout_current_fg, conout_current_bg)); +} + +static void fallback_set_text_fg_default(struct flanterm_context *ctx) { + (void)ctx; + conout_current_fg = EFI_LIGHTGRAY; + gST->ConOut->SetAttribute(gST->ConOut, EFI_TEXT_ATTR(conout_current_fg, conout_current_bg)); +} + +static void fallback_set_text_bg_default(struct flanterm_context *ctx) { + (void)ctx; + conout_current_bg = EFI_BLACK; + gST->ConOut->SetAttribute(gST->ConOut, EFI_TEXT_ATTR(conout_current_fg, conout_current_bg)); +} + +static void fallback_swap_palette(struct flanterm_context *ctx) { + (void)ctx; + UINTN tmp = conout_current_bg; + conout_current_bg = conout_current_fg; + conout_current_fg = tmp; + gST->ConOut->SetAttribute(gST->ConOut, EFI_TEXT_ATTR(conout_current_fg, conout_current_bg)); +} + +#endif + +static bool dummy_handle(void) { + return true; +} + +void term_fallback(void) { + term_notready(); + + terms = ext_mem_alloc(sizeof(void *)); + terms_i = 1; + + terms[0] = ext_mem_alloc(sizeof(struct flanterm_context)); + + struct flanterm_context *term = terms[0]; + +#if defined (UEFI) + if (!efi_boot_services_exited) { +#endif + + fallback_clear(NULL, true); + + term->set_text_fg = (void *)dummy_handle; + term->set_text_bg = (void *)dummy_handle; + term->set_text_fg_bright = (void *)dummy_handle; + term->set_text_bg_bright = (void *)dummy_handle; + term->set_text_fg_rgb = (void *)dummy_handle; + term->set_text_bg_rgb = (void *)dummy_handle; + term->set_text_fg_default = (void *)dummy_handle; + term->set_text_bg_default = (void *)dummy_handle; + term->move_character = (void *)dummy_handle; + term->revscroll = (void *)dummy_handle; + term->swap_palette = (void *)dummy_handle; + term->save_state = (void *)dummy_handle; + term->restore_state = (void *)dummy_handle; + term->double_buffer_flush = (void *)dummy_handle; + term->full_refresh = (void *)dummy_handle; + term->deinit = (void *)dummy_handle; + + term->raw_putchar = fallback_raw_putchar; + term->clear = fallback_clear; + term->set_cursor_pos = fallback_set_cursor_pos; + term->get_cursor_pos = fallback_get_cursor_pos; + term->scroll = fallback_scroll; + term->cols = 80; + term->rows = 24; + term_backend = FALLBACK; + flanterm_context_reinit(term); +#if defined (UEFI) + + term->set_text_fg = fallback_set_text_fg; + term->set_text_bg = fallback_set_text_bg; + term->set_text_fg_bright = fallback_set_text_fg_bright; + term->set_text_bg_bright = fallback_set_text_bg_bright; + term->set_text_fg_default = fallback_set_text_fg_default; + term->set_text_bg_default = fallback_set_text_bg_default; + term->swap_palette = fallback_swap_palette; + + term->set_text_fg_default(term); + term->set_text_bg_default(term); + } else { + if (fb_fbs_count == 0) { + goto fail; + } + + terms[0] = flanterm_fb_init(ext_mem_alloc_size_t, pmm_free_size_t, + (void *)(uintptr_t)fb_fbs[0].framebuffer_addr, fb_fbs[0].framebuffer_width, + fb_fbs[0].framebuffer_height, fb_fbs[0].framebuffer_pitch, + fb_fbs[0].red_mask_size, fb_fbs[0].red_mask_shift, + fb_fbs[0].green_mask_size, fb_fbs[0].green_mask_shift, + fb_fbs[0].blue_mask_size, fb_fbs[0].blue_mask_shift, + NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, 0, 0, 1, + 0, 0, + 0, + FLANTERM_FB_ROTATE_0 + ); + } + + return; + +fail: + pmm_free(terms[0], sizeof(struct flanterm_context)); + pmm_free(terms, sizeof(void *)); + terms_i = 0; + terms = NULL; +#endif +} diff --git a/limine/common/lib/term.h b/limine/common/lib/term.h new file mode 100644 index 0000000..18fd4dd --- /dev/null +++ b/limine/common/lib/term.h @@ -0,0 +1,59 @@ +#ifndef LIB__TERM_H__ +#define LIB__TERM_H__ + +#include +#include +#include +#include + +enum { + _NOT_READY, + GTERM, + TEXTMODE, + FALLBACK +}; + +#if defined (BIOS) +extern int current_video_mode; +#endif + +extern struct flanterm_context **terms; +extern size_t terms_i; + +extern int term_backend; + +#define TERM_CTX_SIZE ((uint64_t)(-1)) +#define TERM_CTX_SAVE ((uint64_t)(-2)) +#define TERM_CTX_RESTORE ((uint64_t)(-3)) +#define TERM_FULL_REFRESH ((uint64_t)(-4)) +#define TERM_OOB_OUTPUT_GET ((uint64_t)(-10)) +#define TERM_OOB_OUTPUT_SET ((uint64_t)(-11)) + +#define FOR_TERM(...) do { \ + for (size_t FOR_TERM_i = 0; FOR_TERM_i < terms_i; FOR_TERM_i++) { \ + struct flanterm_context *TERM = terms[FOR_TERM_i]; \ + __VA_ARGS__ \ + ; \ + } \ +} while (0) + +static inline void reset_term(void) { + for (size_t i = 0; i < terms_i; i++) { + struct flanterm_context *term = terms[i]; + + print("\e[2J\e[H"); + flanterm_context_reinit(term); + term->cursor_enabled = true; + term->double_buffer_flush(term); + } +} + +static inline void set_cursor_pos_helper(size_t x, size_t y) { + print("\e[%u;%uH", (int)y + 1, (int)x + 1); +} + +void term_notready(void); +void term_fallback(void); +void _term_write(struct flanterm_context *term, uint64_t buf, uint64_t count); + +#endif diff --git a/limine/common/lib/time.c b/limine/common/lib/time.c new file mode 100644 index 0000000..da82dbb --- /dev/null +++ b/limine/common/lib/time.c @@ -0,0 +1,75 @@ +#include +#include +#include +#if defined (BIOS) +# include +#elif defined (UEFI) +# include +#endif +#include + +// Julian date calculation from https://en.wikipedia.org/wiki/Julian_day +static int get_jdn(int days, int months, int years) { + return (1461 * (years + 4800 + (months - 14)/12))/4 + (367 * + (months - 2 - 12 * ((months - 14)/12)))/12 - + (3 * ((years + 4900 + (months - 14)/12)/100))/4 + + days - 32075; +} + +static uint64_t get_unix_epoch(uint8_t seconds, uint8_t minutes, uint8_t hours, + uint8_t days, uint8_t months, uint16_t years) { + uint64_t jdn_current = get_jdn(days, months, years); + uint64_t jdn_1970 = get_jdn(1, 1, 1970); + + uint64_t jdn_diff = jdn_current - jdn_1970; + + return (jdn_diff * (60 * 60 * 24)) + hours * 3600 + minutes * 60 + seconds; +} + +#if defined (BIOS) +uint64_t time(void) { + struct rm_regs r; + +again: + r = (struct rm_regs){0}; + r.eax = 0x0400; + rm_int(0x1a, &r, &r); + + uint8_t day = bcd_to_int( r.edx & 0x00ff); + uint8_t month = bcd_to_int((r.edx & 0xff00) >> 8); + uint16_t year = bcd_to_int( r.ecx & 0x00ff) + + /* century */ bcd_to_int((r.ecx & 0xff00) >> 8) * 100; + + r = (struct rm_regs){0}; + r.eax = 0x0200; + rm_int(0x1a, &r, &r); + + uint8_t second = bcd_to_int((r.edx & 0xff00) >> 8); + uint8_t minute = bcd_to_int( r.ecx & 0x00ff); + uint8_t hour = bcd_to_int((r.ecx & 0xff00) >> 8); + + // Check RTC day wraparound + r = (struct rm_regs){0}; + r.eax = 0x0400; + rm_int(0x1a, &r, &r); + if (bcd_to_int(r.edx & 0x00ff) != day) { + goto again; + } + + return get_unix_epoch(second, minute, hour, day, month, year); +} +#endif + +#if defined (UEFI) +uint64_t time(void) { + EFI_TIME time; + EFI_STATUS status = gRT->GetTime(&time, NULL); + + if (status != 0) { + return 0; + } + + return get_unix_epoch(time.Second, time.Minute, time.Hour, + time.Day, time.Month, time.Year); +} +#endif diff --git a/limine/common/lib/time.h b/limine/common/lib/time.h new file mode 100644 index 0000000..b59b1fa --- /dev/null +++ b/limine/common/lib/time.h @@ -0,0 +1,8 @@ +#ifndef LIB__TIME_H__ +#define LIB__TIME_H__ + +#include + +uint64_t time(void); + +#endif diff --git a/limine/common/lib/trace.h b/limine/common/lib/trace.h new file mode 100644 index 0000000..9c3f3bb --- /dev/null +++ b/limine/common/lib/trace.h @@ -0,0 +1,8 @@ +#ifndef LIB__TRACE_H__ +#define LIB__TRACE_H__ + +#include + +void print_stacktrace(size_t *base_ptr); + +#endif diff --git a/limine/common/lib/trace.s2.c b/limine/common/lib/trace.s2.c new file mode 100644 index 0000000..2018c81 --- /dev/null +++ b/limine/common/lib/trace.s2.c @@ -0,0 +1,87 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined (BIOS) +extern symbol stage2_map; +#elif defined (UEFI) +extern symbol __slide; +#endif + +extern symbol full_map; + +static char *trace_address(size_t *off, size_t addr) { + char *limine_map; + +#if defined (BIOS) + if (!stage3_loaded) + limine_map = stage2_map; + else + limine_map = full_map; +#elif defined (UEFI) + limine_map = full_map; + + addr -= (size_t)__slide; +#endif + + uintptr_t prev_addr = 0; + char *prev_sym = NULL; + + for (size_t i = 0;;) { + if (*((uintptr_t *)&limine_map[i]) >= addr) { + *off = addr - prev_addr; + return prev_sym; + } + prev_addr = *((uintptr_t *)&limine_map[i]); + i += sizeof(uintptr_t); + prev_sym = &limine_map[i]; + while (limine_map[i++] != 0); + } +} + +void print_stacktrace(size_t *base_ptr) { + if (base_ptr == NULL) { + asm volatile ( +#if defined (__i386__) + "movl %%ebp, %0" +#elif defined (__x86_64__) + "movq %%rbp, %0" +#elif defined (__aarch64__) + "mov %0, x29" +#elif defined (__riscv) + "mv %0, fp; addi %0, %0, -16" +#elif defined (__loongarch64) + "move %0, $fp; addi.d %0, %0, -16" +#endif + : "=r"(base_ptr) + :: "memory" + ); + } + print("Stacktrace:\n"); + for (;;) { + size_t old_bp = base_ptr[0]; + size_t ret_addr = base_ptr[1]; + if (!ret_addr) + break; + size_t off; + char *name = trace_address(&off, ret_addr); + if (name) + print(" [%p] <%s+%p>\n", ret_addr, name, off); + else + print(" [%p]\n", ret_addr); + if (!old_bp) + break; +#if defined (__riscv) || defined (__loongarch64) + base_ptr = (void *)(old_bp - 16); +#else + base_ptr = (void*)old_bp; +#endif + } + print("End of trace. "); +} diff --git a/limine/common/lib/uri.c b/limine/common/lib/uri.c new file mode 100644 index 0000000..f390d2c --- /dev/null +++ b/limine/common/lib/uri.c @@ -0,0 +1,286 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// A URI takes the form of: resource(root):/path#hash +// The following function splits up a URI into its components. +// Note: Returns pointers into a static buffer. Callers must copy values +// if they need to persist across multiple uri_resolve() calls. +bool uri_resolve(char *uri, char **resource, char **root, char **path, char **hash) { + #define URI_BUF_SIZE 4096 + static char buf[URI_BUF_SIZE]; + + size_t length = strlen(uri) + 1; + if (length > URI_BUF_SIZE) { + panic(true, "uri_resolve: URI too long (max %u)", URI_BUF_SIZE - 1); + } + memcpy(buf, uri, length); + uri = buf; + + *resource = *root = *path = *hash = NULL; + + // Get resource + for (size_t i = 0; ; i++) { + if (strlen(uri + i) < 1) + return false; + + if (!strncmp(uri + i, "(", 1)) { + *resource = uri; + uri[i] = 0; + uri += i + 1; + break; + } + } + + // Get root + for (size_t i = 0; ; i++) { + if (strlen(uri + i) < 3) + return false; + + if (!strncmp(uri + i, "):/", 3)) { + *root = uri; + uri[i] = 0; + uri += i + 3; + break; + } + } + + // Get path + if (*uri == 0) + return false; + *path = uri; + + // Get hash + for (int i = (int)strlen(uri) - 1; i >= 0; i--) { + if (uri[i] != '#') { + continue; + } + + uri[i++] = 0; + + if (hash != NULL) { + *hash = uri + i; + } + + if (strlen(uri + i) != 128) { + panic(true, "Blake2b hash must be 128 characters long"); + return false; + } + + // Validate all 128 characters are valid hexadecimal + for (size_t j = 0; j < 128; j++) { + char c = uri[i + j]; + if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))) { + panic(true, "Blake2b hash contains invalid character at position %d", (int)j); + return false; + } + } + + break; + } + + return true; +} + +static bool parse_bios_partition(char *loc, int *drive, int *partition) { + uint64_t val; + + for (size_t i = 0; ; i++) { + if (loc[i] == 0) + return false; + + if (loc[i] == ':') { + loc[i] = 0; + if (*loc == 0) { + panic(true, "Drive number cannot be omitted for hdd():/ and odd():/"); + } else { + val = strtoui(loc, NULL, 10); + if (val < 1 || val > 256) { + panic(true, "Drive number outside range 1-256"); + } + *drive = val; + } + loc += i + 1; + break; + } + } + + val = strtoui(loc, NULL, 10); + if (val > 256) { + panic(true, "Partition number outside range 0-256"); + } + *partition = val; + + return true; +} + +static struct file_handle *uri_hdd_dispatch(char *loc, char *path) { + int drive, partition; + + if (!parse_bios_partition(loc, &drive, &partition)) + return NULL; + + struct volume *volume = volume_get_by_coord(false, drive, partition); + + if (volume == NULL) + return NULL; + + return fopen(volume, path); +} + +static struct file_handle *uri_odd_dispatch(char *loc, char *path) { + int drive, partition; + + if (!parse_bios_partition(loc, &drive, &partition)) + return NULL; + + struct volume *volume = volume_get_by_coord(true, drive, partition); + + if (volume == NULL) + return NULL; + + return fopen(volume, path); +} + +static struct file_handle *uri_guid_dispatch(char *guid_str, char *path) { + struct guid guid; + if (!string_to_guid_be(&guid, guid_str)) + return NULL; + + struct volume *volume = volume_get_by_guid(&guid); + if (volume == NULL) { + if (!string_to_guid_mixed(&guid, guid_str)) + return NULL; + + volume = volume_get_by_guid(&guid); + if (volume == NULL) + return NULL; + } + + return fopen(volume, path); +} + +static struct file_handle *uri_fslabel_dispatch(char *fslabel, char *path) { + struct volume *volume = volume_get_by_fslabel(fslabel); + if (volume == NULL) { + return NULL; + } + + return fopen(volume, path); +} + +static struct file_handle *uri_tftp_dispatch(char *root, char *path) { + uint32_t ip; + if (!strcmp(root, "")) { + ip = 0; + } else { + if (inet_pton(root, &ip)) { + panic(true, "tftp: Invalid ipv4 address: %s", root); + } + } + + struct file_handle *ret; + if ((ret = tftp_open(boot_volume, root, path)) == NULL) { + return NULL; + } + + return ret; +} + +static struct file_handle *uri_boot_dispatch(char *s_part, char *path) { + if (boot_volume->pxe) + return uri_tftp_dispatch(s_part, path); + + int partition; + + if (s_part[0] != '\0') { + uint64_t val = strtoui(s_part, NULL, 10); + if (val > 256) { + panic(true, "Partition number outside range 0-256"); + } + partition = val; + } else { + partition = boot_volume->partition; + } + + struct volume *volume = volume_get_by_coord(boot_volume->is_optical, + boot_volume->index, partition); + if (volume == NULL) + return NULL; + + return fopen(volume, path); +} + +struct file_handle *uri_open(char *uri) { + struct file_handle *ret; + + char *resource = NULL, *root = NULL, *path = NULL, *hash = NULL; + if (!uri_resolve(uri, &resource, &root, &path, &hash)) { + return NULL; + } + + if (resource == NULL) { + panic(true, "No resource specified for URI `%#`.", uri); + } + + if (!strcmp(resource, "hdd")) { + ret = uri_hdd_dispatch(root, path); + } else if (!strcmp(resource, "odd")) { + ret = uri_odd_dispatch(root, path); + } else if (!strcmp(resource, "boot")) { + ret = uri_boot_dispatch(root, path); + } else if (!strcmp(resource, "guid")) { + ret = uri_guid_dispatch(root, path); + } else if (!strcmp(resource, "uuid")) { + ret = uri_guid_dispatch(root, path); + } else if (!strcmp(resource, "fslabel")) { + ret = uri_fslabel_dispatch(root, path); + } else if (!strcmp(resource, "tftp")) { + ret = uri_tftp_dispatch(root, path); + } else { + panic(true, "Resource `%s` not valid.", resource); + } + + if (hash != NULL && ret != NULL) { + uint8_t out_buf[BLAKE2B_OUT_BYTES]; +#if defined (UEFI) && defined (__x86_64__) + void *file_buf = freadall_mode(ret, MEMMAP_BOOTLOADER_RECLAIMABLE, true); +#else + void *file_buf = freadall(ret, MEMMAP_BOOTLOADER_RECLAIMABLE); +#endif + blake2b(out_buf, file_buf, ret->size); + uint8_t hash_buf[BLAKE2B_OUT_BYTES]; + + for (size_t i = 0; i < sizeof(hash_buf); i++) { + hash_buf[i] = digit_to_int(hash[i * 2]) << 4 | digit_to_int(hash[i * 2 + 1]); + } + + if (memcmp(hash_buf, out_buf, sizeof(out_buf)) != 0) { + if (hash_mismatch_panic) { + panic(true, "Blake2b hash for URI `%#` does not match!", uri); + } else { + print("WARNING: Blake2b hash for URI `%#` does not match!\n" + " Press Y to continue, press any other key to return to menu...", uri); + + char ch = getchar(); + if (ch != 'Y' && ch != 'y') { + menu(false); + } + print("\n"); + } + } + } + + return ret; +} diff --git a/limine/common/lib/uri.h b/limine/common/lib/uri.h new file mode 100644 index 0000000..d15a549 --- /dev/null +++ b/limine/common/lib/uri.h @@ -0,0 +1,10 @@ +#ifndef LIB__URI_H__ +#define LIB__URI_H__ + +#include +#include + +bool uri_resolve(char *uri, char **resource, char **root, char **path, char **hash); +struct file_handle *uri_open(char *uri); + +#endif diff --git a/limine/common/libc-compat/stdlib.h b/limine/common/libc-compat/stdlib.h new file mode 100644 index 0000000..8e01eb2 --- /dev/null +++ b/limine/common/libc-compat/stdlib.h @@ -0,0 +1,6 @@ +#ifndef LIBC_COMPAT__STDLIB_H__ +#define LIBC_COMPAT__STDLIB_H__ + +unsigned long strtoul(const char *str, char **end, int base); + +#endif diff --git a/limine/common/libc-compat/string.h b/limine/common/libc-compat/string.h new file mode 100644 index 0000000..df3fa05 --- /dev/null +++ b/limine/common/libc-compat/string.h @@ -0,0 +1,23 @@ +#ifndef LIBC_COMPAT__STRING_H__ +#define LIBC_COMPAT__STRING_H__ + +#include + +void *memset(void *, int, size_t); +void *memcpy(void *restrict, const void *restrict, size_t); +int memcmp(const void *, const void *, size_t); +void *memmove(void *, const void *, size_t); +void *memchr(const void *, int, size_t); + +char *strcpy(char *, const char *); +char *strncpy(char *, const char *, size_t); +char *strchr(const char *, int); +char *strrchr(const char *, int); +size_t strlen(const char *); +size_t strnlen(const char *, size_t); +int strcmp(const char *, const char *); +int strcasecmp(const char *, const char *); +int strncmp(const char *, const char *, size_t); +int strncasecmp(const char *, const char *, size_t); + +#endif diff --git a/limine/common/linker_bios.ld.in b/limine/common/linker_bios.ld.in new file mode 100644 index 0000000..7154ec9 --- /dev/null +++ b/limine/common/linker_bios.ld.in @@ -0,0 +1,113 @@ +OUTPUT_FORMAT(elf32-i386) +ENTRY(_start) + +PHDRS +{ + text_s2 PT_LOAD FLAGS(0x05); + rodata_s2 PT_LOAD FLAGS(0x04); + data_s2 PT_LOAD FLAGS(0x06); + text_s3 PT_LOAD FLAGS(0x05); + rodata_s3 PT_LOAD FLAGS(0x04); + data_s3 PT_LOAD FLAGS(0x06); +} + +SECTIONS +{ + . = 0xf000; + + .text.stage2 : { + *(.entry) + *(.realmode) + *.s2.o(.text .text.*) + } :text_s2 + + .rodata.stage2 : { + *.s2.o(.rodata .rodata.*) + + build_id_s2 = .; + KEEP(*build-id.s2.o(*)) + +#ifdef LINKER_STAGE2ONLY + /* stage2 missing symbols overrides */ + stage2_map = .; + stage3_common = .; + build_id_s3 = .; + full_map = .; + getchar = .; + menu = .; + booting_from_editor = .; + flanterm_write = .; + term_backend = .; + term_fallback = .; + term_notready = .; + terms = .; + terms_i = .; + usec_at_bootloader_entry = .; + serial_out = .; + stage3_addr = .; +#else +#ifdef LINKER_NOS2MAP + stage2_map = .; +#else + *(.stage2_map) +#endif +#endif + } :rodata_s2 + + .data.stage2 : { + s2_data_begin = .; + *.s2.o(.data .data.*) + s2_data_end = .; + } :data_s2 + + .no_unwind.stage2 : { + *.s2.o(.no_unwind) + } :data_s2 + +#ifndef LINKER_STAGE2ONLY + .text.stage3 : { + stage3_addr = .; + *(.text .text.*) + } :text_s3 + + .rodata.stage3 : { + *(.rodata .rodata.*) + + build_id_s3 = .; + KEEP(*build-id.s3.o(*)) + +#ifdef LINKER_NOMAP + full_map = .; +#else + *(.full_map) +#endif + } :rodata_s3 + + .data.stage3 : { + data_begin = .; + *(.data .data.*) + data_end = .; + } :data_s3 + + .no_unwind.stage3 : { + *(.no_unwind) + } :data_s3 +#endif + + .note.gnu.build-id : { + *(.note.gnu.build-id) + limine_bios_sys_size = . - 0xf000; + } :data_s3 + + .bss : { + bss_begin = .; + *(.bss .bss.*) + *(COMMON) + bss_end = .; + } :data_s3 + + /DISCARD/ : { + *(.eh_frame*) + *(.note .note.*) + } +} diff --git a/limine/common/linker_uefi_aarch64.ld.in b/limine/common/linker_uefi_aarch64.ld.in new file mode 100644 index 0000000..760ddac --- /dev/null +++ b/limine/common/linker_uefi_aarch64.ld.in @@ -0,0 +1,79 @@ +OUTPUT_FORMAT(elf64-littleaarch64) +ENTRY(_start) + +PHDRS +{ + text PT_LOAD FLAGS(0x05); + rodata PT_LOAD FLAGS(0x04); + data PT_LOAD FLAGS(0x06); + dynamic PT_DYNAMIC FLAGS(0x06); +} + +SECTIONS +{ + . = 0; + __slide = .; + __image_base = ABSOLUTE(.); + __image_size = ABSOLUTE(__image_end - __image_base); + + .text : { + KEEP(*(.pe_header)) + + . = ALIGN(0x1000); + + __text_start = ABSOLUTE(.); + *(.text .text.*) + } :text + + . = ALIGN(0x1000); + __text_end = ABSOLUTE(.); + __text_size = ABSOLUTE(__text_end - __text_start); + + .rodata : { + __reloc_start = ABSOLUTE(.); + *(.dummy_reloc) + + . = ALIGN(0x1000); + __reloc_end = ABSOLUTE(.); + __reloc_size = ABSOLUTE(__reloc_end - __reloc_start); + + __data_start = ABSOLUTE(.); + *(.rodata .rodata.*) + +#ifdef LINKER_NOMAP + full_map = .; +#else + *(.full_map) +#endif + } :rodata + + .data : { + data_begin = .; + *(.data .data.*) + } :data + + .bss : { + *(.bss .bss.*) + *(COMMON) + data_end = .; + } :data + + .no_unwind : { + *(.no_unwind) + } :data + + .dynamic : { + *(.dynamic) + } :data :dynamic + + __data_end = ABSOLUTE(ALIGN(0x1000)); + __data_size = ABSOLUTE(__data_end - __data_start); + + __image_end = ABSOLUTE(ALIGN(0x1000)); + + /DISCARD/ : { + *(.eh_frame*) + *(.note .note.*) + *(.interp) + } +} diff --git a/limine/common/linker_uefi_ia32.ld.in b/limine/common/linker_uefi_ia32.ld.in new file mode 100644 index 0000000..c0cc0f4 --- /dev/null +++ b/limine/common/linker_uefi_ia32.ld.in @@ -0,0 +1,79 @@ +OUTPUT_FORMAT(elf32-i386) +ENTRY(_start) + +PHDRS +{ + text PT_LOAD FLAGS(0x05); + rodata PT_LOAD FLAGS(0x04); + data PT_LOAD FLAGS(0x06); + dynamic PT_DYNAMIC FLAGS(0x06); +} + +SECTIONS +{ + . = 0; + __slide = .; + __image_base = ABSOLUTE(.); + __image_size = ABSOLUTE(__image_end - __image_base); + + .text : { + KEEP(*(.pe_header)) + + . = ALIGN(0x1000); + + __text_start = ABSOLUTE(.); + *(.text .text.*) + } :text + + . = ALIGN(0x1000); + __text_end = ABSOLUTE(.); + __text_size = ABSOLUTE(__text_end - __text_start); + + .rodata : { + __reloc_start = ABSOLUTE(.); + *(.dummy_reloc) + + . = ALIGN(0x1000); + __reloc_end = ABSOLUTE(.); + __reloc_size = ABSOLUTE(__reloc_end - __reloc_start); + + __data_start = ABSOLUTE(.); + *(.rodata .rodata.*) + +#ifdef LINKER_NOMAP + full_map = .; +#else + *(.full_map) +#endif + } :rodata + + .data : { + data_begin = .; + *(.data .data.*) + } :data + + .bss : { + *(.bss .bss.*) + *(COMMON) + data_end = .; + } :data + + .no_unwind : { + *(.no_unwind) + } :data + + .dynamic : { + *(.dynamic) + } :data :dynamic + + __data_end = ABSOLUTE(ALIGN(0x1000)); + __data_size = ABSOLUTE(__data_end - __data_start); + + __image_end = ABSOLUTE(ALIGN(0x1000)); + + /DISCARD/ : { + *(.eh_frame*) + *(.note .note.*) + *(.interp) + } +} diff --git a/limine/common/linker_uefi_loongarch64.ld.in b/limine/common/linker_uefi_loongarch64.ld.in new file mode 100644 index 0000000..3946af5 --- /dev/null +++ b/limine/common/linker_uefi_loongarch64.ld.in @@ -0,0 +1,79 @@ +OUTPUT_FORMAT(elf64-loongarch) +ENTRY(_start) + +PHDRS +{ + text PT_LOAD FLAGS(0x05); + rodata PT_LOAD FLAGS(0x04); + data PT_LOAD FLAGS(0x06); + dynamic PT_DYNAMIC FLAGS(0x06); +} + +SECTIONS +{ + . = 0; + __slide = .; + __image_base = ABSOLUTE(.); + __image_size = ABSOLUTE(__image_end - __image_base); + + .text : { + KEEP(*(.pe_header)) + + . = ALIGN(0x1000); + + __text_start = ABSOLUTE(.); + *(.text .text.*) + } :text + + . = ALIGN(0x1000); + __text_end = ABSOLUTE(.); + __text_size = ABSOLUTE(__text_end - __text_start); + + .rodata : { + __reloc_start = ABSOLUTE(.); + *(.dummy_reloc) + + . = ALIGN(0x1000); + __reloc_end = ABSOLUTE(.); + __reloc_size = ABSOLUTE(__reloc_end - __reloc_start); + + __data_start = ABSOLUTE(.); + *(.rodata .rodata.*) + +#ifdef LINKER_NOMAP + full_map = .; +#else + *(.full_map) +#endif + } :rodata + + .data : { + data_begin = .; + *(.data .data.*) + } :data + + .bss : { + *(.bss .bss.*) + *(COMMON) + data_end = .; + } :data + + .no_unwind : { + *(.no_unwind) + } :data + + .dynamic : { + *(.dynamic) + } :data :dynamic + + __data_end = ABSOLUTE(ALIGN(0x1000)); + __data_size = ABSOLUTE(__data_end - __data_start); + + __image_end = ABSOLUTE(ALIGN(0x1000)); + + /DISCARD/ : { + *(.eh_frame*) + *(.note .note.*) + *(.interp) + } +} diff --git a/limine/common/linker_uefi_riscv64.ld.in b/limine/common/linker_uefi_riscv64.ld.in new file mode 100644 index 0000000..1432a5f --- /dev/null +++ b/limine/common/linker_uefi_riscv64.ld.in @@ -0,0 +1,83 @@ +OUTPUT_FORMAT(elf64-littleriscv) +ENTRY(_start) + +PHDRS +{ + text PT_LOAD FLAGS(0x05); + rodata PT_LOAD FLAGS(0x04); + data PT_LOAD FLAGS(0x06); + dynamic PT_DYNAMIC FLAGS(0x06); +} + +SECTIONS +{ + . = 0; + __slide = .; + __image_base = ABSOLUTE(.); + __image_size = ABSOLUTE(__image_end - __image_base); + + .text : { + KEEP(*(.pe_header)) + + . = ALIGN(0x1000); + + __text_start = ABSOLUTE(.); + *(.text .text.*) + } :text + + . = ALIGN(0x1000); + __text_end = ABSOLUTE(.); + __text_size = ABSOLUTE(__text_end - __text_start); + + .rodata : { + __reloc_start = ABSOLUTE(.); + *(.dummy_reloc) + + . = ALIGN(0x1000); + __reloc_end = ABSOLUTE(.); + __reloc_size = ABSOLUTE(__reloc_end - __reloc_start); + + __data_start = ABSOLUTE(.); + *(.rodata .rodata.*) + *(.srodata .srodata.*) + *(.sdata2 .sdata2.*) + +#ifdef LINKER_NOMAP + full_map = .; +#else + *(.full_map) +#endif + } :rodata + + .data : { + data_begin = .; + *(.data .data.*) + *(.sdata .sdata.*) + } :data + + .bss : { + *(.bss .bss.*) + *(.sbss .sbss.*) + *(COMMON) + data_end = .; + } :data + + .no_unwind : { + *(.no_unwind) + } :data + + .dynamic : { + *(.dynamic) + } :data :dynamic + + __data_end = ABSOLUTE(ALIGN(0x1000)); + __data_size = ABSOLUTE(__data_end - __data_start); + + __image_end = ABSOLUTE(ALIGN(0x1000)); + + /DISCARD/ : { + *(.eh_frame*) + *(.note .note.*) + *(.interp) + } +} diff --git a/limine/common/linker_uefi_x86_64.ld.in b/limine/common/linker_uefi_x86_64.ld.in new file mode 100644 index 0000000..db5f176 --- /dev/null +++ b/limine/common/linker_uefi_x86_64.ld.in @@ -0,0 +1,79 @@ +OUTPUT_FORMAT(elf64-x86-64) +ENTRY(_start) + +PHDRS +{ + text PT_LOAD FLAGS(0x05); + rodata PT_LOAD FLAGS(0x04); + data PT_LOAD FLAGS(0x06); + dynamic PT_DYNAMIC FLAGS(0x06); +} + +SECTIONS +{ + . = 0; + __slide = .; + __image_base = ABSOLUTE(.); + __image_size = ABSOLUTE(__image_end - __image_base); + + .text : { + KEEP(*(.pe_header)) + + . = ALIGN(0x1000); + + __text_start = ABSOLUTE(.); + *(.text .text.*) + } :text + + . = ALIGN(0x1000); + __text_end = ABSOLUTE(.); + __text_size = ABSOLUTE(__text_end - __text_start); + + .rodata : { + __reloc_start = ABSOLUTE(.); + *(.dummy_reloc) + + . = ALIGN(0x1000); + __reloc_end = ABSOLUTE(.); + __reloc_size = ABSOLUTE(__reloc_end - __reloc_start); + + __data_start = ABSOLUTE(.); + *(.rodata .rodata.*) + +#ifdef LINKER_NOMAP + full_map = .; +#else + *(.full_map) +#endif + } :rodata + + .data : { + data_begin = .; + *(.data .data.*) + } :data + + .bss : { + *(.bss .bss.*) + *(COMMON) + data_end = .; + } :data + + .no_unwind : { + *(.no_unwind) + } :data + + .dynamic : { + *(.dynamic) + } :data :dynamic + + __data_end = ABSOLUTE(ALIGN(0x1000)); + __data_size = ABSOLUTE(__data_end - __data_start); + + __image_end = ABSOLUTE(ALIGN(0x1000)); + + /DISCARD/ : { + *(.eh_frame*) + *(.note .note.*) + *(.interp) + } +} diff --git a/limine/common/menu.c b/limine/common/menu.c new file mode 100644 index 0000000..22a89eb --- /dev/null +++ b/limine/common/menu.c @@ -0,0 +1,1312 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined (UEFI) +EFI_GUID limine_efi_vendor_guid = + { 0x513ee0d0, 0x6e43, 0xcb05, { 0xb2, 0x72, 0xf1, 0x46, 0xa2, 0xfc, 0xb8, 0x8a } }; +#endif + +#define EDITOR_MAX_BUFFER_SIZE 4096 +#define TOK_KEY 0 +#define TOK_EQUALS 1 +#define TOK_VALUE 2 +#define TOK_BADKEY 3 +#define TOK_COMMENT 4 + +static char interface_help_colour[] = "\e[32m"; +static char interface_help_colour_bright[] = "\e[92m"; + +static char *menu_branding = NULL; +static char *menu_branding_colour = NULL; +no_unwind bool booting_from_editor = false; +static no_unwind bool booting_from_blank = false; +static no_unwind char saved_orig_entry[EDITOR_MAX_BUFFER_SIZE]; +static no_unwind char saved_title[64]; + +static size_t get_line_offset(size_t *displacement, size_t index, const char *buffer) { + size_t offset = 0; + size_t _index = index; + for (size_t i = 0; buffer[i]; i++) { + if (!_index--) + break; + if (buffer[i] == '\n') + offset = i + 1; + } + if (displacement) + *displacement = index - offset; + return offset; +} + +static size_t get_line_length(size_t index, const char *buffer) { + size_t i; + for (i = index; buffer[i] != '\n' && buffer[i] != 0; i++); + return i - index; +} + +static size_t get_next_line(size_t index, const char *buffer) { + if (buffer[index] == 0) + return index; + size_t displacement; + get_line_offset(&displacement, index, buffer); + while (buffer[index] != '\n') { + if (buffer[index] == 0) + return index; + index++; + } + index++; + size_t next_line_length = get_line_length(index, buffer); + if (displacement > next_line_length) + displacement = next_line_length; + return index + displacement; +} + +static size_t get_prev_line(size_t index, const char *buffer) { + size_t offset, displacement, prev_line_offset, prev_line_length; + offset = get_line_offset(&displacement, index, buffer); + if (offset) { + prev_line_offset = get_line_offset(NULL, offset - 1, buffer); + prev_line_length = get_line_length(prev_line_offset, buffer); + if (displacement > prev_line_length) + displacement = prev_line_length; + return prev_line_offset + displacement; + } + return offset; +} + +static const char *VALID_KEYS[] = { + "COMMENT", + "PROTOCOL", + "CMDLINE", + "PATH", + "KERNEL_CMDLINE", + "KERNEL_PATH", + "INITRD_PATH", + "MODULE_PATH", + "MODULE_STRING", + "MODULE_CMDLINE", + "RESOLUTION", + "TEXTMODE", + "KASLR", + "RANDOMISE_HHDM_BASE", + "RANDOMIZE_HHDM_BASE", + "PAGING_MODE", + "MAX_PAGING_MODE", + "MIN_PAGING_MODE", + "DRIVE", + "PARTITION", + "MBR_ID", + "GPT_GUID", + "GPT_UUID", + "IMAGE_PATH", + "DTB_PATH", + NULL +}; + +static bool validation_enabled = true; +static bool invalid_syntax = false; + +static int validate_line(const char *buffer) { + if (!validation_enabled) return TOK_KEY; + if (buffer[0] == '#') + return TOK_COMMENT; + char keybuf[64]; + size_t i; + for (i = 0; buffer[i] && i < 64; i++) { + if (buffer[i] == ':') goto found_equals; + keybuf[i] = buffer[i]; + } +fail: + if (i < 64) keybuf[i] = 0; + if (keybuf[0] == '\n' || (!keybuf[0] && buffer[0] != ':')) return TOK_KEY; // blank line is valid + invalid_syntax = true; + return TOK_BADKEY; +found_equals: + if (i < 64) keybuf[i] = 0; + for (i = 0; VALID_KEYS[i]; i++) { + if (!strcasecmp(keybuf, VALID_KEYS[i])) { + return TOK_KEY; + } + } + goto fail; +} + +static void putchar_tokencol(int type, char c) { + switch (type) { + case TOK_KEY: + print("\e[36m%c\e[0m", c); + break; + case TOK_EQUALS: + print("\e[32m%c\e[0m", c); + break; + default: + case TOK_VALUE: + print("\e[39m%c\e[0m", c); + break; + case TOK_BADKEY: + print("\e[31m%c\e[0m", c); + break; + case TOK_COMMENT: + print("\e[33m%c\e[0m", c); + break; + } +} + +static bool editor_no_term_reset = false; + +char *config_entry_editor(const char *title, const char *orig_entry) { + if (terms[0]->cols < 40 || terms[0]->rows < 16) { + return NULL; + } + + FOR_TERM(TERM->autoflush = false); + + FOR_TERM(TERM->cursor_enabled = true); + + print("\e[2J\e[H"); + + if (booting_from_editor) { + orig_entry = saved_orig_entry; + title = saved_title; + } + + size_t cursor_offset = 0; + size_t entry_size = strlen(orig_entry); + size_t _window_size = terms[0]->rows - 8; + size_t window_offset = 0; + size_t line_size = terms[0]->cols - 2; + + bool display_overflow_error = false; + + // Skip leading newlines + while (*orig_entry == '\n') { + orig_entry++; + entry_size--; + } + + if (entry_size >= EDITOR_MAX_BUFFER_SIZE) { + panic(true, "Entry is too big to be edited."); + } + + bool syntax_highlighting_enabled = true; + char *syntax_highlighting_enabled_config = config_get_value(NULL, 0, "EDITOR_HIGHLIGHTING"); + if (syntax_highlighting_enabled_config != NULL + && strcmp(syntax_highlighting_enabled_config, "no") == 0) { + syntax_highlighting_enabled = false; + } + + validation_enabled = true; + char *validation_enabled_config = config_get_value(NULL, 0, "EDITOR_VALIDATION"); + if (validation_enabled_config != NULL + && strcmp(validation_enabled_config, "no") == 0) { + validation_enabled = false; + } + + char *buffer = ext_mem_alloc(EDITOR_MAX_BUFFER_SIZE); + memcpy(buffer, orig_entry, entry_size); + buffer[entry_size] = 0; + +refresh: + invalid_syntax = false; + + print("\e[2J\e[H"); + FOR_TERM(TERM->cursor_enabled = false); + { + size_t x, y; + print("\n"); + terms[0]->get_cursor_pos(terms[0], &x, &y); + set_cursor_pos_helper(terms[0]->cols / 2 - DIV_ROUNDUP(strlen(menu_branding), 2), y); + print("\e[3%sm%s\e[0m", menu_branding_colour, menu_branding); + print("\n\n"); + } + + print(" %sESC\e[0m Discard and Exit %sF10\e[0m Boot\n\n", interface_help_colour, interface_help_colour); + + print(serial ? "/" : "┌"); + for (size_t i = 0; i < terms[0]->cols - 2; i++) { + switch (i) { + case 1: case 2: case 3: + if (window_offset > 0) { + print(serial ? "^" : "↑"); + break; + } + // FALLTHRU + default: { + size_t title_length = strlen(title); + if (i == (terms[0]->cols / 2) - DIV_ROUNDUP(title_length, 2) - 1 - 1) { + print(serial ? "|%s|" : "┤%s├", title); + i += (title_length + 2) - 1; + } else { + print(serial ? "-" : "─"); + } + } + } + } + size_t tmpx, tmpy; + + terms[0]->get_cursor_pos(terms[0], &tmpx, &tmpy); + print(serial ? "\\" : "┐"); + set_cursor_pos_helper(0, tmpy + 1); + print(serial ? "|" : "│"); + + size_t cursor_x, cursor_y; + size_t current_line = 0, line_offset = 0, window_size = _window_size; + bool printed_cursor = false; + bool printed_early = false; + int token_type = validate_line(buffer); + size_t tab_space_count = 0; + for (size_t i = 0; ; i++) { + // tab + if (buffer[i] == '\t') { + tab_space_count = 8 - (line_offset % 8); + goto tab_part; + } + + // newline + if (buffer[i] == '\n' + && current_line < window_offset + window_size + && current_line >= window_offset) { + size_t x, y; + terms[0]->get_cursor_pos(terms[0], &x, &y); + if (i == cursor_offset) { + cursor_x = x; + cursor_y = y; + printed_cursor = true; + } + set_cursor_pos_helper(terms[0]->cols - 1, y); + if (current_line == window_offset + window_size - 1) { + terms[0]->get_cursor_pos(terms[0], &tmpx, &tmpy); + print(serial ? "|" : "│"); + set_cursor_pos_helper(0, tmpy + 1); + print(serial ? "\\" : "└"); + } else { + terms[0]->get_cursor_pos(terms[0], &tmpx, &tmpy); + print(serial ? "|" : "│"); + set_cursor_pos_helper(0, tmpy + 1); + print(serial ? "|" : "│"); + } + line_offset = 0; + token_type = validate_line(buffer + i + 1); + current_line++; + continue; + } + + // switch to token type 1 if equals sign + if (token_type == TOK_KEY && buffer[i] == ':') token_type = TOK_EQUALS; + +tab_part: + if (buffer[i] != 0 && line_offset % line_size == line_size - 1) { + if (current_line < window_offset + window_size + && current_line >= window_offset) { + if (i == cursor_offset && !printed_cursor) { + terms[0]->get_cursor_pos(terms[0], &cursor_x, &cursor_y); + printed_cursor = true; + } + if (syntax_highlighting_enabled) { + putchar_tokencol(token_type, tab_space_count ? ' ' : buffer[i]); + } else { + print("%c", tab_space_count ? ' ' : buffer[i]); + } + if (tab_space_count != 0) { + tab_space_count--; + } + printed_early = true; + size_t x, y; + terms[0]->get_cursor_pos(terms[0], &x, &y); + if (y == terms[0]->rows - 3) { + print(serial ? ">" : "→"); + set_cursor_pos_helper(0, y + 1); + print(serial ? "\\" : "└"); + } else { + print(serial ? ">" : "→"); + set_cursor_pos_helper(0, y + 1); + print(serial ? "<" : "←"); + } + } + if (window_size > 0) { + window_size--; + } + } + + if (i == cursor_offset + && current_line < window_offset + window_size + && current_line >= window_offset + && !printed_cursor) { + terms[0]->get_cursor_pos(terms[0], &cursor_x, &cursor_y); + printed_cursor = true; + } + + if (buffer[i] == 0 || current_line >= window_offset + window_size) { + if (!printed_cursor) { + if (i <= cursor_offset) { + window_offset++; + goto refresh; + } + if (i > cursor_offset) { + window_offset--; + goto refresh; + } + } + break; + } + + if (buffer[i] == '\n') { + line_offset = 0; + token_type = validate_line(buffer + i + 1); + current_line++; + continue; + } + + if (current_line >= window_offset) { + line_offset++; + + // syntax highlighting + if (!printed_early) { + if (syntax_highlighting_enabled) { + putchar_tokencol(token_type, tab_space_count ? ' ' : buffer[i]); + } else { + print("%c", tab_space_count ? ' ' : buffer[i]); + } + + if (tab_space_count != 0) { + tab_space_count--; + } + } + + printed_early = false; + + // switch to token type 2 after equals sign + if (token_type == TOK_EQUALS) token_type = TOK_VALUE; + + } + + if (tab_space_count != 0) { + goto tab_part; + } + } + + // syntax error alert + if (validation_enabled) { + size_t x, y; + terms[0]->get_cursor_pos(terms[0], &x, &y); + set_cursor_pos_helper(0, terms[0]->rows - 1); + FOR_TERM(TERM->scroll_enabled = false); + if (invalid_syntax) { + print("\e[31mConfiguration is INVALID.\e[0m"); + } else { + print("\e[32mConfiguration is valid.\e[0m"); + } + FOR_TERM(TERM->scroll_enabled = true); + set_cursor_pos_helper(x, y); + } + + if (current_line - window_offset < window_size) { + size_t x, y; + for (size_t i = 0; i < (window_size - (current_line - window_offset)) - 1; i++) { + terms[0]->get_cursor_pos(terms[0], &x, &y); + set_cursor_pos_helper(terms[0]->cols - 1, y); + print(serial ? "|" : "│"); + set_cursor_pos_helper(0, y + 1); + print(serial ? "|" : "│"); + } + terms[0]->get_cursor_pos(terms[0], &x, &y); + set_cursor_pos_helper(terms[0]->cols - 1, y); + print(serial ? "|" : "│"); + set_cursor_pos_helper(0, y + 1); + print(serial ? "\\" : "└"); + } + + for (size_t i = 0; i < terms[0]->cols - 2; i++) { + switch (i) { + case 1: case 2: case 3: + if (current_line - window_offset >= window_size) { + print(serial ? "v" : "↓"); + break; + } + // FALLTHRU + default: + print(serial ? "-" : "─"); + } + } + terms[0]->get_cursor_pos(terms[0], &tmpx, &tmpy); + print(serial ? "/" : "┘"); + set_cursor_pos_helper(0, tmpy + 1); + + if (display_overflow_error) { + FOR_TERM(TERM->scroll_enabled = false); + print("\e[31mText buffer not big enough, delete something instead."); + FOR_TERM(TERM->scroll_enabled = true); + display_overflow_error = false; + } + + // Hack to redraw the cursor + set_cursor_pos_helper(cursor_x, cursor_y); + FOR_TERM(TERM->cursor_enabled = true); + + FOR_TERM(TERM->double_buffer_flush(TERM)); + + int c = getchar(); + size_t buffer_len = strlen(buffer); + switch (c) { + case GETCHAR_CURSOR_DOWN: + cursor_offset = get_next_line(cursor_offset, buffer); + break; + case GETCHAR_CURSOR_UP: + cursor_offset = get_prev_line(cursor_offset, buffer); + break; + case GETCHAR_CURSOR_LEFT: + if (cursor_offset) { + cursor_offset--; + } + break; + case GETCHAR_CURSOR_RIGHT: + if (cursor_offset < buffer_len) { + cursor_offset++; + } + break; + case GETCHAR_HOME: { + size_t displacement; + get_line_offset(&displacement, cursor_offset, buffer); + cursor_offset -= displacement; + break; + } + case GETCHAR_END: { + cursor_offset += get_line_length(cursor_offset, buffer); + break; + } + case '\b': + if (cursor_offset) { + cursor_offset--; + case GETCHAR_DELETE: + for (size_t i = cursor_offset; i < buffer_len; i++) { + buffer[i] = buffer[i+1]; + if (!buffer[i]) + break; + } + } + break; + case GETCHAR_F10: + memcpy(saved_orig_entry, buffer, buffer_len); + saved_orig_entry[buffer_len] = 0; + size_t title_len = strlen(title); + if (title_len >= sizeof(saved_title)) { + title_len = sizeof(saved_title) - 1; + } + memcpy(saved_title, title, title_len); + saved_title[title_len] = 0; + editor_no_term_reset ? editor_no_term_reset = false : reset_term(); + booting_from_editor = true; + return buffer; + case GETCHAR_ESCAPE: + pmm_free(buffer, EDITOR_MAX_BUFFER_SIZE); + editor_no_term_reset ? editor_no_term_reset = false : reset_term(); + booting_from_editor = false; + return NULL; + default: + if (buffer_len < EDITOR_MAX_BUFFER_SIZE - 1) { + if (isprint(c) || c == '\n' || c == '\t') { + for (size_t i = buffer_len; ; i--) { + buffer[i+1] = buffer[i]; + if (i == cursor_offset) + break; + } + buffer[cursor_offset++] = c; + } + } else { + display_overflow_error = true; + } + break; + } + + goto refresh; +} + +static size_t print_tree(size_t offset, size_t window, const char *shift, size_t level, size_t base_index, size_t selected_entry, + struct menu_entry *current_entry, + struct menu_entry **selected_menu_entry, + size_t *max_len, size_t *max_height) { + size_t max_entries = 0; + + bool no_print = false; + size_t dummy_max_len = 0; + if (max_len == NULL) { + max_len = &dummy_max_len; + } + size_t dummy_max_height = 0; + if (max_height == NULL) { + max_height = &dummy_max_height; + } + if (!level) { + *max_len = 0; + *max_height = 0; + } + if (shift == NULL) { + no_print = true; + } + + for (;;) { + size_t cur_len = 0; + if (current_entry == NULL) + break; + if (current_entry->sub == NULL) { + bool skip_entry = false; + char *cur_entry_protocol = config_get_value(current_entry->body, 0, "PROTOCOL"); + if (cur_entry_protocol) { +#if defined (UEFI) + if (strcmp(cur_entry_protocol, "bios") == 0 + || strcmp(cur_entry_protocol, "bios_chainload") == 0) { +#elif defined (BIOS) + if (strcmp(cur_entry_protocol, "efi") == 0 + || strcmp(cur_entry_protocol, "uefi") == 0 + || strcmp(cur_entry_protocol, "efi_chainload") == 0) { +#endif + skip_entry = true; + } + if (skip_entry) { + current_entry = current_entry->next; + continue; + } + } + } + if (!no_print && base_index + max_entries < offset) { + goto skip_line; + } + if (!no_print && base_index + max_entries >= offset + window) { + goto skip_line; + } + if (!no_print) print("%s", shift); + if (level) { + for (size_t i = level - 1; i > 0; i--) { + struct menu_entry *actual_parent = current_entry; + for (size_t j = 0; j < i; j++) + actual_parent = actual_parent->parent; + if (actual_parent->next != NULL) { + if (!no_print) print(serial ? " |" : " │"); + } else { + if (!no_print) print(" "); + } + cur_len += 2; + } + if (current_entry->next == NULL) { + if (!no_print) print(serial ? " `" : " └"); + } else { + if (!no_print) print(serial ? " |" : " ├"); + } + cur_len += 2; + } + if (current_entry->sub) { + if (!no_print) print(current_entry->expanded ? "[-]" : "[+]"); + } else if (level) { + if (!no_print) print(serial ? "-->" : "──►"); + } else { + if (!no_print) print(" "); + } + cur_len += 3; + if (base_index + max_entries == selected_entry) { + *selected_menu_entry = current_entry; + if (!no_print) print("\e[7m"); + } + if (!no_print) print(" %s \e[27m\n", current_entry->name); + (*max_height)++; + cur_len += 1 + strlen(current_entry->name) + 1; +skip_line: + if (current_entry->sub && current_entry->expanded) { + max_entries += print_tree(offset, window, shift, level + 1, base_index + max_entries + 1, + selected_entry, + current_entry->sub, + selected_menu_entry, + max_len, max_height); + } + max_entries++; + current_entry = current_entry->next; + if (cur_len > *max_len) { + *max_len = cur_len; + } + } + return max_entries; +} + +static struct memmap_entry *rewound_memmap = NULL; +static size_t rewound_memmap_entries = 0; +static no_unwind uint8_t *rewound_data; +#if defined (BIOS) +static no_unwind uint8_t *rewound_s2_data; +static no_unwind uint8_t *rewound_bss; +#endif + +extern symbol data_begin; +extern symbol data_end; +#if defined (BIOS) +extern symbol s2_data_begin; +extern symbol s2_data_end; +extern symbol bss_begin; +extern symbol bss_end; +#endif + +static void menu_init_term(void) { + // If there is GRAPHICS config key and the value is "yes", enable graphics +#if defined (BIOS) + char *graphics = config_get_value(NULL, 0, "GRAPHICS"); +#elif defined (UEFI) + char *graphics = "yes"; +#endif + + if (graphics == NULL || strcmp(graphics, "no") != 0) { + size_t req_width = 0, req_height = 0, req_bpp = 0; + + char *menu_resolution = config_get_value(NULL, 0, "INTERFACE_RESOLUTION"); + if (menu_resolution != NULL) + parse_resolution(&req_width, &req_height, &req_bpp, menu_resolution); + + if (!quiet && !gterm_init(NULL, NULL, NULL, req_width, req_height)) { +#if defined (BIOS) + vga_textmode_init(true); +#elif defined (UEFI) + serial = true; + term_fallback(); +#endif + } + } else { +#if defined (BIOS) + if (!quiet) { + vga_textmode_init(true); + } +#endif + } +} + +#if defined(UEFI) +bool reboot_to_fw_ui_supported(void) { + uint64_t os_indications_supported; + UINTN size = sizeof(os_indications_supported); + EFI_GUID global_variable = EFI_GLOBAL_VARIABLE; + EFI_STATUS status = gRT->GetVariable(L"OsIndicationsSupported", &global_variable, NULL, &size, &os_indications_supported); + if (status == EFI_SUCCESS && size == sizeof(os_indications_supported)) { + return (os_indications_supported & EFI_OS_INDICATIONS_BOOT_TO_FW_UI) != 0; + } + return false; +} + +noreturn void reboot_to_fw_ui(void) { + reset_term(); + print("Rebooting to the firmware setup...\n"); + + uint64_t os_indications; + UINTN size = sizeof(os_indications); + EFI_GUID global_variable = EFI_GLOBAL_VARIABLE; + EFI_STATUS status = gRT->GetVariable(L"OsIndications", &global_variable, NULL, &size, &os_indications); + if (status != EFI_SUCCESS || size != sizeof(os_indications)) { + if (status == EFI_NOT_FOUND) { + os_indications = 0; + goto not_found; + } + + panic(true, "Failed to get OsIndications variable, status=%X", status); + } + +not_found:; + uint64_t new_os_indications = os_indications | EFI_OS_INDICATIONS_BOOT_TO_FW_UI; + status = gRT->SetVariable(L"OsIndications", &global_variable, + EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS, + sizeof(new_os_indications), &new_os_indications); + + if (status != EFI_SUCCESS) { + panic(true, "Failed to set OsIndications variable, status=%X", status); + } + + gRT->ResetSystem(EfiResetWarm, EFI_SUCCESS, 0, NULL); + panic(true, "Failed to reboot to firmware UI"); +} +#endif + +noreturn void _menu(bool first_run) { + size_t data_size = (uintptr_t)data_end - (uintptr_t)data_begin; +#if defined (BIOS) + size_t s2_data_size = (uintptr_t)s2_data_end - (uintptr_t)s2_data_begin; + size_t bss_size = (uintptr_t)bss_end - (uintptr_t)bss_begin; +#endif + + if (rewound_memmap != NULL) { + memcpy(data_begin, rewound_data, data_size); +#if defined (BIOS) + memcpy(s2_data_begin, rewound_s2_data, s2_data_size); + memcpy(bss_begin, rewound_bss, bss_size); +#endif + memcpy(memmap, rewound_memmap, rewound_memmap_entries * sizeof(struct memmap_entry)); + memmap_entries = rewound_memmap_entries; + } else { + rewound_data = ext_mem_alloc(data_size); +#if defined (BIOS) + rewound_s2_data = ext_mem_alloc(s2_data_size); + rewound_bss = ext_mem_alloc(bss_size); +#endif + /* addition due to allocation potentially adding new memory map entries */ + rewound_memmap = ext_mem_alloc((memmap_entries + 16) * sizeof(struct memmap_entry)); + memcpy(rewound_memmap, memmap, memmap_entries * sizeof(struct memmap_entry)); + rewound_memmap_entries = memmap_entries; + memcpy(rewound_data, data_begin, data_size); +#if defined (BIOS) + memcpy(rewound_s2_data, s2_data_begin, s2_data_size); + memcpy(rewound_bss, bss_begin, bss_size); +#endif + } + + term_fallback(); + + if (bad_config == false) { + if (!init_config_smbios()) { + +#if defined (UEFI) + if (init_config_disk(boot_volume)) { +#endif + volume_iterate_parts(boot_volume, + if (!init_config_disk(_PART)) { + boot_volume = _PART; + break; + } + ); +#if defined (UEFI) + } +#endif + } + } + +#if defined (__riscv) + init_riscv(NULL); +#endif + + char *quiet_str = config_get_value(NULL, 0, "QUIET"); + quiet = quiet_str != NULL && strcmp(quiet_str, "yes") == 0; + + char *verbose_str = config_get_value(NULL, 0, "VERBOSE"); + verbose = verbose_str != NULL && strcmp(verbose_str, "yes") == 0; + + char *serial_str = config_get_value(NULL, 0, "SERIAL"); + serial = +#if defined (UEFI) + is_efi_serial_present() && +#endif + serial_str != NULL && strcmp(serial_str, "yes") == 0; + +#if defined (BIOS) + if (serial) { + char *baudrate_s = config_get_value(NULL, 0, "SERIAL_BAUDRATE"); + if (baudrate_s == NULL) { + serial_baudrate = 115200; + } else { + serial_baudrate = strtoui(baudrate_s, NULL, 10); + if (serial_baudrate == 0 || serial_baudrate > 115200) { + serial_baudrate = 115200; + } + } + } +#endif + + char *hash_mismatch_panic_str = config_get_value(NULL, 0, "HASH_MISMATCH_PANIC"); + hash_mismatch_panic = hash_mismatch_panic_str == NULL || strcmp(hash_mismatch_panic_str, "yes") == 0; + + char *randomise_mem_str = config_get_value(NULL, 0, "RANDOMISE_MEMORY"); + if (randomise_mem_str == NULL) + randomise_mem_str = config_get_value(NULL, 0, "RANDOMIZE_MEMORY"); + bool randomise_mem = randomise_mem_str != NULL && strcmp(randomise_mem_str, "yes") == 0; + if (randomise_mem) { + pmm_randomise_memory(); + } + + char *editor_enabled_str = config_get_value(NULL, 0, "EDITOR_ENABLED"); + if (editor_enabled_str != NULL) { + editor_enabled = strcmp(editor_enabled_str, "yes") == 0; + } + + char *help_hidden_str = config_get_value(NULL, 0, "INTERFACE_HELP_HIDDEN"); + if (help_hidden_str != NULL) { + help_hidden = strcmp(help_hidden_str, "yes") == 0; + } + + char *interface_help_colour_str = config_get_value(NULL, 0, "INTERFACE_HELP_COLOUR"); + if (interface_help_colour_str == NULL) { + interface_help_colour_str = config_get_value(NULL, 0, "INTERFACE_HELP_COLOR"); + } + if (interface_help_colour_str != NULL && interface_help_colour_str[0] != '\0') { + interface_help_colour[3] = interface_help_colour_str[0]; + interface_help_colour_bright[3] = interface_help_colour_str[0]; + } + + { + char *tmp = config_get_value(NULL, 0, "INTERFACE_BRANDING"); + if (tmp != NULL) { + size_t len = strlen(tmp) + 1; + menu_branding = ext_mem_alloc(len); + memcpy(menu_branding, tmp, len); + } + } + if (menu_branding == NULL) { +#if defined (BIOS) + { + uint32_t eax, ebx, ecx, edx; + if (!cpuid(0x80000001, 0, &eax, &ebx, &ecx, &edx) || !(edx & (1 << 29))) { + menu_branding = "Limine " LIMINE_VERSION " (ia-32, BIOS)"; + } else { + menu_branding = "Limine " LIMINE_VERSION " (x86-64, BIOS)"; + } + } +#elif defined (UEFI) +#if defined (__i386__) + { + uint32_t eax, ebx, ecx, edx; + if (!cpuid(0x80000001, 0, &eax, &ebx, &ecx, &edx) || !(edx & (1 << 29))) { + menu_branding = "Limine " LIMINE_VERSION " (ia-32, UEFI32)"; + } else { + menu_branding = "Limine " LIMINE_VERSION " (x86-64, UEFI32)"; + } + } +#else + menu_branding = "Limine " LIMINE_VERSION " (" +#if defined (__x86_64__) + "x86-64" +#elif defined (__riscv) + "riscv64" +#elif defined (__aarch64__) + "aarch64" +#elif defined (__loongarch64) + "loongarch64" +#endif + ", UEFI)"; +#endif +#endif + } + + { + char *tmp = config_get_value(NULL, 0, "INTERFACE_BRANDING_COLOUR"); + if (tmp == NULL) + tmp = config_get_value(NULL, 0, "INTERFACE_BRANDING_COLOR"); + if (tmp != NULL) { + size_t len = strlen(tmp) + 1; + menu_branding_colour = ext_mem_alloc(len); + memcpy(menu_branding_colour, tmp, len); + } else { + menu_branding_colour = "6"; + } + } + + bool skip_timeout = false; + struct menu_entry *selected_menu_entry = NULL; + + size_t selected_entry = 0; + + char *default_entry = config_get_value(NULL, 0, "DEFAULT_ENTRY"); + if (default_entry != NULL) { + selected_entry = strtoui(default_entry, NULL, 10); + if (selected_entry) + selected_entry--; + } + +#if defined (UEFI) + char *remember_last = config_get_value(NULL, 0, "REMEMBER_LAST_ENTRY"); + if (remember_last != NULL && strcasecmp(remember_last, "yes") == 0) { + UINTN getvar_size = sizeof(size_t); + size_t last; + if (gRT->GetVariable(L"LimineLastBootedEntry", + &limine_efi_vendor_guid, + NULL, + &getvar_size, + &last) == 0 && getvar_size == sizeof(size_t)) { + selected_entry = last; + } + } +#endif + + // Use print tree to load up selected_menu_entry and determine if the + // default entry is valid. + size_t max_entries = print_tree(0, 0, NULL, 0, 0, selected_entry, menu_tree, &selected_menu_entry, NULL, NULL); + if (selected_entry >= max_entries) { + selected_entry = 0; + } + + size_t timeout = 5; + char *timeout_config = config_get_value(NULL, 0, "TIMEOUT"); + if (timeout_config != NULL) { + if (!strcmp(timeout_config, "no")) + skip_timeout = true; + else + timeout = strtoui(timeout_config, NULL, 10); + } + +#if defined(UEFI) + bool reboot_to_firmware_supported = reboot_to_fw_ui_supported(); +#endif + + if (!first_run) { + quiet = false; + skip_timeout = true; + } + + if (!skip_timeout && !timeout) { + if (max_entries == 0 || selected_menu_entry->sub != NULL) { + quiet = false; + print("Default entry is not valid or directory, booting to menu.\n"); + skip_timeout = true; + } else { + goto autoboot; + } + } + + menu_init_term(); + + if (terms[0]->cols < 40 || terms[0]->rows < 16) { + // Terminal too small for menu, fall back to text console +#if defined (BIOS) + vga_textmode_init(true); +#elif defined (UEFI) + serial = true; + term_fallback(); +#endif + } + + size_t tree_offset = 0; + +refresh: + if (selected_entry >= tree_offset + terms[0]->rows - 8) { + tree_offset = selected_entry - (terms[0]->rows - 9); + } + if (selected_entry < tree_offset) { + tree_offset = selected_entry; + } + + FOR_TERM(TERM->autoflush = false); + + FOR_TERM(TERM->cursor_enabled = false); + + print("\e[2J\e[H"); + { + size_t x, y; + print("\n"); + terms[0]->get_cursor_pos(terms[0], &x, &y); + set_cursor_pos_helper(terms[0]->cols / 2 - DIV_ROUNDUP(strlen(menu_branding), 2), y); + print("\e[3%sm%s\e[0m", menu_branding_colour, menu_branding); + print("\n\n\n\n"); + } + + if (max_entries == 0) { + if (quiet) { + quiet = false; + menu_init_term(); + } + const char *msg; + if (config_ready) { + msg = "[config file contains no valid entries]"; + } else { + msg = "[config file not found]"; + } + set_cursor_pos_helper(terms[0]->cols / 2 - strlen(msg) / 2, terms[0]->rows / 2); + print("%s\n", msg); + } + + size_t max_tree_len, max_tree_height; + max_entries = print_tree(tree_offset, terms[0]->rows - 8, NULL, 0, 0, selected_entry, menu_tree, + &selected_menu_entry, &max_tree_len, &max_tree_height); + + if (max_entries != 0) { + size_t half_cols = terms[0]->cols / 2; + size_t half_tree = DIV_ROUNDUP(max_tree_len, 2); + size_t tree_prefix_len = (half_cols > half_tree + 2) ? (half_cols - half_tree - 2) : 0; + char *tree_prefix = ext_mem_alloc(tree_prefix_len + 1); + memset(tree_prefix, ' ', tree_prefix_len); + + if (max_tree_height > terms[0]->rows - 10) { + max_tree_height = terms[0]->rows - 10; + } + + set_cursor_pos_helper(0, terms[0]->rows / 2 - max_tree_height / 2); + + max_entries = print_tree(tree_offset, terms[0]->rows - 8, tree_prefix, 0, 0, selected_entry, menu_tree, + &selected_menu_entry, NULL, NULL); + + pmm_free(tree_prefix, tree_prefix_len + 1); + } + + { + size_t x, y; + terms[0]->get_cursor_pos(terms[0], &x, &y); + + if (max_entries != 0) { + if (tree_offset > 0) { + set_cursor_pos_helper(terms[0]->cols / 2 - 1, 4); + print(serial ? "^^^" : "↑↑↑"); + } + + if (tree_offset + (terms[0]->rows - 8) < max_entries) { + set_cursor_pos_helper(terms[0]->cols / 2 - 1, terms[0]->rows - 3); + print(serial ? "vvv" : "↓↓↓"); + } + } + + if (!help_hidden) { + set_cursor_pos_helper(0, 3); + if (max_entries != 0) { + if (selected_menu_entry->sub == NULL) { + print(" %sARROWS\e[0m Select %sENTER\e[0m Boot %s%s", + interface_help_colour, interface_help_colour, interface_help_colour, + editor_enabled ? "E\e[0m Edit" : "\e[0m"); + } else { + print(" %sARROWS\e[0m Select %sENTER\e[0m %s", + interface_help_colour, interface_help_colour, + selected_menu_entry->expanded ? "Collapse" : "Expand"); + } + } +#if defined(UEFI) + if (reboot_to_firmware_supported) { + set_cursor_pos_helper(terms[0]->cols - (editor_enabled ? 37 : 20), 3); + print("%sS\e[0m Firmware Setup", interface_help_colour); + } +#endif + if (editor_enabled) { + set_cursor_pos_helper(terms[0]->cols - 17, 3); + print("%sB\e[0m Blank Entry", interface_help_colour); + } + } + set_cursor_pos_helper(x, y); + } + + if (max_entries == 0 || selected_menu_entry->sub != NULL) + skip_timeout = true; + + int c; + + if (skip_timeout == false) { + print("\n\n"); + for (size_t i = timeout; i; i--) { + set_cursor_pos_helper(0, terms[0]->rows - 1); + FOR_TERM(TERM->scroll_enabled = false); + print("\e[2K%sBooting automatically in %s%U%s, press any key to stop the countdown...\e[0m", + interface_help_colour, interface_help_colour_bright, (uint64_t)i, interface_help_colour); + FOR_TERM(TERM->scroll_enabled = true); + FOR_TERM(TERM->double_buffer_flush(TERM)); + if ((c = pit_sleep_and_quit_on_keypress(1))) { + skip_timeout = true; + if (quiet) { + quiet = false; + menu_init_term(); + goto timeout_aborted; + } + print("\e[2K"); + FOR_TERM(TERM->double_buffer_flush(TERM)); + goto timeout_aborted; + } + } + goto autoboot; + } + + set_cursor_pos_helper(0, terms[0]->rows - 1); + if (max_entries != 0 && selected_menu_entry->comment != NULL) { + FOR_TERM(TERM->scroll_enabled = false); + print("\e[36m%s\e[0m", selected_menu_entry->comment); + FOR_TERM(TERM->scroll_enabled = true); + } + + if (booting_from_editor) { + if (booting_from_blank) { + goto editor_blank; + } + goto editor; + } + + FOR_TERM(TERM->double_buffer_flush(TERM)); + + for (;;) { + c = getchar(); +timeout_aborted: + if (max_entries == 0) { + switch (c) { + case 'b': case 'B': case 's': case 'S': + break; + default: + continue; + + } + } + switch (c) { + case '1': case '2': case '3': case '4': case '5': + case '6': case '7': case '8': case '9': { + int ent = (c - '0') - 1; + if (ent < (int)max_entries) { + selected_entry = ent; + print_tree(0, 0, NULL, 0, 0, selected_entry, menu_tree, + &selected_menu_entry, NULL, NULL); + goto autoboot; + } + goto refresh; + } + case GETCHAR_HOME: + selected_entry = 0; + goto refresh; + case GETCHAR_END: + selected_entry = max_entries - 1; + goto refresh; + case GETCHAR_CURSOR_UP: + if (selected_entry == 0) + selected_entry = max_entries - 1; + else + selected_entry--; + goto refresh; + case GETCHAR_CURSOR_DOWN: + if (++selected_entry == max_entries) + selected_entry = 0; + goto refresh; + case GETCHAR_CURSOR_RIGHT: + case '\n': + case ' ': + autoboot: + if (max_entries == 0) { + break; + } + if (selected_menu_entry->sub != NULL) { + selected_menu_entry->expanded = !selected_menu_entry->expanded; + goto refresh; + } + if (!quiet) { + if (term_backend == FALLBACK) { + if (!gterm_init(NULL, NULL, NULL, 0, 0)) { +#if defined (BIOS) + vga_textmode_init(true); +#elif defined (UEFI) + serial = true; + term_fallback(); +#endif + } + } else { + reset_term(); + } + } + +#if defined (UEFI) + gRT->SetVariable(L"LimineLastBootedEntry", + &limine_efi_vendor_guid, + EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS, + sizeof(size_t), + &selected_entry); +#endif + + boot(selected_menu_entry->body); + case 'e': + case 'E': { + if (editor_enabled) { + editor: + if (max_entries == 0 || selected_menu_entry->sub != NULL) { + break; + } + editor_no_term_reset = true; + char *new_body = config_entry_editor(selected_menu_entry->name, selected_menu_entry->body); + if (new_body == NULL) + goto refresh; + selected_menu_entry->body = new_body; + goto autoboot; + } + break; + } +#if defined(UEFI) + case 's': + case 'S': { + if (reboot_to_firmware_supported) { + reboot_to_fw_ui(); + } + break; + } +#endif + case 'b': + case 'B': { + if (editor_enabled) { + editor_blank: + booting_from_blank = true; + char *new_entry = config_entry_editor("Blank Entry", ""); + if (new_entry != NULL) { + config_ready = true; + boot(new_entry); + } + booting_from_blank = false; + goto refresh; + } + break; + } + } + } +} + +noreturn void boot(char *config) { +#if defined (__riscv) + init_riscv(config); +#endif + + char *cmdline; + { + char *tmp = config_get_value(config, 0, "KERNEL_CMDLINE"); + if (!tmp) { + tmp = config_get_value(config, 0, "CMDLINE"); + } + if (tmp) { + size_t len = strlen(tmp) + 1; + cmdline = ext_mem_alloc(len); + memcpy(cmdline, tmp, len); + } else { + cmdline = ""; + } + } + + char *proto = config_get_value(config, 0, "PROTOCOL"); + if (proto == NULL) { + panic(true, "Boot protocol not specified for this entry"); + } + + if (!strcmp(proto, "limine")) { + limine_load(config, cmdline); + } else if (!strcmp(proto, "linux")) { + linux_load(config, cmdline); + } else if (!strcmp(proto, "multiboot1") || !strcmp(proto, "multiboot")) { +#if defined (__x86_64__) || defined (__i386__) + multiboot1_load(config, cmdline); +#else + quiet = false; + print("Multiboot 1 is not available on non-x86 architectures.\n\n"); +#endif + } else if (!strcmp(proto, "multiboot2")) { +#if defined (__x86_64__) || defined (__i386__) + multiboot2_load(config, cmdline); +#else + quiet = false; + print("Multiboot 2 is not available on non-x86 architectures.\n\n"); +#endif +#if defined (BIOS) + } else if (!strcmp(proto, "bios_chainload") + || !strcmp(proto, "bios")) { +#elif defined (UEFI) + } else if (!strcmp(proto, "efi_chainload") + || !strcmp(proto, "efi") + || !strcmp(proto, "uefi")) { +#endif + chainload(config, cmdline); + } + + panic(true, "Unsupported protocol specified."); +} diff --git a/limine/common/menu.h b/limine/common/menu.h new file mode 100644 index 0000000..38f48f6 --- /dev/null +++ b/limine/common/menu.h @@ -0,0 +1,20 @@ +#ifndef MENU_H__ +#define MENU_H__ + +#include +#include + +#if defined(UEFI) +bool reboot_to_fw_ui_supported(void); +noreturn void reboot_to_fw_ui(void); +#endif + +noreturn void menu(bool first_run); + +noreturn void boot(char *config); + +char *config_entry_editor(const char *title, const char *orig_entry); + +extern bool booting_from_editor; + +#endif diff --git a/limine/common/menu_thunk.asm_aarch64 b/limine/common/menu_thunk.asm_aarch64 new file mode 100644 index 0000000..5fcff87 --- /dev/null +++ b/limine/common/menu_thunk.asm_aarch64 @@ -0,0 +1,27 @@ +.section .data + +.align 3 +stack_at_first_entry: + .quad 0 + +.section .text + +.global menu +.extern _menu + +menu: + adrp x8, stack_at_first_entry + ldr x9, [x8, :lo12:stack_at_first_entry] + cbz x9, 1f + mov sp, x9 + b 2f +1: + mov x9, sp + str x9, [x8, :lo12:stack_at_first_entry] +2: + mov x30, xzr + mov x29, xzr + + b _menu + +.section .note.GNU-stack,"",%progbits diff --git a/limine/common/menu_thunk.asm_ia32 b/limine/common/menu_thunk.asm_ia32 new file mode 100644 index 0000000..08af227 --- /dev/null +++ b/limine/common/menu_thunk.asm_ia32 @@ -0,0 +1,29 @@ +section .text + +global menu +extern _menu +menu: + pop eax + call .L1 +.L1: + pop eax + add eax, .L3 - .L1 + cmp dword [eax], 0 + jne .L2 + mov [eax], esp + jmp .L4 + +.L2: + mov edi, [esp] + mov esp, [eax] + push edi + jmp .L4 + +.L3: + dq 0 + +.L4: + push 0 + jmp _menu + +section .note.GNU-stack noalloc noexec nowrite progbits diff --git a/limine/common/menu_thunk.asm_loongarch64 b/limine/common/menu_thunk.asm_loongarch64 new file mode 100644 index 0000000..831c566 --- /dev/null +++ b/limine/common/menu_thunk.asm_loongarch64 @@ -0,0 +1,25 @@ +.section .data + +.align 3 +stack_at_first_entry: + .quad 0 + +.section .text + +.global menu +.extern _menu + +menu: + la $t0, stack_at_first_entry + ld.d $t1, $t0, 0 + beqz $t1, 1f + move $sp, $t1 + b 2f +1: + st.d $sp, $t0, 0 +2: + move $fp, $r0 + move $ra, $r0 + b _menu + +.section .note.GNU-stack,"",%progbits diff --git a/limine/common/menu_thunk.asm_riscv64 b/limine/common/menu_thunk.asm_riscv64 new file mode 100644 index 0000000..75d6301 --- /dev/null +++ b/limine/common/menu_thunk.asm_riscv64 @@ -0,0 +1,24 @@ +.section .data + +.p2align 3 +stack_at_first_entry: + .8byte 0 + +.section .text + +.global menu +.extern _menu + +menu: +.option norelax + lla t0, stack_at_first_entry + ld t1, (t0) + beqz t1, 1f + mv sp, t1 + j 2f +1: sd sp, (t0) +2: mv fp, zero + mv ra, zero + j _menu + +.section .note.GNU-stack,"",%progbits diff --git a/limine/common/menu_thunk.asm_x86_64 b/limine/common/menu_thunk.asm_x86_64 new file mode 100644 index 0000000..506c177 --- /dev/null +++ b/limine/common/menu_thunk.asm_x86_64 @@ -0,0 +1,23 @@ +section .data + +stack_at_first_entry: + dq 0 + +section .text + +global menu +extern _menu +menu: + xor eax, eax + cmp [rel stack_at_first_entry], rax + jne .L1 + mov [rel stack_at_first_entry], rsp + jmp .L2 +.L1: + mov rsp, [rel stack_at_first_entry] +.L2: + push 0 + push 0 + jmp _menu + +section .note.GNU-stack noalloc noexec nowrite progbits diff --git a/limine/common/mm/mtrr.c b/limine/common/mm/mtrr.c new file mode 100644 index 0000000..b00e171 --- /dev/null +++ b/limine/common/mm/mtrr.c @@ -0,0 +1,140 @@ +#if defined (__x86_64__) || defined (__i386__) + +#include +#include +#include +#include +#include +#include +#include + +static bool mtrr_supported(void) { + uint32_t eax, ebx, ecx, edx; + + if (!cpuid(1, 0, &eax, &ebx, &ecx, &edx)) + return false; + + return !!(edx & (1 << 12)); +} + +uint64_t *saved_mtrrs = NULL; + +void mtrr_save(void) { + if (!mtrr_supported()) { + return; + } + + uint64_t ia32_mtrrcap = rdmsr(0xfe); + + uint8_t var_reg_count = ia32_mtrrcap & 0xff; + + if (saved_mtrrs == NULL) { + saved_mtrrs = ext_mem_alloc(( + (var_reg_count * 2) /* variable MTRRs, 2 MSRs each */ + + 11 /* 11 fixed MTRRs */ + + 1 /* 1 default type MTRR */ + ) * sizeof(uint64_t)); + } + + /* save variable range MTRRs */ + for (uint8_t i = 0; i < var_reg_count * 2; i += 2) { + saved_mtrrs[i] = rdmsr(0x200 + i); + saved_mtrrs[i + 1] = rdmsr(0x200 + i + 1); + } + + /* save fixed range MTRRs */ + saved_mtrrs[var_reg_count * 2 + 0] = rdmsr(0x250); + saved_mtrrs[var_reg_count * 2 + 1] = rdmsr(0x258); + saved_mtrrs[var_reg_count * 2 + 2] = rdmsr(0x259); + saved_mtrrs[var_reg_count * 2 + 3] = rdmsr(0x268); + saved_mtrrs[var_reg_count * 2 + 4] = rdmsr(0x269); + saved_mtrrs[var_reg_count * 2 + 5] = rdmsr(0x26a); + saved_mtrrs[var_reg_count * 2 + 6] = rdmsr(0x26b); + saved_mtrrs[var_reg_count * 2 + 7] = rdmsr(0x26c); + saved_mtrrs[var_reg_count * 2 + 8] = rdmsr(0x26d); + saved_mtrrs[var_reg_count * 2 + 9] = rdmsr(0x26e); + saved_mtrrs[var_reg_count * 2 + 10] = rdmsr(0x26f); + + /* save MTRR default type */ + saved_mtrrs[var_reg_count * 2 + 11] = rdmsr(0x2ff); + + /* make sure that the saved MTRR default has MTRRs off */ + saved_mtrrs[var_reg_count * 2 + 11] &= ~((uint64_t)1 << 11); +} + +void mtrr_restore(void) { + if (!mtrr_supported()) { + return; + } + + uint64_t ia32_mtrrcap = rdmsr(0xfe); + + uint8_t var_reg_count = ia32_mtrrcap & 0xff; + + if (saved_mtrrs == NULL) { + panic(true, "mtrr: Attempted restore without prior save"); + } + + /* according to the Intel SDM 12.11.7.2 "MemTypeSet() Function", + we need to follow this procedure before changing MTRR set up */ + + /* save old cr0 and then enable the CD flag and disable the NW flag */ + uintptr_t old_cr0; + asm volatile ("mov %%cr0, %0" : "=r"(old_cr0) :: "memory"); + uintptr_t new_cr0 = (old_cr0 | (1 << 30)) & ~((uintptr_t)1 << 29); + asm volatile ("mov %0, %%cr0" :: "r"(new_cr0) : "memory"); + + /* then invalidate the caches */ + asm volatile ("wbinvd" ::: "memory"); + + /* do a cr3 read/write to flush the TLB */ + uintptr_t cr3; + asm volatile ("mov %%cr3, %0" : "=r"(cr3) :: "memory"); + asm volatile ("mov %0, %%cr3" :: "r"(cr3) : "memory"); + + /* disable the MTRRs */ + uint64_t mtrr_def = rdmsr(0x2ff); + mtrr_def &= ~((uint64_t)1 << 11); + wrmsr(0x2ff, mtrr_def); + + /* restore variable range MTRRs */ + for (uint8_t i = 0; i < var_reg_count * 2; i += 2) { + wrmsr(0x200 + i, saved_mtrrs[i]); + wrmsr(0x200 + i + 1, saved_mtrrs[i + 1]); + } + + /* restore fixed range MTRRs */ + wrmsr(0x250, saved_mtrrs[var_reg_count * 2 + 0]); + wrmsr(0x258, saved_mtrrs[var_reg_count * 2 + 1]); + wrmsr(0x259, saved_mtrrs[var_reg_count * 2 + 2]); + wrmsr(0x268, saved_mtrrs[var_reg_count * 2 + 3]); + wrmsr(0x269, saved_mtrrs[var_reg_count * 2 + 4]); + wrmsr(0x26a, saved_mtrrs[var_reg_count * 2 + 5]); + wrmsr(0x26b, saved_mtrrs[var_reg_count * 2 + 6]); + wrmsr(0x26c, saved_mtrrs[var_reg_count * 2 + 7]); + wrmsr(0x26d, saved_mtrrs[var_reg_count * 2 + 8]); + wrmsr(0x26e, saved_mtrrs[var_reg_count * 2 + 9]); + wrmsr(0x26f, saved_mtrrs[var_reg_count * 2 + 10]); + + /* restore MTRR default type */ + wrmsr(0x2ff, saved_mtrrs[var_reg_count * 2 + 11]); + + /* now do the opposite of the cache disable and flush from above */ + + /* re-enable MTRRs */ + mtrr_def = rdmsr(0x2ff); + mtrr_def |= (1 << 11); + wrmsr(0x2ff, mtrr_def); + + /* do a cr3 read/write to flush the TLB */ + asm volatile ("mov %%cr3, %0" : "=r"(cr3) :: "memory"); + asm volatile ("mov %0, %%cr3" :: "r"(cr3) : "memory"); + + /* then invalidate the caches */ + asm volatile ("wbinvd" ::: "memory"); + + /* restore old value of cr0 */ + asm volatile ("mov %0, %%cr0" :: "r"(old_cr0) : "memory"); +} + +#endif diff --git a/limine/common/mm/mtrr.h b/limine/common/mm/mtrr.h new file mode 100644 index 0000000..9cead12 --- /dev/null +++ b/limine/common/mm/mtrr.h @@ -0,0 +1,11 @@ +#ifndef MM__MTRR_H__ +#define MM__MTRR_H__ + +#if defined (__x86_64__) || defined (__i386__) + +void mtrr_save(void); +void mtrr_restore(void); + +#endif + +#endif diff --git a/limine/common/mm/pmm.c b/limine/common/mm/pmm.c new file mode 100644 index 0000000..123c156 --- /dev/null +++ b/limine/common/mm/pmm.c @@ -0,0 +1,87 @@ +#include +#include +#include +#include +#include +#include + +static bool full_overlap_check(uint64_t base1, uint64_t top1, + uint64_t base2, uint64_t top2) { + return ((base1 >= base2 && base1 < top2) + && (top1 > base2 && top1 <= top2)); +} + +bool check_usable_memory(uint64_t base, uint64_t top) { + uint64_t overlap_remaining = top - base; + + for (size_t i = 0; i < memmap_entries; i++) { + if (memmap[i].type != MEMMAP_USABLE +#if defined (UEFI) + && memmap[i].type != MEMMAP_EFI_RECLAIMABLE +#endif + && memmap[i].type != MEMMAP_BOOTLOADER_RECLAIMABLE + && memmap[i].type != MEMMAP_KERNEL_AND_MODULES) { + continue; + } + + uint64_t memmap_top = memmap[i].base + memmap[i].length; + + if (full_overlap_check(base, top, memmap[i].base, memmap_top)) { + return true; + } + + // Count how many bytes from a real-RAM entry overlap our range + if (memmap[i].base < top && memmap_top > base) { + uint64_t overlap_bottom = memmap[i].base > base ? memmap[i].base : base; + uint64_t overlap_top = memmap_top < top ? memmap_top : top; + + uint64_t overlap_size = overlap_top - overlap_bottom; + overlap_remaining -= overlap_size; + + if (overlap_remaining == 0) { + return true; + } + } + } + + return false; +} + +void pmm_randomise_memory(void) { + print("pmm: Randomising memory contents..."); + + for (size_t i = 0; i < memmap_entries; i++) { + if (memmap[i].type != MEMMAP_USABLE) + continue; + +#if defined (BIOS) + // We're not going to randomise memory above 4GiB from protected mode, + // are we? + if (memmap[i].base >= 0x100000000) { + continue; + } +#endif + + uint8_t *ptr = (void *)(uintptr_t)memmap[i].base; + size_t len = memmap[i].length; + + for (size_t j = 0;;) { + uint32_t random = rand32(); + uint8_t *rnd_data = (void *)&random; + if (j >= len) + break; + ptr[j++] = rnd_data[0]; + if (j >= len) + break; + ptr[j++] = rnd_data[1]; + if (j >= len) + break; + ptr[j++] = rnd_data[2]; + if (j >= len) + break; + ptr[j++] = rnd_data[3]; + } + } + + print("\n"); +} diff --git a/limine/common/mm/pmm.h b/limine/common/mm/pmm.h new file mode 100644 index 0000000..e3bc404 --- /dev/null +++ b/limine/common/mm/pmm.h @@ -0,0 +1,79 @@ +#ifndef MM__PMM_H__ +#define MM__PMM_H__ + +#include +#include +#include + +struct memmap_entry { + uint64_t base; + uint64_t length; + uint32_t type; + uint32_t unused; +}; + +#define MEMMAP_USABLE 1 +#define MEMMAP_RESERVED 2 +#define MEMMAP_ACPI_RECLAIMABLE 3 +#define MEMMAP_ACPI_NVS 4 +#define MEMMAP_BAD_MEMORY 5 +#define MEMMAP_BOOTLOADER_RECLAIMABLE 0x1000 +#define MEMMAP_KERNEL_AND_MODULES 0x1001 +#define MEMMAP_FRAMEBUFFER 0x1002 +#define MEMMAP_RESERVED_MAPPED 0x1003 +#define MEMMAP_EFI_RECLAIMABLE 0x2000 + +struct meminfo { + size_t uppermem; + size_t lowermem; +}; + +struct meminfo mmap_get_info(size_t mmap_count, struct memmap_entry *mmap); + +#if defined (BIOS) +extern struct memmap_entry memmap[]; +extern size_t memmap_entries; +#endif + +#if defined (UEFI) +extern struct memmap_entry *memmap; +extern size_t memmap_entries; + +extern struct memmap_entry *untouched_memmap; +extern size_t untouched_memmap_entries; +#endif + +extern bool allocations_disallowed; + +void init_memmap(void); +struct memmap_entry *get_memmap(size_t *entries); +struct memmap_entry *get_raw_memmap(size_t *entry_count); +void print_memmap(struct memmap_entry *mm, size_t size); +uint64_t pmm_check_type(uint64_t addr); +bool memmap_alloc_range_in(struct memmap_entry *m, size_t *_count, + uint64_t base, uint64_t length, uint32_t type, uint32_t overlay_type, bool do_panic, bool simulation, bool new_entry); +bool memmap_alloc_range(uint64_t base, uint64_t length, uint32_t type, uint32_t overlay_type, bool panic, bool simulation, bool new_entry); +void pmm_randomise_memory(void); + +void *ext_mem_alloc_size_t(size_t count); +void *ext_mem_alloc(uint64_t count); +void *ext_mem_alloc_type(uint64_t count, uint32_t type); +void *ext_mem_alloc_type_aligned(uint64_t count, uint32_t type, size_t alignment); +void *ext_mem_alloc_type_aligned_mode(uint64_t count, uint32_t type, size_t alignment, bool allow_high_allocs); + +void *conv_mem_alloc(uint64_t count); + +void pmm_free_size_t(void *ptr, size_t length); +void pmm_free(void *ptr, uint64_t length); +void *pmm_realloc(void *old_ptr, uint64_t old_size, uint64_t new_size); + +#if defined (UEFI) +void pmm_release_uefi_mem(void); +#endif + +bool check_usable_memory(uint64_t base, uint64_t top); +void pmm_sanitise_entries(struct memmap_entry *m, size_t *_count, bool align_entries); + +extern bool pmm_sanitiser_keep_first_page; + +#endif diff --git a/limine/common/mm/pmm.s2.c b/limine/common/mm/pmm.s2.c new file mode 100644 index 0000000..6cc74cb --- /dev/null +++ b/limine/common/mm/pmm.s2.c @@ -0,0 +1,885 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined (UEFI) +# include +#endif + +#define PAGE_SIZE 4096 + +#if defined (BIOS) +extern symbol bss_end; +#endif + +bool allocations_disallowed = true; + +void *conv_mem_alloc(uint64_t count) { + static uint64_t base = 4096; + + if (allocations_disallowed) + panic(false, "Memory allocations disallowed"); + + count = ALIGN_UP(count, 4096); + + for (;;) { + if (base + count > 0x100000) + panic(false, "Conventional memory allocation failed"); + + if (memmap_alloc_range(base, count, MEMMAP_BOOTLOADER_RECLAIMABLE, MEMMAP_USABLE, false, false, false)) { + void *ret = (void *)(uintptr_t)base; + // Zero out allocated space + memset(ret, 0, count); + base += count; + + pmm_sanitise_entries(memmap, &memmap_entries, false); + + return ret; + } + + base += 4096; + } +} + +#if defined (BIOS) +#define memmap_max_entries ((size_t)512) + +struct memmap_entry memmap[memmap_max_entries]; +size_t memmap_entries = 0; +#endif + +#if defined (UEFI) +static size_t memmap_max_entries; + +struct memmap_entry *memmap; +size_t memmap_entries = 0; + +struct memmap_entry *untouched_memmap; +size_t untouched_memmap_entries = 0; +#endif + +static const char *memmap_type(uint32_t type) { + switch (type) { + case MEMMAP_USABLE: + return "Usable RAM"; + case MEMMAP_RESERVED: + return "Reserved"; + case MEMMAP_RESERVED_MAPPED: + return "Reserved (Mapped)"; + case MEMMAP_ACPI_RECLAIMABLE: + return "ACPI reclaimable"; + case MEMMAP_ACPI_NVS: + return "ACPI NVS"; + case MEMMAP_BAD_MEMORY: + return "Bad memory"; + case MEMMAP_FRAMEBUFFER: + return "Framebuffer"; + case MEMMAP_BOOTLOADER_RECLAIMABLE: + return "Bootloader reclaimable"; + case MEMMAP_KERNEL_AND_MODULES: + return "Kernel/Modules"; + case MEMMAP_EFI_RECLAIMABLE: + return "EFI reclaimable"; + default: + return "???"; + } +} + +void print_memmap(struct memmap_entry *mm, size_t size) { + for (size_t i = 0; i < size; i++) { + print("[%X -> %X] : %X <%s (%x)>\n", + mm[i].base, + mm[i].base + mm[i].length, + mm[i].length, + memmap_type(mm[i].type), mm[i].type); + } +} + +static bool align_entry(uint64_t *base, uint64_t *length) { + if (*length < PAGE_SIZE) + return false; + + uint64_t orig_base = *base; + + *base = ALIGN_UP(*base, PAGE_SIZE); + + *length -= (*base - orig_base); + *length = ALIGN_DOWN(*length, PAGE_SIZE); + + if (*length == 0) + return false; + + return true; +} + +#if defined (BIOS) +bool pmm_sanitiser_keep_first_page = false; +#else +bool pmm_sanitiser_keep_first_page = true; +#endif + +void pmm_sanitise_entries(struct memmap_entry *m, size_t *_count, bool align_entries) { + size_t count = *_count; + + for (size_t i = 0; i < count; i++) { + if (m[i].type != MEMMAP_USABLE) + continue; + + // Check if the entry overlaps other entries + for (size_t j = 0; j < count; j++) { + if (j == i) + continue; + + uint64_t base = m[i].base; + uint64_t length = m[i].length; + uint64_t top = base + length; + + uint64_t res_base = m[j].base; + uint64_t res_length = m[j].length; + uint64_t res_top = res_base + res_length; + + // Non-usable entry fully contains usable entry + if (res_base <= base && res_top >= top) { + m[i].base = top; + m[i].length = 0; + break; + } + + if ( (res_base >= base && res_base < top) + && (res_top >= base && res_top < top) ) { + // TODO actually handle splitting off usable chunks + panic(false, "A non-usable memory map entry is inside a usable section."); + } + + if (res_base >= base && res_base < top) { + top = res_base; + } + + if (res_top >= base && res_top < top) { + base = res_top; + } + + m[i].base = base; + m[i].length = top - base; + } + + if (!m[i].length + || (align_entries && !align_entry(&m[i].base, &m[i].length))) { + // Remove i from memmap + if (i < count - 1) { + m[i] = m[count - 1]; + } + count--; i--; + } + } + + // Remove 0 length usable entries and usable entries below 0x1000 + for (size_t i = 0; i < count; i++) { + if (m[i].type != MEMMAP_USABLE) + continue; + + if (!pmm_sanitiser_keep_first_page && m[i].base < 0x1000) { + uint64_t entry_top; + if (__builtin_add_overflow(m[i].base, m[i].length, &entry_top) || + entry_top <= 0x1000) { + goto del_mm1; + } + + m[i].length -= 0x1000 - m[i].base; + m[i].base = 0x1000; + } + + if (m[i].length == 0) { +del_mm1: + // Remove i from memmap + if (i < count - 1) { + m[i] = m[count - 1]; + } + count--; i--; + } + } + + // Sort the entries + for (size_t p = 0; p + 1 < count; p++) { + uint64_t min = m[p].base; + size_t min_index = p; + for (size_t i = p; i < count; i++) { + if (m[i].base < min) { + min = m[i].base; + min_index = i; + } + } + struct memmap_entry min_e = m[min_index]; + m[min_index] = m[p]; + m[p] = min_e; + } + + // Merge contiguous bootloader-reclaimable, reserved (mapped), usable entries + for (size_t i = 0; i + 1 < count; i++) { + if (m[i].type != MEMMAP_BOOTLOADER_RECLAIMABLE + && m[i].type != MEMMAP_RESERVED_MAPPED + && m[i].type != MEMMAP_USABLE) + continue; + + if (m[i+1].type == m[i].type + && m[i+1].base == m[i].base + m[i].length) { + m[i].length += m[i+1].length; + + // Eradicate from memmap + for (size_t j = i + 2; j < count; j++) { + m[j - 1] = m[j]; + } + count--; + i--; + } + } + + *_count = count; +} + +#if defined (UEFI) +static void pmm_reclaim_uefi_mem(struct memmap_entry *m, size_t *_count, bool raw); +#endif + +struct memmap_entry *get_memmap(size_t *entries) { +#if defined (UEFI) + if (efi_boot_services_exited == false) { + panic(true, "get_memmap called whilst in boot services"); + } + + pmm_reclaim_uefi_mem(memmap, &memmap_entries, false); +#endif + + pmm_sanitise_entries(memmap, &memmap_entries, true); + + *entries = memmap_entries; + + allocations_disallowed = true; + + return memmap; +} + +#if defined (BIOS) +void init_memmap(void) { + for (size_t i = 0; i < e820_entries; i++) { + if (memmap_entries == memmap_max_entries) { + panic(false, "Memory map exhausted."); + } + + memmap[memmap_entries] = e820_map[i]; + + uint64_t top = memmap[memmap_entries].base + memmap[memmap_entries].length; + + if (memmap[memmap_entries].type == MEMMAP_USABLE) { + if (memmap[memmap_entries].base >= EBDA && memmap[memmap_entries].base < 0x100000) { + if (top <= 0x100000) + continue; + + memmap[memmap_entries].length -= 0x100000 - memmap[memmap_entries].base; + memmap[memmap_entries].base = 0x100000; + } + + if (top > EBDA && top <= 0x100000) { + memmap[memmap_entries].length -= top - EBDA; + } + } + + memmap_entries++; + } + + pmm_sanitise_entries(memmap, &memmap_entries, false); + + // Allocate bootloader itself + memmap_alloc_range(4096, + ALIGN_UP((uintptr_t)bss_end, 4096) - 4096, MEMMAP_BOOTLOADER_RECLAIMABLE, 0, true, false, false); + + pmm_sanitise_entries(memmap, &memmap_entries, false); + + allocations_disallowed = false; +} +#endif + +#if defined (UEFI) +static struct memmap_entry *recl; + +extern symbol __slide, __image_base, __image_end; + +void init_memmap(void) { + EFI_STATUS status; + + EFI_MEMORY_DESCRIPTOR tmp_mmap[1]; + efi_mmap_size = sizeof(tmp_mmap); + UINTN mmap_key = 0; + + gBS->GetMemoryMap(&efi_mmap_size, tmp_mmap, &mmap_key, &efi_desc_size, &efi_desc_ver); + + memmap_max_entries = (efi_mmap_size / efi_desc_size) + 512; + + efi_mmap_size += 4096; + + status = gBS->AllocatePool(EfiLoaderData, efi_mmap_size, (void **)&efi_mmap); + if (status) { + goto fail; + } + + status = gBS->AllocatePool(EfiLoaderData, memmap_max_entries * sizeof(struct memmap_entry), (void **)&memmap); + if (status) { + gBS->FreePool(efi_mmap); + goto fail; + } + + status = gBS->AllocatePool(EfiLoaderData, memmap_max_entries * sizeof(struct memmap_entry), (void **)&untouched_memmap); + if (status) { + gBS->FreePool(efi_mmap); + gBS->FreePool(memmap); + goto fail; + } + + status = gBS->GetMemoryMap(&efi_mmap_size, efi_mmap, &mmap_key, &efi_desc_size, &efi_desc_ver); + if (status) { + gBS->FreePool(efi_mmap); + gBS->FreePool(memmap); + gBS->FreePool(untouched_memmap); + goto fail; + } + + size_t entry_count = efi_mmap_size / efi_desc_size; + + for (size_t i = 0; i < entry_count; i++) { + EFI_MEMORY_DESCRIPTOR *entry = (void *)efi_mmap + i * efi_desc_size; + + if (entry->NumberOfPages == 0) { + continue; + } + + uint32_t our_type; + switch (entry->Type) { + case EfiReservedMemoryType: + case EfiRuntimeServicesCode: + case EfiRuntimeServicesData: + case EfiUnusableMemory: + case EfiMemoryMappedIO: + case EfiMemoryMappedIOPortSpace: + case EfiPalCode: + default: + our_type = MEMMAP_RESERVED; break; + case EfiLoaderCode: + case EfiLoaderData: + case EfiBootServicesCode: + case EfiBootServicesData: + our_type = MEMMAP_EFI_RECLAIMABLE; break; + case EfiACPIReclaimMemory: + our_type = MEMMAP_ACPI_RECLAIMABLE; break; + case EfiACPIMemoryNVS: + our_type = MEMMAP_ACPI_NVS; break; + case EfiConventionalMemory: + our_type = MEMMAP_USABLE; break; + } + + uint64_t base = entry->PhysicalStart; + uint64_t length; + if (__builtin_mul_overflow(entry->NumberOfPages, (uint64_t)4096, &length)) { + continue; + } + + memmap[memmap_entries].base = base; + memmap[memmap_entries].length = length; + memmap[memmap_entries].type = our_type; + + memmap_entries++; + } + + pmm_sanitise_entries(memmap, &memmap_entries, false); + + allocations_disallowed = false; + + // Let's leave 64MiB to the firmware below 4GiB + for (size_t i = 0; i < 64; i++) { + ext_mem_alloc_type(0x100000, MEMMAP_EFI_RECLAIMABLE); + } + + memcpy(untouched_memmap, memmap, memmap_entries * sizeof(struct memmap_entry)); + untouched_memmap_entries = memmap_entries; + + // Now own all the usable entries + for (size_t i = 0; i < untouched_memmap_entries; i++) { + if (untouched_memmap[i].type != MEMMAP_USABLE) + continue; + + EFI_PHYSICAL_ADDRESS base = untouched_memmap[i].base; + +#if defined (__i386__) + if (untouched_memmap[i].base + untouched_memmap[i].length > 0x100000000) { + continue; + } +#endif + + status = gBS->AllocatePages(AllocateAddress, EfiLoaderCode, + untouched_memmap[i].length / 4096, &base); + + if (status) { + for (size_t j = 0; j < untouched_memmap[i].length; j += 4096) { + base = untouched_memmap[i].base + j; + status = gBS->AllocatePages(AllocateAddress, EfiLoaderCode, 1, &base); + if (status) { + memmap_alloc_range(base, 4096, MEMMAP_EFI_RECLAIMABLE, MEMMAP_USABLE, true, false, false); + } + } + } + } + + memcpy(untouched_memmap, memmap, memmap_entries * sizeof(struct memmap_entry)); + untouched_memmap_entries = memmap_entries; + + // Allocate bootloader itself + size_t image_size = ALIGN_UP((uintptr_t)__image_end - (uintptr_t)__image_base, 4096); + + memmap_alloc_range((uintptr_t)__slide, (uintptr_t)image_size, + MEMMAP_BOOTLOADER_RECLAIMABLE, 0, true, false, true); + + pmm_sanitise_entries(memmap, &memmap_entries, false); + + recl = ext_mem_alloc(1024 * sizeof(struct memmap_entry)); + + return; + +fail: + panic(false, "pmm: Failure initialising memory map"); +} + +static void pmm_reclaim_uefi_mem(struct memmap_entry *m, size_t *_count, bool raw) { + size_t count = *_count; + + size_t recl_i = 0; + + for (size_t i = 0; i < count; i++) { + if (m[i].type == MEMMAP_EFI_RECLAIMABLE) { + recl[recl_i++] = m[i]; + } + } + + for (size_t ri = 0; ri < recl_i; ri++) { + struct memmap_entry *r = &recl[ri]; + + // Punch holes in our EFI reclaimable entry for every EFI area which is + // boot services or conventional that fits within + size_t efi_mmap_entry_count = efi_mmap_size / efi_desc_size; + for (size_t i = 0; i < efi_mmap_entry_count; i++) { + EFI_MEMORY_DESCRIPTOR *entry = (void *)efi_mmap + i * efi_desc_size; + + uint64_t base = r->base; + uint64_t top = base + r->length; + uint64_t efi_base = entry->PhysicalStart; + uint64_t efi_size; + if (__builtin_mul_overflow(entry->NumberOfPages, (uint64_t)4096, &efi_size)) { + continue; // Skip malformed entry + } + + if (efi_base < base) { + if (efi_size <= base - efi_base) + continue; + efi_size -= base - efi_base; + efi_base = base; + } + + uint64_t efi_top = efi_base + efi_size; + + if (efi_top > top) { + if (efi_size <= efi_top - top) + continue; + efi_size -= efi_top - top; + efi_top = top; + } + + // Sanity check + if (!(efi_base >= base && efi_base < top + && efi_top > base && efi_top <= top)) + continue; + + uint32_t our_type; + switch (entry->Type) { + case EfiLoaderCode: + case EfiLoaderData: + case EfiBootServicesCode: + case EfiBootServicesData: + if (raw) { + our_type = MEMMAP_USABLE; + } else { + our_type = MEMMAP_BOOTLOADER_RECLAIMABLE; + } + break; + case EfiConventionalMemory: + our_type = MEMMAP_USABLE; break; + case EfiACPIReclaimMemory: + our_type = MEMMAP_ACPI_RECLAIMABLE; break; + case EfiACPIMemoryNVS: + our_type = MEMMAP_ACPI_NVS; break; + default: + our_type = MEMMAP_RESERVED; break; + } + + memmap_alloc_range_in(m, &count, efi_base, efi_size, our_type, 0, true, false, false); + } + } + + allocations_disallowed = true; + + pmm_sanitise_entries(m, &count, false); + + *_count = count; +} + +void pmm_release_uefi_mem(void) { + EFI_STATUS status; + + for (size_t i = 0; i < untouched_memmap_entries; i++) { + if (untouched_memmap[i].type != MEMMAP_USABLE + && untouched_memmap[i].type != MEMMAP_BOOTLOADER_RECLAIMABLE) { + continue; + } + + status = gBS->FreePages(untouched_memmap[i].base, untouched_memmap[i].length / 4096); + + if (status) { + panic(false, "pmm: FreePages failure (%X)", (uint64_t)status); + } + } + + allocations_disallowed = true; +} +#endif + +#if defined (BIOS) +struct memmap_entry *get_raw_memmap(size_t *entry_count) { + *entry_count = e820_entries; + return e820_map; +} +#endif + +#if defined (UEFI) +struct memmap_entry *get_raw_memmap(size_t *entry_count) { + if (efi_boot_services_exited == false) { + panic(true, "get_raw_memmap called whilst in boot services"); + } + + bool old_skfp = pmm_sanitiser_keep_first_page; + pmm_sanitiser_keep_first_page = true; + pmm_reclaim_uefi_mem(untouched_memmap, &untouched_memmap_entries, true); + pmm_sanitiser_keep_first_page = old_skfp; + + *entry_count = untouched_memmap_entries; + return untouched_memmap; +} +#endif + +void pmm_free_size_t(void *ptr, size_t length) { + pmm_free(ptr, length); +} + +void pmm_free(void *ptr, uint64_t count) { + count = ALIGN_UP(count, 4096); + if (allocations_disallowed) + panic(false, "Memory allocations disallowed"); + memmap_alloc_range((uintptr_t)ptr, count, MEMMAP_USABLE, 0, false, false, true); +} + +void *pmm_realloc(void *old_ptr, uint64_t old_size, uint64_t new_size) { + if (new_size == 0) { + if (old_ptr != NULL) { + pmm_free(old_ptr, old_size); + } + return NULL; + } + if (old_ptr == NULL) { + return ext_mem_alloc(new_size); + } + + void *new_ptr = ext_mem_alloc(new_size); + + memcpy(new_ptr, old_ptr, MIN(new_size, old_size)); + + pmm_free(old_ptr, old_size); + + return new_ptr; +} + +void *ext_mem_alloc_size_t(size_t count) { + return ext_mem_alloc(count); +} + +void *ext_mem_alloc(uint64_t count) { + return ext_mem_alloc_type(count, MEMMAP_BOOTLOADER_RECLAIMABLE); +} + +void *ext_mem_alloc_type(uint64_t count, uint32_t type) { + return ext_mem_alloc_type_aligned(count, type, 4096); +} + +void *ext_mem_alloc_type_aligned(uint64_t count, uint32_t type, size_t alignment) { + return ext_mem_alloc_type_aligned_mode(count, type, alignment, false); +} + +// Allocate memory top down. +void *ext_mem_alloc_type_aligned_mode(uint64_t count, uint32_t type, size_t alignment, bool allow_high_allocs) { +#if !defined (__x86_64__) && !defined (__i386__) + (void)allow_high_allocs; +#endif + + count = ALIGN_UP(count, alignment); + + if (allocations_disallowed) + panic(false, "Memory allocations disallowed"); + +#if defined(__x86_64__) || defined(__i386__) + // Try below 4GiB first to avoid relying on firmware identity-mapping + // memory above 4GiB (some buggy UEFI implementations don't). + uint64_t limit = 0x100000000; + +again: +#else + uint64_t limit = UINT64_MAX; +#endif + + for (int i = memmap_entries - 1; i >= 0; i--) { + if (memmap[i].type != 1) + continue; + + int64_t entry_base = (int64_t)(memmap[i].base); + int64_t entry_top = (int64_t)(memmap[i].base + memmap[i].length); + + if ((uint64_t)entry_top > limit) { + entry_top = (int64_t)limit; + if (entry_base >= entry_top) + continue; + } + + int64_t alloc_base = ALIGN_DOWN(entry_top - (int64_t)count, alignment); + + // This entry is too small for us. + if (alloc_base < entry_base) + continue; + + // We now reserve the range we need. + int64_t aligned_length = entry_top - alloc_base; + memmap_alloc_range((uint64_t)alloc_base, (uint64_t)aligned_length, type, MEMMAP_USABLE, true, false, false); + + void *ret; + +#if defined (__i386__) + if (!allow_high_allocs) { +#endif + ret = (void *)(size_t)alloc_base; + + // Zero out allocated space + memset(ret, 0, count); +#if defined (__i386__) + } else { + static uint64_t above64_ret; + above64_ret = alloc_base; + ret = &above64_ret; + } +#endif + + pmm_sanitise_entries(memmap, &memmap_entries, false); + + return ret; + } + +#if defined(__x86_64__) || defined(__i386__) + if (allow_high_allocs && limit < UINT64_MAX) { + limit = UINT64_MAX; + goto again; + } +#endif + + panic(false, "High memory allocator: Out of memory"); +} + +/// Compute and returns the amount of upper and lower memory till +/// the first hole. +struct meminfo mmap_get_info(size_t mmap_count, struct memmap_entry *mmap) { + struct meminfo info = {0}; + + // Find contiguous usable memory from address 0 (lower) and 1 MiB (upper) + // by iteratively extending a frontier. Handles unsorted entries. + uint64_t lower_end = 0; + uint64_t upper_end = 0x100000; + bool progress; + + do { + progress = false; + for (size_t i = 0; i < mmap_count; i++) { + if (mmap[i].type != MEMMAP_USABLE) + continue; + uint64_t base = mmap[i].base; + uint64_t top = base + mmap[i].length; + if (base <= lower_end && top > lower_end) { + lower_end = top; + progress = true; + } + if (base <= upper_end && top > upper_end) { + upper_end = top; + progress = true; + } + } + } while (progress); + + if (lower_end > 0x100000) + lower_end = 0x100000; + + info.lowermem = lower_end; + info.uppermem = upper_end - 0x100000; + + return info; +} + +static bool pmm_new_entry(struct memmap_entry *m, size_t *_count, + uint64_t base, uint64_t length, uint32_t type) { + size_t count = *_count; + + uint64_t top; + if (__builtin_add_overflow(base, length, &top)) { + panic(false, "pmm: Integer overflow in memory range calculation"); + } + + // Handle overlapping new entries. + for (size_t i = 0; i < count; i++) { + uint64_t entry_base = m[i].base; + uint64_t entry_top; + if (__builtin_add_overflow(m[i].base, m[i].length, &entry_top)) { + continue; // Skip malformed entries + } + + // Full overlap + if (base <= entry_base && top >= entry_top) { + // Remove overlapped entry + if (i < count - 1) { + m[i] = m[count - 1]; + } + count--; + i--; + continue; + } + + // Partial overlap (bottom) + if (base <= entry_base && top < entry_top && top > entry_base) { + // Entry gets bottom shaved off + m[i].base += top - entry_base; + m[i].length -= top - entry_base; + continue; + } + + // Partial overlap (top) + if (base > entry_base && base < entry_top && top >= entry_top) { + // Entry gets top shaved off + m[i].length -= entry_top - base; + continue; + } + + // Nested (pain) + if (base > entry_base && top < entry_top) { + // Entry gets top shaved off first + m[i].length -= entry_top - base; + + // Now we need to create a new entry + if (count >= memmap_max_entries) + panic(false, "Memory map exhausted."); + + struct memmap_entry *new_entry = &m[count++]; + + new_entry->type = m[i].type; + new_entry->base = top; + new_entry->length = entry_top - top; + + continue; + } + } + + if (count >= memmap_max_entries) + panic(false, "Memory map exhausted."); + + struct memmap_entry *target = &m[count++]; + + target->type = type; + target->base = base; + target->length = length; + + *_count = count; + return true; +} + +uint64_t pmm_check_type(uint64_t addr) { + for (size_t i = 0; i < memmap_entries; i++) { + uint64_t entry_base = memmap[i].base; + uint64_t entry_top = memmap[i].base + memmap[i].length; + + if (addr >= entry_base && addr < entry_top) { + return memmap[i].type; + } + } + + return (uint64_t)-1; +} + +bool memmap_alloc_range_in(struct memmap_entry *m, size_t *_count, + uint64_t base, uint64_t length, uint32_t type, uint32_t overlay_type, bool do_panic, bool simulation, bool new_entry) { + size_t count = *_count; + + if (length == 0) + return true; + + if (simulation && new_entry) { + return true; + } + + uint64_t top; + if (__builtin_add_overflow(base, length, &top)) { + if (do_panic) + panic(false, "Memory allocation overflow."); + return false; + } + + for (size_t i = 0; i < count; i++) { + if (overlay_type != 0 && m[i].type != overlay_type) + continue; + + uint64_t entry_base = m[i].base; + uint64_t entry_top; + if (__builtin_add_overflow(m[i].base, m[i].length, &entry_top)) + continue; + + if (base >= entry_base && base < entry_top && top <= entry_top) { + if (simulation) + return true; + + if (pmm_new_entry(m, &count, base, length, type) == true) { + goto success; + } + } + } + + if (!new_entry && do_panic) + panic(false, "Memory allocation failure."); + + if (!new_entry) { + return false; + } + + if (pmm_new_entry(m, &count, base, length, type) == false) { + return false; + } + +success: + pmm_sanitise_entries(m, &count, false); + *_count = count; + return true; +} + +bool memmap_alloc_range(uint64_t base, uint64_t length, uint32_t type, uint32_t overlay_type, bool do_panic, bool simulation, bool new_entry) { + return memmap_alloc_range_in(memmap, &memmap_entries, base, length, type, overlay_type, do_panic, simulation, new_entry); +} diff --git a/limine/common/mm/vmm.c b/limine/common/mm/vmm.c new file mode 100644 index 0000000..106f47e --- /dev/null +++ b/limine/common/mm/vmm.c @@ -0,0 +1,555 @@ +#include +#include +#include +#include +#include +#include +#include + +#define PT_SIZE ((uint64_t)0x1000) + +typedef uint64_t pt_entry_t; + +// Maps level indexes to the page size for that level. +_Static_assert(VMM_MAX_LEVEL <= 5, "6-level paging not supported"); +static uint64_t page_sizes[5] = { + 0x1000, + 0x200000, + 0x40000000, + 0x8000000000, + 0x1000000000000, +}; + +static pt_entry_t *get_next_level(pagemap_t pagemap, pt_entry_t *current_level, + uint64_t virt, enum page_size desired_sz, + size_t level_idx, size_t entry); + +void map_pages(pagemap_t pagemap, uint64_t virt, uint64_t phys, uint64_t flags, uint64_t count) { + if (virt % 0x1000 != 0 || phys % 0x1000 != 0 || count % 0x1000 != 0) { + panic(true, "vmm: Misaligned call to map_pages()"); + } + + for (uint64_t i = 0; i < count; ) { + if (((phys + i) & (0x40000000 - 1)) == 0 && ((virt + i) & (0x40000000 - 1)) == 0 && count - i >= 0x40000000) { + map_page(pagemap, virt + i, phys + i, flags, Size1GiB); + i += 0x40000000; + continue; + } + if (((phys + i) & (0x200000 - 1)) == 0 && ((virt + i) & (0x200000 - 1)) == 0 && count - i >= 0x200000) { + map_page(pagemap, virt + i, phys + i, flags, Size2MiB); + i += 0x200000; + continue; + } + map_page(pagemap, virt + i, phys + i, flags, Size4KiB); + i += 0x1000; + } +} + +#if defined (__x86_64__) || defined (__i386__) + +#define PT_FLAG_VALID ((uint64_t)1 << 0) +#define PT_FLAG_WRITE ((uint64_t)1 << 1) +#define PT_FLAG_USER ((uint64_t)1 << 2) +#define PT_FLAG_LARGE ((uint64_t)1 << 7) +#define PT_FLAG_NX ((uint64_t)1 << 63) +#define PT_PADDR_MASK ((uint64_t)0x0000FFFFFFFFF000) + +#define PT_TABLE_FLAGS (PT_FLAG_VALID | PT_FLAG_WRITE | PT_FLAG_USER) +#define PT_IS_TABLE(x) (((x) & (PT_FLAG_VALID | PT_FLAG_LARGE)) == PT_FLAG_VALID) +#define PT_IS_LARGE(x) (((x) & (PT_FLAG_VALID | PT_FLAG_LARGE)) == (PT_FLAG_VALID | PT_FLAG_LARGE)) +#define PT_TO_VMM_FLAGS(x) ((x) & (PT_FLAG_WRITE | PT_FLAG_NX | VMM_FLAG_FB)) + +#define pte_new(addr, flags) ((pt_entry_t)(addr) | (flags)) +#define pte_addr(pte) ((pte) & PT_PADDR_MASK) + +pagemap_t new_pagemap(int paging_mode) { + pagemap_t pagemap; + pagemap.levels = paging_mode == PAGING_MODE_X86_64_5LVL ? 5 : 4; + pagemap.top_level = ext_mem_alloc(PT_SIZE); + return pagemap; +} + +static bool is_1gib_page_supported(void) { + // Cache the cpuid result :^) + static bool CACHE_INIT = false; + static bool CACHE = false; + + if (!CACHE_INIT) { + // Check if 1GiB pages are supported: + uint32_t eax, ebx, ecx, edx; + + CACHE = cpuid(0x80000001, 0, &eax, &ebx, &ecx, &edx) && ((edx & 1 << 26) == 1 << 26); + CACHE_INIT = true; + + printv("paging: 1GiB pages are %s!\n", CACHE ? "supported" : "not supported"); + } + + return CACHE; +} + +void map_page(pagemap_t pagemap, uint64_t virt_addr, uint64_t phys_addr, uint64_t flags, enum page_size pg_size) { + // Calculate the indices in the various tables using the virtual address + size_t pml5_entry = (virt_addr & ((uint64_t)0x1ff << 48)) >> 48; + size_t pml4_entry = (virt_addr & ((uint64_t)0x1ff << 39)) >> 39; + size_t pml3_entry = (virt_addr & ((uint64_t)0x1ff << 30)) >> 30; + size_t pml2_entry = (virt_addr & ((uint64_t)0x1ff << 21)) >> 21; + size_t pml1_entry = (virt_addr & ((uint64_t)0x1ff << 12)) >> 12; + + pt_entry_t *pml5, *pml4, *pml3, *pml2, *pml1; + + static bool pat_supported = false, pat_supported_got = false; + if (!pat_supported_got) { + uint32_t eax, ebx, ecx, edx; + if (cpuid(1, 0, &eax, &ebx, &ecx, &edx) && (edx & (1 << 16))) { + pat_supported = true; + } + pat_supported_got = true; + } + + flags |= PT_FLAG_VALID; // Always present + if ((flags & VMM_FLAG_FB) && !pat_supported) { + flags &= ~(uint64_t)VMM_FLAG_FB; + } + + // Paging levels + switch (pagemap.levels) { + case 5: + pml5 = pagemap.top_level; + goto level5; + case 4: + pml4 = pagemap.top_level; + goto level4; + default: + __builtin_unreachable(); + } + +level5: + pml4 = get_next_level(pagemap, pml5, virt_addr, pg_size, 4, pml5_entry); +level4: + pml3 = get_next_level(pagemap, pml4, virt_addr, pg_size, 3, pml4_entry); + + if (pg_size == Size1GiB) { + // Check if 1GiB pages are available. + if (is_1gib_page_supported()) { + pml3[pml3_entry] = (pt_entry_t)(phys_addr | flags | PT_FLAG_LARGE); + } else { + // If 1GiB pages are not supported then emulate it by splitting them into + // 2MiB pages. + for (uint64_t i = 0; i < 0x40000000; i += 0x200000) { + map_page(pagemap, virt_addr + i, phys_addr + i, flags, Size2MiB); + } + } + + return; + } + + pml2 = get_next_level(pagemap, pml3, virt_addr, pg_size, 2, pml3_entry); + + if (pg_size == Size2MiB) { + pml2[pml2_entry] = (pt_entry_t)(phys_addr | flags | PT_FLAG_LARGE); + return; + } + + pml1 = get_next_level(pagemap, pml2, virt_addr, pg_size, 1, pml2_entry); + + // PML1 wants PAT bit at 7 instead of 12 + if (flags & ((uint64_t)1 << 12)) { + flags &= ~((uint64_t)1 << 12); + flags |= ((uint64_t)1 << 7); + } + + pml1[pml1_entry] = (pt_entry_t)(phys_addr | flags); +} + +#elif defined (__aarch64__) + +// Here we operate under the assumption that 4K pages are supported by the CPU. +// This appears to be guaranteed by UEFI, as section 2.3.6 "AArch64 Platforms" +// states that the primary processor core configuration includes 4K translation +// granules (TCR_EL1.TG0 = 0). +// Support for 4K pages also implies 2M, 1G and 512G blocks. + +// Sanity check that 4K pages are supported. +void vmm_assert_4k_pages(void) { + uint64_t aa64mmfr0; + asm volatile ("mrs %0, id_aa64mmfr0_el1" : "=r"(aa64mmfr0)); + + if (((aa64mmfr0 >> 28) & 0b1111) == 0b1111) { + panic(false, "vmm: CPU does not support 4K pages, please make a bug report about this."); + } +} + +#define PT_FLAG_VALID ((uint64_t)1 << 0) +#define PT_FLAG_TABLE ((uint64_t)1 << 1) +#define PT_FLAG_4K_PAGE ((uint64_t)1 << 1) +#define PT_FLAG_BLOCK ((uint64_t)0 << 1) +#define PT_FLAG_USER ((uint64_t)1 << 6) +#define PT_FLAG_READONLY ((uint64_t)1 << 7) +#define PT_FLAG_INNER_SH ((uint64_t)3 << 8) +#define PT_FLAG_ACCESS ((uint64_t)1 << 10) +#define PT_FLAG_XN ((uint64_t)1 << 54) +#define PT_FLAG_WB ((uint64_t)0 << 2) +#define PT_FLAG_FB ((uint64_t)1 << 2) +#define PT_PADDR_MASK ((uint64_t)0x0000FFFFFFFFF000) + +#define PT_TABLE_FLAGS (PT_FLAG_VALID | PT_FLAG_TABLE) + +#define PT_IS_TABLE(x) (((x) & (PT_FLAG_VALID | PT_FLAG_TABLE)) == (PT_FLAG_VALID | PT_FLAG_TABLE)) +#define PT_IS_LARGE(x) (((x) & (PT_FLAG_VALID | PT_FLAG_TABLE)) == PT_FLAG_VALID) +#define PT_TO_VMM_FLAGS(x) (pt_to_vmm_flags_internal(x)) + +#define pte_new(addr, flags) ((pt_entry_t)(addr) | (flags)) +#define pte_addr(pte) ((pte) & PT_PADDR_MASK) + +static uint64_t pt_to_vmm_flags_internal(pt_entry_t entry) { + uint64_t flags = 0; + + if (!(entry & PT_FLAG_READONLY)) + flags |= VMM_FLAG_WRITE; + if (entry & PT_FLAG_XN) + flags |= VMM_FLAG_NOEXEC; + if (entry & PT_FLAG_FB) + flags |= VMM_FLAG_FB; + + return flags; +} + +pagemap_t new_pagemap(int paging_mode) { + pagemap_t pagemap; + pagemap.levels = paging_mode == PAGING_MODE_AARCH64_5LVL ? 5 : 4; + pagemap.top_level[0] = ext_mem_alloc(PT_SIZE); + pagemap.top_level[1] = ext_mem_alloc(PT_SIZE); + return pagemap; +} + +void map_page(pagemap_t pagemap, uint64_t virt_addr, uint64_t phys_addr, uint64_t flags, enum page_size pg_size) { + // Calculate the indices in the various tables using the virtual address + size_t pml5_entry = (virt_addr & ((uint64_t)0xf << 48)) >> 48; + size_t pml4_entry = (virt_addr & ((uint64_t)0x1ff << 39)) >> 39; + size_t pml3_entry = (virt_addr & ((uint64_t)0x1ff << 30)) >> 30; + size_t pml2_entry = (virt_addr & ((uint64_t)0x1ff << 21)) >> 21; + size_t pml1_entry = (virt_addr & ((uint64_t)0x1ff << 12)) >> 12; + + pt_entry_t *pml5, *pml4, *pml3, *pml2, *pml1; + + bool is_higher_half = virt_addr & ((uint64_t)1 << 63); + + uint64_t real_flags = PT_FLAG_VALID | PT_FLAG_INNER_SH | PT_FLAG_ACCESS | PT_FLAG_WB; + if (!(flags & VMM_FLAG_WRITE)) + real_flags |= PT_FLAG_READONLY; + if (flags & VMM_FLAG_NOEXEC) + real_flags |= PT_FLAG_XN; + if (flags & VMM_FLAG_FB) + real_flags |= PT_FLAG_FB; + + // Paging levels + switch (pagemap.levels) { + case 5: + pml5 = pagemap.top_level[is_higher_half]; + goto level5; + case 4: + pml4 = pagemap.top_level[is_higher_half]; + goto level4; + default: + __builtin_unreachable(); + } + +level5: + pml4 = get_next_level(pagemap, pml5, virt_addr, pg_size, 4, pml5_entry); +level4: + pml3 = get_next_level(pagemap, pml4, virt_addr, pg_size, 3, pml4_entry); + + if (pg_size == Size1GiB) { + pml3[pml3_entry] = (pt_entry_t)(phys_addr | real_flags | PT_FLAG_BLOCK); + return; + } + + pml2 = get_next_level(pagemap, pml3, virt_addr, pg_size, 2, pml3_entry); + + if (pg_size == Size2MiB) { + pml2[pml2_entry] = (pt_entry_t)(phys_addr | real_flags | PT_FLAG_BLOCK); + return; + } + + pml1 = get_next_level(pagemap, pml2, virt_addr, pg_size, 1, pml2_entry); + + pml1[pml1_entry] = (pt_entry_t)(phys_addr | real_flags | PT_FLAG_4K_PAGE); +} + +#elif defined (__riscv) + +#define PT_FLAG_VALID ((uint64_t)1 << 0) +#define PT_FLAG_READ ((uint64_t)1 << 1) +#define PT_FLAG_WRITE ((uint64_t)1 << 2) +#define PT_FLAG_EXEC ((uint64_t)1 << 3) +#define PT_FLAG_USER ((uint64_t)1 << 4) +#define PT_FLAG_ACCESSED ((uint64_t)1 << 6) +#define PT_FLAG_DIRTY ((uint64_t)1 << 7) +#define PT_FLAG_PBMT_NC ((uint64_t)1 << 62) +#define PT_PADDR_MASK ((uint64_t)0x003ffffffffffc00) + +#define PT_FLAG_RWX (PT_FLAG_READ | PT_FLAG_WRITE | PT_FLAG_EXEC) + +#define PT_TABLE_FLAGS PT_FLAG_VALID +#define PT_IS_TABLE(x) (((x) & (PT_FLAG_VALID | PT_FLAG_RWX)) == PT_FLAG_VALID) +#define PT_IS_LARGE(x) (((x) & (PT_FLAG_VALID | PT_FLAG_RWX)) > PT_FLAG_VALID) +#define PT_TO_VMM_FLAGS(x) (pt_to_vmm_flags_internal(x)) + +#define pte_new(addr, flags) (((pt_entry_t)(addr) >> 2) | (flags)) +#define pte_addr(pte) (((pte) & PT_PADDR_MASK) << 2) + +static uint64_t pt_to_vmm_flags_internal(pt_entry_t entry) { + uint64_t flags = 0; + + if (entry & PT_FLAG_WRITE) + flags |= VMM_FLAG_WRITE; + if (!(entry & PT_FLAG_EXEC)) + flags |= VMM_FLAG_NOEXEC; + if (entry & PT_FLAG_PBMT_NC) + flags |= VMM_FLAG_FB; + + return flags; +} + +uint64_t paging_mode_higher_half(int paging_mode) { + switch (paging_mode) { + case PAGING_MODE_RISCV_SV39: + return 0xffffffc000000000; + case PAGING_MODE_RISCV_SV48: + return 0xffff800000000000; + case PAGING_MODE_RISCV_SV57: + return 0xff00000000000000; + default: + panic(false, "paging_mode_higher_half: invalid mode"); + } +} + +int paging_mode_va_bits(int paging_mode) { + switch (paging_mode) { + case PAGING_MODE_RISCV_SV39: + return 39; + case PAGING_MODE_RISCV_SV48: + return 48; + case PAGING_MODE_RISCV_SV57: + return 57; + default: + panic(false, "paging_mode_va_bits: invalid mode"); + } +} + +int vmm_max_paging_mode(void) +{ + if (bsp_hart == NULL || !(bsp_hart->flags & RISCV_HART_HAS_MMU)) { + panic(false, "vmm: BSP hart does not advertise MMU support"); + } + + return PAGING_MODE_RISCV_SV39 + bsp_hart->mmu_type; +} + +static pt_entry_t pbmt_nc = 0; + +pagemap_t new_pagemap(int paging_mode) { + pagemap_t pagemap; + pagemap.paging_mode = paging_mode; + pagemap.max_page_size = paging_mode - 6; + pagemap.top_level = ext_mem_alloc(PT_SIZE); + + if (riscv_check_isa_extension("svpbmt", NULL, NULL)) { + printv("riscv: Svpbmt extension is supported.\n"); + pbmt_nc = PT_FLAG_PBMT_NC; + } + + return pagemap; +} + +void map_page(pagemap_t pagemap, uint64_t virt_addr, uint64_t phys_addr, uint64_t flags, enum page_size page_size) { + // Truncate the requested page size to the maximum supported. + if (page_size > pagemap.max_page_size) + page_size = pagemap.max_page_size; + + // Convert VMM_FLAG_* into PT_FLAG_*. + // Set the ACCESSED and DIRTY flags to avoid faults. + pt_entry_t ptflags = PT_FLAG_VALID | PT_FLAG_READ | PT_FLAG_ACCESSED | PT_FLAG_DIRTY; + if (flags & VMM_FLAG_WRITE) + ptflags |= PT_FLAG_WRITE; + if (!(flags & VMM_FLAG_NOEXEC)) + ptflags |= PT_FLAG_EXEC; + if (flags & VMM_FLAG_FB) + ptflags |= pbmt_nc; + + // Start at the highest level. + // The values of `enum page_size` map to the level index at which that size is mapped. + int level = pagemap.max_page_size; + pt_entry_t *table = pagemap.top_level; + for (;;) { + int index = (virt_addr >> (12 + 9 * level)) & 0x1ff; + + // Stop when we reach the level for the requested page size. + if (level == (int)page_size) { + table[index] = pte_new(phys_addr, ptflags); + break; + } + + table = get_next_level(pagemap, table, virt_addr, page_size, level, index); + level--; + } +} + +#elif defined (__loongarch64) + +#define INVALID_PAGE 0 + +#define PT_FLAG_VALID ((uint64_t)1 << 0) +#define PT_FLAG_DIRTY ((uint64_t)1 << 1) +#define PT_FLAG_MAT_CC ((uint64_t)1 << 4) +#define PT_FLAG_MAT_WUC ((uint64_t)1 << 5) +#define PT_FLAG_GLOBAL ((uint64_t)1 << 6) +#define PT_FLAG_HUGE ((uint64_t)1 << 6) +#define PT_FLAG_WRITE ((uint64_t)1 << 8) +#define PT_FLAG_HGLOBAL ((uint64_t)1 << 12) +#define PT_FLAG_NX ((uint64_t)1 << 62) +#define PT_PADDR_MASK ((uint64_t)0x0000FFFFFFFFF000) +#define PT_PADDR_HMASK ((uint64_t)0x0000FFFFFF000000) + +#define PT_TABLE_FLAGS 0 +#define PT_IS_TABLE(x) ((level_idx > 0) && (((x) & PT_FLAG_VALID) == 0) && ((x) != INVALID_PAGE)) +#define PT_IS_LARGE(x) (((x) & (PT_FLAG_HGLOBAL | PT_FLAG_HUGE)) == (PT_FLAG_HGLOBAL | PT_FLAG_HUGE)) +#define PT_TO_VMM_FLAGS(x) (pt_to_vmm_flags_internal(x)) + +#define pte_new(addr, flags) (pt_entry_t)((addr) | (flags)) + +static inline uint64_t pte_addr(uint64_t pte) { + if (PT_IS_LARGE(pte)) { + return pte & PT_PADDR_HMASK; + } + return pte & PT_PADDR_MASK; +} + +static uint64_t pt_to_vmm_flags_internal(pt_entry_t entry) { + uint64_t flags = 0; + + if (entry & PT_FLAG_WRITE) + flags |= VMM_FLAG_WRITE; + if (entry & PT_FLAG_NX) + flags |= VMM_FLAG_NOEXEC; + if (entry & PT_FLAG_MAT_WUC) + flags |= VMM_FLAG_FB; + + return flags; +} + +pagemap_t new_pagemap(int paging_mode) { + (void)paging_mode; + + uint32_t cpucfg_val; + + asm volatile ( + "cpucfg %0, %1" + : "=r"(cpucfg_val) + : "r"(1) + ); + + uint32_t valen = ((cpucfg_val >> 12) & 0xff) + 1; + if (valen < 48) { + panic(true, "vmm: VALEN values < 48 not currently supported"); + } + + (void)paging_mode; + pagemap_t pagemap; + pagemap.pgd[0] = ext_mem_alloc(PT_SIZE); + pagemap.pgd[1] = ext_mem_alloc(PT_SIZE); + return pagemap; +} + +void map_page(pagemap_t pagemap, uint64_t virt_addr, uint64_t phys_addr, uint64_t flags, enum page_size pg_size) { + size_t pml4_entry = (virt_addr & ((uint64_t)0x1ff << 39)) >> 39; + size_t pml3_entry = (virt_addr & ((uint64_t)0x1ff << 30)) >> 30; + size_t pml2_entry = (virt_addr & ((uint64_t)0x1ff << 21)) >> 21; + size_t pml1_entry = (virt_addr & ((uint64_t)0x1ff << 12)) >> 12; + + pt_entry_t *pml4, *pml3, *pml2, *pml1; + + bool is_higher_half = virt_addr & ((uint64_t)1 << 63); + + uint64_t real_flags = PT_FLAG_VALID | PT_FLAG_GLOBAL; + if (flags & VMM_FLAG_WRITE) + real_flags |= PT_FLAG_DIRTY | PT_FLAG_WRITE; + if (flags & VMM_FLAG_NOEXEC) + real_flags |= PT_FLAG_NX; + if (flags & VMM_FLAG_FB) + real_flags |= PT_FLAG_MAT_WUC; + else + real_flags |= PT_FLAG_MAT_CC; + + pml4 = pagemap.pgd[is_higher_half]; + + pml3 = get_next_level(pagemap, pml4, virt_addr, pg_size, 3, pml4_entry); + + if (pg_size == Size1GiB) { + pml3[pml3_entry] = pte_new(phys_addr, real_flags | PT_FLAG_HGLOBAL | PT_FLAG_HUGE); + return; + } + + pml2 = get_next_level(pagemap, pml3, virt_addr, pg_size, 2, pml3_entry); + + if (pg_size == Size2MiB) { + pml2[pml2_entry] = pte_new(phys_addr, real_flags | PT_FLAG_HGLOBAL | PT_FLAG_HUGE); + return; + } + + pml1 = get_next_level(pagemap, pml2, virt_addr, pg_size, 1, pml2_entry); + + pml1[pml1_entry] = pte_new(phys_addr, real_flags); +} + +#else +#error Unknown architecture +#endif + +static pt_entry_t *get_next_level(pagemap_t pagemap, pt_entry_t *current_level, + uint64_t virt, enum page_size desired_sz, + size_t level_idx, size_t entry) { + pt_entry_t *ret; + + if (PT_IS_TABLE(current_level[entry])) { + ret = (pt_entry_t *)(size_t)pte_addr(current_level[entry]); + } else { + if (PT_IS_LARGE(current_level[entry])) { + // We are replacing an existing large page with a smaller page. + // Split the previous mapping into mappings of the newly requested size + // before performing the requested map operation. + + + if ((level_idx >= VMM_MAX_LEVEL) || (level_idx == 0)) + panic(false, "Unexpected level in get_next_level"); + if (desired_sz >= VMM_MAX_LEVEL) + panic(false, "Unexpected page size in get_next_level"); + + uint64_t old_page_size = page_sizes[level_idx]; + uint64_t new_page_size = page_sizes[desired_sz]; + + // Save all the information from the old entry at this level + uint64_t old_flags = PT_TO_VMM_FLAGS(current_level[entry]); + uint64_t old_phys = pte_addr(current_level[entry]); + uint64_t old_virt = virt & ~(old_page_size - 1); + + if (old_phys & (old_page_size - 1)) + panic(false, "Unexpected page table entry address in get_next_level"); + + // Allocate a table for the next level + ret = ext_mem_alloc(PT_SIZE); + current_level[entry] = pte_new((size_t)ret, PT_TABLE_FLAGS); + + // Recreate the old mapping with smaller pages + for (uint64_t i = 0; i < old_page_size; i += new_page_size) { + map_page(pagemap, old_virt + i, old_phys + i, old_flags, desired_sz); + } + } else { + // Allocate a table for the next level + ret = ext_mem_alloc(PT_SIZE); + current_level[entry] = pte_new((size_t)ret, PT_TABLE_FLAGS); + } + } + + return ret; +} diff --git a/limine/common/mm/vmm.h b/limine/common/mm/vmm.h new file mode 100644 index 0000000..01cf7e6 --- /dev/null +++ b/limine/common/mm/vmm.h @@ -0,0 +1,166 @@ +#ifndef MM__VMM_H__ +#define MM__VMM_H__ + +#include +#include + +#if defined (__x86_64__) || defined (__i386__) + +#define VMM_FLAG_WRITE ((uint64_t)1 << 1) +#define VMM_FLAG_NOEXEC ((uint64_t)1 << 63) +#define VMM_FLAG_FB (((uint64_t)1 << 3) | ((uint64_t)1 << 12)) + +#define VMM_MAX_LEVEL 3 + +#define PAGING_MODE_X86_64_4LVL 0 +#define PAGING_MODE_X86_64_5LVL 1 + +#define PAGING_MODE_MIN PAGING_MODE_X86_64_4LVL +#define PAGING_MODE_MAX PAGING_MODE_X86_64_5LVL + +#define paging_mode_va_bits(mode) ((mode) ? 57 : 48) + +static inline uint64_t paging_mode_higher_half(int paging_mode) { + if (paging_mode == PAGING_MODE_X86_64_5LVL) { + return 0xff00000000000000; + } else { + return 0xffff800000000000; + } +} + +typedef struct { + int levels; + void *top_level; +} pagemap_t; + +enum page_size { + Size4KiB, + Size2MiB, + Size1GiB +}; + +pagemap_t new_pagemap(int lv); +void map_page(pagemap_t pagemap, uint64_t virt_addr, uint64_t phys_addr, uint64_t flags, enum page_size page_size); + +#elif defined (__aarch64__) + +// We use fake flags here because these don't properly map onto the +// aarch64 flags. +#define VMM_FLAG_WRITE ((uint64_t)1 << 0) +#define VMM_FLAG_NOEXEC ((uint64_t)1 << 1) +#define VMM_FLAG_FB ((uint64_t)1 << 2) + +#define VMM_MAX_LEVEL 3 + +#define PAGING_MODE_AARCH64_4LVL 0 +#define PAGING_MODE_AARCH64_5LVL 1 + +#define PAGING_MODE_MIN PAGING_MODE_AARCH64_4LVL +#define PAGING_MODE_MAX PAGING_MODE_AARCH64_5LVL + +#define paging_mode_va_bits(mode) ((mode) ? 53 : 49) + +static inline uint64_t paging_mode_higher_half(int paging_mode) { + if (paging_mode == PAGING_MODE_AARCH64_5LVL) { + return 0xffe0000000000000; + } else { + return 0xffff000000000000; + } +} + +typedef struct { + int levels; + void *top_level[2]; +} pagemap_t; + +enum page_size { + Size4KiB, + Size2MiB, + Size1GiB +}; + +void vmm_assert_4k_pages(void); +pagemap_t new_pagemap(int lv); +void map_page(pagemap_t pagemap, uint64_t virt_addr, uint64_t phys_addr, uint64_t flags, enum page_size page_size); + +#elif defined (__riscv) + +// We use fake flags here because these don't properly map onto the +// RISC-V flags. +#define VMM_FLAG_WRITE ((uint64_t)1 << 0) +#define VMM_FLAG_NOEXEC ((uint64_t)1 << 1) +#define VMM_FLAG_FB ((uint64_t)1 << 2) + +#define VMM_MAX_LEVEL 5 + +#define PAGING_MODE_RISCV_SV39 8 +#define PAGING_MODE_RISCV_SV48 9 +#define PAGING_MODE_RISCV_SV57 10 + +#define PAGING_MODE_MIN PAGING_MODE_RISCV_SV39 +#define PAGING_MODE_MAX PAGING_MODE_RISCV_SV57 + +int paging_mode_va_bits(int paging_mode); + +enum page_size { + Size4KiB, + Size2MiB, + Size1GiB, + Size512GiB, + Size256TiB +}; + +typedef struct { + enum page_size max_page_size; + int paging_mode; + void *top_level; +} pagemap_t; + +uint64_t paging_mode_higher_half(int paging_mode); +int vmm_max_paging_mode(void); +pagemap_t new_pagemap(int paging_mode); +void map_page(pagemap_t pagemap, uint64_t virt_addr, uint64_t phys_addr, uint64_t flags, enum page_size page_size); + +#elif defined (__loongarch64) + +#define paging_mode_va_bits(mode) 48 + +static inline uint64_t paging_mode_higher_half(int paging_mode) { + (void)paging_mode; + return 0xffff800000000000; +} + +// We use fake flags here because these don't properly map onto the +// LoongArch flags. +#define VMM_FLAG_WRITE ((uint64_t)1 << 0) +#define VMM_FLAG_NOEXEC ((uint64_t)1 << 1) +#define VMM_FLAG_FB ((uint64_t)1 << 2) + +#define VMM_MAX_LEVEL 3 + +#define PAGING_MODE_LOONGARCH64_4LVL 0 + +#define PAGING_MODE_MIN PAGING_MODE_LOONGARCH64_4LVL +#define PAGING_MODE_MAX PAGING_MODE_LOONGARCH64_4LVL + +enum page_size { + Size4KiB, + Size2MiB, + Size1GiB +}; + +typedef struct { + void *pgd[2]; +} pagemap_t; + +pagemap_t new_pagemap(int paging_mode); +void map_page(pagemap_t pagemap, uint64_t virt_addr, uint64_t phys_addr, uint64_t flags, enum page_size page_size); + +#else +#error Unknown architecture +#endif + +int vmm_max_paging_mode(void); +void map_pages(pagemap_t pagemap, uint64_t virt, uint64_t phys, uint64_t flags, uint64_t count); + +#endif diff --git a/limine/common/protos/chainload.c b/limine/common/protos/chainload.c new file mode 100644 index 0000000..a5512f4 --- /dev/null +++ b/limine/common/protos/chainload.c @@ -0,0 +1,367 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined (UEFI) +# include +#endif + +#if defined (BIOS) + +__attribute__((noinline, section(".realmode"))) +noreturn static void spinup(uint8_t drive, void *buf) { + struct idtr real_mode_idt; + real_mode_idt.limit = 0x3ff; + real_mode_idt.ptr = 0; + + asm volatile ( + "cli\n\t" + "cld\n\t" + + // Safe stack location + "mov $0x7c00, %%esp\n\t" + + // move buffer to final location + "mov $0x7c00, %%edi\n\t" + "mov $512, %%ecx\n\t" + "rep movsb\n\t" + + "lidt (%%eax)\n\t" + + "pushl $0x08\n\t" + "pushl $1f\n\t" + "lret\n\t" + "1: .code16\n\t" + "movw $0x10, %%ax\n\t" + "movw %%ax, %%ds\n\t" + "movw %%ax, %%es\n\t" + "movw %%ax, %%fs\n\t" + "movw %%ax, %%gs\n\t" + "movw %%ax, %%ss\n\t" + "movl %%cr0, %%eax\n\t" + "andb $0xfe, %%al\n\t" + "movl %%eax, %%cr0\n\t" + "movl $1f, %%eax\n\t" + "pushw $0\n\t" + "pushw %%ax\n\t" + "lret\n\t" + "1:\n\t" + "xorw %%ax, %%ax\n\t" + "movw %%ax, %%ds\n\t" + "movw %%ax, %%es\n\t" + "movw %%ax, %%fs\n\t" + "movw %%ax, %%gs\n\t" + "movw %%ax, %%ss\n\t" + + "sti\n\t" + + "pushw $0\n\t" + "pushw $0x7c00\n\t" + "lret\n\t" + + ".code32\n\t" + : + : "a" (&real_mode_idt), "d" (drive), "S"(buf) + : "memory" + ); + + __builtin_unreachable(); +} + +noreturn void chainload(char *config, char *cmdline) { + (void)cmdline; + + uint64_t val; + + int part; { + char *part_config = config_get_value(config, 0, "PARTITION"); + if (part_config == NULL) { + part = 0; + } else { + val = strtoui(part_config, NULL, 10); + if (val > 256) { + panic(true, "bios: BIOS partition number outside range 0-256"); + } + part = val; + } + } + int drive; { + char *drive_config = config_get_value(config, 0, "DRIVE"); + if (drive_config == NULL) { + drive = boot_volume->index; + } else { + val = strtoui(drive_config, NULL, 10); + if (val < 1 || val > 256) { + panic(true, "bios: BIOS drive number outside range 1-256"); + } + drive = val; + } + } + + struct volume *p = volume_get_by_coord(false, drive, part); + if (p == NULL && config_get_value(config, 0, "GPT_GUID") == NULL + && config_get_value(config, 0, "GPT_UUID") == NULL + && config_get_value(config, 0, "MBR_ID") == NULL) { + panic(true, "bios: Specified drive/partition not found"); + } + + char *gpt_guid_s = config_get_value(config, 0, "GPT_GUID"); + if (gpt_guid_s == NULL) { + gpt_guid_s = config_get_value(config, 0, "GPT_UUID"); + } + if (gpt_guid_s != NULL) { + struct guid guid; + if (!string_to_guid_be(&guid, gpt_guid_s)) { + panic(true, "bios: Malformed GUID"); + } + + p = volume_get_by_guid(&guid); + if (p == NULL) { + if (!string_to_guid_mixed(&guid, gpt_guid_s)) { + panic(true, "bios: Malformed GUID"); + } + + p = volume_get_by_guid(&guid); + } + + if (p == NULL) { + panic(true, "bios: No matching GPT drive for GPT_GUID found"); + } + + if (p->partition != 0) { + panic(true, "bios: GPT_GUID is that of a partition, not a drive"); + } + + p = volume_get_by_coord(false, p->index, part); + + if (p == NULL) { + panic(true, "bios: Partition specified is not valid"); + } + + goto load; + } + + char *mbr_id_s = config_get_value(config, 0, "MBR_ID"); + if (mbr_id_s != NULL) { + uint32_t mbr_id = strtoui(mbr_id_s, NULL, 16); + + for (size_t i = 0; i < volume_index_i; i++) { + p = volume_index[i]; + + if (!is_valid_mbr(p)) { + continue; + } + + uint32_t mbr_id_1; + if (!volume_read(p, &mbr_id_1, 0x1b8, sizeof(uint32_t))) { + continue; + } + + if (mbr_id_1 == mbr_id) { + p = volume_get_by_coord(false, p->index, part); + + if (p == NULL) { + panic(true, "bios: Partition specified is not valid"); + } + + goto load; + } + } + + panic(true, "bios: No matching MBR ID found"); + } + +load: + vga_textmode_init(false); + + void *buf = ext_mem_alloc(512); + + if (!volume_read(p, buf, 0, 512)) { + panic(true, "bios: Failed to read boot sector"); + } + + uint16_t *boot_sig = (uint16_t *)(buf + 0x1fe); + + if (*boot_sig != 0xaa55) { + panic(true, "bios: Volume is not bootable"); + } + + spinup(p->drive, buf); +} + +#elif defined (UEFI) + +static EFI_DEVICE_PATH_PROTOCOL *build_relative_efi_file_path(struct file_handle *image) { + // The file path stored in EFI_LOADED_IMAGE_PROTOCOL::FilePath is + // expected to be relative to the EFI_LOADED_IMAGE_PROTOCOL::DeviceHandle. + // For this reason the EFI_DEVICE_PATH_PROTOCOL of the efi_part_handle + // is not used as a prefix. This likely also means that the returned + // path cannot be given to gBS->LoadImage() directly. + + size_t original_path_chars = strlen(image->path); + + size_t efi_file_path_alloc_len = (original_path_chars + 1) * sizeof(CHAR16); + CHAR16 *efi_file_path = ext_mem_alloc(efi_file_path_alloc_len); + + bool leading_slash = true; + size_t j = 0; + for (size_t i = 0; i < original_path_chars; i++) { + if (image->path[i] == '/' && leading_slash) { + continue; + } + leading_slash = false; + efi_file_path[j++] = image->path[i] == '/' ? '\\' : image->path[i]; + } + efi_file_path[j] = 0; + + + size_t efi_file_path_len = ((j + 1) * sizeof(CHAR16)); + size_t path_item_len = sizeof(EFI_DEVICE_PATH_PROTOCOL) + efi_file_path_len; + size_t end_item_len = sizeof(EFI_DEVICE_PATH_PROTOCOL); + size_t alloc_len = path_item_len + end_item_len; + + EFI_DEVICE_PATH_PROTOCOL *device_path; + EFI_STATUS status = gBS->AllocatePool(EfiLoaderData, alloc_len, (void **)&device_path); + if (status) { + panic(true, "efi: AllocatePool() failure (%x)", status); + } + + FILEPATH_DEVICE_PATH *path_item = (FILEPATH_DEVICE_PATH *)device_path; + path_item->Header.Type = MEDIA_DEVICE_PATH; + path_item->Header.SubType = MEDIA_FILEPATH_DP; + path_item->Header.Length[0] = path_item_len; + path_item->Header.Length[1] = path_item_len >> 8; + memcpy(&path_item->PathName, efi_file_path, efi_file_path_len); + + EFI_DEVICE_PATH_PROTOCOL *end_item = (void *)device_path + path_item_len; + end_item->Type = END_DEVICE_PATH_TYPE; + end_item->SubType = END_ENTIRE_DEVICE_PATH_SUBTYPE; + end_item->Length[0] = end_item_len; + end_item->Length[1] = end_item_len >> 8; + + pmm_free(efi_file_path, efi_file_path_alloc_len); + return device_path; +} + +noreturn void chainload(char *config, char *cmdline) { + char *image_path = config_get_value(config, 0, "PATH"); + if (image_path == NULL) { + image_path = config_get_value(config, 0, "IMAGE_PATH"); + } + if (image_path == NULL) { + panic(true, "efi: Image path not specified"); + } + + struct file_handle *image; + if ((image = uri_open(image_path)) == NULL) + panic(true, "efi: Failed to open image with path `%s`. Is the path correct?", image_path); + + EFI_STATUS status; + + EFI_HANDLE efi_part_handle = image->efi_part_handle; + + void *ptr = freadall(image, MEMMAP_RESERVED); + size_t image_size = image->size; + + memmap_alloc_range_in(untouched_memmap, &untouched_memmap_entries, + (uintptr_t)ptr, ALIGN_UP(image_size, 4096), + MEMMAP_RESERVED, MEMMAP_USABLE, true, false, true); + + EFI_DEVICE_PATH_PROTOCOL *efi_file_path = build_relative_efi_file_path(image); + + fclose(image); + term_notready(); + + size_t req_width = 0, req_height = 0, req_bpp = 0; + + char *resolution = config_get_value(config, 0, "RESOLUTION"); + if (resolution != NULL) + parse_resolution(&req_width, &req_height, &req_bpp, resolution); + + struct fb_info *fbinfo; + size_t fb_count; + fb_init(&fbinfo, &fb_count, req_width, req_height, req_bpp); + + size_t cmdline_len = strlen(cmdline); + CHAR16 *new_cmdline; + status = gBS->AllocatePool(EfiLoaderData, (cmdline_len + 1) * sizeof(CHAR16), (void **)&new_cmdline); + if (status) { + panic(true, "efi: Allocation failure"); + } + for (size_t i = 0; i < cmdline_len + 1; i++) { + new_cmdline[i] = cmdline[i]; + } + + pmm_release_uefi_mem(); + + MEMMAP_DEVICE_PATH memdev_path[2]; + + memdev_path[0].Header.Type = HARDWARE_DEVICE_PATH; + memdev_path[0].Header.SubType = HW_MEMMAP_DP; + memdev_path[0].Header.Length[0] = sizeof(MEMMAP_DEVICE_PATH); + memdev_path[0].Header.Length[1] = sizeof(MEMMAP_DEVICE_PATH) >> 8; + + memdev_path[0].MemoryType = EfiLoaderCode; + memdev_path[0].StartingAddress = (uintptr_t)ptr; + memdev_path[0].EndingAddress = (uintptr_t)ptr + image_size; + + memdev_path[1].Header.Type = END_DEVICE_PATH_TYPE; + memdev_path[1].Header.SubType = END_ENTIRE_DEVICE_PATH_SUBTYPE; + memdev_path[1].Header.Length[0] = sizeof(EFI_DEVICE_PATH); + memdev_path[1].Header.Length[1] = sizeof(EFI_DEVICE_PATH) >> 8; + + EFI_HANDLE new_handle = 0; + + status = gBS->LoadImage(0, efi_image_handle, + (EFI_DEVICE_PATH *)memdev_path, + ptr, image_size, &new_handle); + if (status) { + panic(false, "efi: LoadImage failure (%X)", (uint64_t)status); + } + + EFI_GUID loaded_img_prot_guid = EFI_LOADED_IMAGE_PROTOCOL_GUID; + + EFI_LOADED_IMAGE_PROTOCOL *new_handle_loaded_image = NULL; + status = gBS->HandleProtocol(new_handle, &loaded_img_prot_guid, + (void **)&new_handle_loaded_image); + if (status) { + panic(false, "efi: HandleProtocol failure (%X)", (uint64_t)status); + } + + if (efi_part_handle != 0) { + new_handle_loaded_image->DeviceHandle = efi_part_handle; + } + + new_handle_loaded_image->FilePath = efi_file_path; + + new_handle_loaded_image->LoadOptionsSize = (cmdline_len + 1) * sizeof(CHAR16); + new_handle_loaded_image->LoadOptions = new_cmdline; + + bli_on_boot(); + + UINTN exit_data_size = 0; + CHAR16 *exit_data = NULL; + EFI_STATUS exit_status = gBS->StartImage(new_handle, &exit_data_size, &exit_data); + + status = gBS->Exit(efi_image_handle, exit_status, exit_data_size, exit_data); + if (status) { + panic(false, "efi: Exit failure (%X)", (uint64_t)status); + } + + __builtin_unreachable(); +} + +#endif diff --git a/limine/common/protos/chainload.h b/limine/common/protos/chainload.h new file mode 100644 index 0000000..93759bc --- /dev/null +++ b/limine/common/protos/chainload.h @@ -0,0 +1,8 @@ +#ifndef PROTOS__CHAINLOAD_H__ +#define PROTOS__CHAINLOAD_H__ + +#include + +noreturn void chainload(char *config, char *cmdline); + +#endif diff --git a/limine/common/protos/limine.c b/limine/common/protos/limine.c new file mode 100644 index 0000000..fc2a220 --- /dev/null +++ b/limine/common/protos/limine.c @@ -0,0 +1,1746 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define LIMINE_NO_POINTERS +#include +#include + +enum executable_format { + EXECUTABLE_FORMAT_ELF, + EXECUTABLE_FORMAT_PE, +}; + +static enum executable_format detect_kernel_format(uint8_t *kernel, size_t kernel_size) { + if (elf_bits(kernel) != -1) { + return EXECUTABLE_FORMAT_ELF; + } else if (pe_bits(kernel, kernel_size) != -1) { + return EXECUTABLE_FORMAT_PE; + } else { + panic(true, "limine: Unknown kernel executable format"); + } +} + +#define SUPPORTED_BASE_REVISION 5 + +#define MAX_REQUESTS 128 + +#define MEMMAP_MAX 1024 + +static int paging_mode; + +static uint64_t get_hhdm_span_top(int base_revision) { + uint64_t ret = base_revision >= 3 ? 0 : 0x100000000; + for (size_t i = 0; i < memmap_entries; i++) { + if (((base_revision >= 1 && base_revision < 3) || base_revision >= 4) && ( + memmap[i].type == MEMMAP_RESERVED + || memmap[i].type == MEMMAP_BAD_MEMORY)) { + continue; + } + + if (base_revision == 3 && ( + memmap[i].type != MEMMAP_USABLE + && memmap[i].type != MEMMAP_BOOTLOADER_RECLAIMABLE + && memmap[i].type != MEMMAP_KERNEL_AND_MODULES + && memmap[i].type != MEMMAP_FRAMEBUFFER + && memmap[i].type != MEMMAP_EFI_RECLAIMABLE)) { + continue; + } + + uint64_t base = memmap[i].base; + uint64_t length = memmap[i].length; + uint64_t top = base + length; + + if (base_revision < 3 && base < 0x100000000) { + base = 0x100000000; + } + + if (base >= top) { + continue; + } + + uint64_t aligned_top = ALIGN_UP(top, 0x40000000); + + if (aligned_top > ret) { + ret = aligned_top; + } + } + + return ret; +} + +#if defined (__i386__) +static pagemap_t build_identity_map(void) { + pagemap_t pagemap = new_pagemap(paging_mode); + + map_pages(pagemap, 0, 0, VMM_FLAG_WRITE, 0x100000000); + + size_t _memmap_entries = memmap_entries; + struct memmap_entry *_memmap = + ext_mem_alloc(_memmap_entries * sizeof(struct memmap_entry)); + for (size_t i = 0; i < _memmap_entries; i++) { + _memmap[i] = memmap[i]; + } + + for (size_t i = 0; i < _memmap_entries; i++) { + if (_memmap[i].type != MEMMAP_USABLE + && _memmap[i].type != MEMMAP_BOOTLOADER_RECLAIMABLE + && _memmap[i].type != MEMMAP_KERNEL_AND_MODULES + && _memmap[i].type != MEMMAP_FRAMEBUFFER + && _memmap[i].type != MEMMAP_EFI_RECLAIMABLE) { + continue; + } + + uint64_t base = _memmap[i].base; + uint64_t length = _memmap[i].length; + uint64_t top = base + length; + + if (base < 0x100000000) { + base = 0x100000000; + } + + if (base >= top) { + continue; + } + + uint64_t aligned_base = ALIGN_DOWN(base, 0x1000); + uint64_t aligned_top = ALIGN_UP(top, 0x1000); + uint64_t aligned_length = aligned_top - aligned_base; + + map_pages(pagemap, aligned_base, aligned_base, VMM_FLAG_WRITE, aligned_length); + } + + return pagemap; +} + +void limine_memcpy_to_64_asm(int paging_mode, void *pagemap, uint64_t dst, void *src, size_t count); + +static void limine_memcpy_to_64(uint64_t dst, void *src, size_t count) { + static bool identity_map_ready = false; + static pagemap_t identity_map; + + if (!identity_map_ready) { + identity_map = build_identity_map(); + identity_map_ready = true; + } + + limine_memcpy_to_64_asm(paging_mode, identity_map.top_level, dst, src, count); +} +#endif + +static pagemap_t build_pagemap(int base_revision, + bool nx, struct mem_range *ranges, size_t ranges_count, + uint64_t physical_base, uint64_t virtual_base, + uint64_t direct_map_offset) { + pagemap_t pagemap = new_pagemap(paging_mode); + + if (ranges_count == 0) { + panic(true, "limine: ranges_count == 0"); + } + + for (size_t i = 0; i < ranges_count; i++) { + uint64_t virt = ranges[i].base; + uint64_t phys; + + if (virt & ((uint64_t)1 << 63)) { + phys = physical_base + (virt - virtual_base); + } else { + panic(false, "limine: Virtual address of a PHDR in lower half"); + } + + uint64_t pf = + (ranges[i].permissions & MEM_RANGE_X ? 0 : (nx ? VMM_FLAG_NOEXEC : 0)) | + (ranges[i].permissions & MEM_RANGE_W ? VMM_FLAG_WRITE : 0); + + map_pages(pagemap, virt, phys, pf, ranges[i].length); + } + + // Map 0x1000->4GiB range to identity if base revision == 0 + if (base_revision == 0) { + map_pages(pagemap, 0x1000, 0x1000, VMM_FLAG_WRITE, 0x100000000 - 0x1000); + } + + // Map 0->4GiB range to HHDM if base revision < 3 + if (base_revision < 3) { + map_pages(pagemap, direct_map_offset, 0, VMM_FLAG_WRITE, 0x100000000); + } + + size_t _memmap_entries = memmap_entries; + struct memmap_entry *_memmap = + ext_mem_alloc(_memmap_entries * sizeof(struct memmap_entry)); + for (size_t i = 0; i < _memmap_entries; i++) + _memmap[i] = memmap[i]; + + // Map all free memory regions to the higher half direct map offset + for (size_t i = 0; i < _memmap_entries; i++) { + if (((base_revision >= 1 && base_revision < 3) || base_revision >= 4) && ( + _memmap[i].type == MEMMAP_RESERVED + || _memmap[i].type == MEMMAP_BAD_MEMORY)) { + continue; + } + + if (base_revision == 3 && ( + _memmap[i].type != MEMMAP_USABLE + && _memmap[i].type != MEMMAP_BOOTLOADER_RECLAIMABLE + && _memmap[i].type != MEMMAP_KERNEL_AND_MODULES + && _memmap[i].type != MEMMAP_FRAMEBUFFER + && _memmap[i].type != MEMMAP_EFI_RECLAIMABLE)) { + continue; + } + + uint64_t base = _memmap[i].base; + uint64_t length = _memmap[i].length; + uint64_t top = base + length; + + if (base_revision < 3 && base < 0x100000000) { + base = 0x100000000; + } + + if (base >= top) { + continue; + } + + uint64_t aligned_base = ALIGN_DOWN(base, 0x1000); + uint64_t aligned_top = ALIGN_UP(top, 0x1000); + uint64_t aligned_length = aligned_top - aligned_base; + + if (base_revision == 0) { + map_pages(pagemap, aligned_base, aligned_base, VMM_FLAG_WRITE, aligned_length); + } + map_pages(pagemap, direct_map_offset + aligned_base, aligned_base, VMM_FLAG_WRITE, aligned_length); + } + + // Map the framebuffer with appropriate permissions + for (size_t i = 0; i < _memmap_entries; i++) { + if (_memmap[i].type != MEMMAP_FRAMEBUFFER) { + continue; + } + + uint64_t base = _memmap[i].base; + uint64_t length = _memmap[i].length; + uint64_t top = base + length; + + uint64_t aligned_base = ALIGN_DOWN(base, 0x1000); + uint64_t aligned_top = ALIGN_UP(top, 0x1000); + uint64_t aligned_length = aligned_top - aligned_base; + + if (base_revision == 0) { + map_pages(pagemap, aligned_base, aligned_base, VMM_FLAG_WRITE | VMM_FLAG_FB, aligned_length); + } + map_pages(pagemap, direct_map_offset + aligned_base, aligned_base, VMM_FLAG_WRITE | VMM_FLAG_FB, aligned_length); + } + + // XXX we do this as a quick and dirty way to switch to the higher half +#if defined (__x86_64__) || defined (__i386__) + if (base_revision >= 1) { + map_pages(pagemap, 0, 0, VMM_FLAG_WRITE, 0x100000000); + } +#endif + + return pagemap; +} + +#if defined (__x86_64__) || defined (__i386__) +extern symbol limine_spinup_32; +#elif defined (__aarch64__) + +#define LIMINE_SCTLR ((1 << 29) /* Res1 */ \ + | (1 << 28) /* Res1 */ \ + | (1 << 23) /* Res1 */ \ + | (1 << 22) /* Res1 */ \ + | (1 << 20) /* Res1 */ \ + | (1 << 12) /* I-Cache */ \ + | (1 << 11) /* Res1 */ \ + | (1 << 8) /* Res1 */ \ + | (1 << 7) /* Res1 */ \ + | (1 << 4) /* SP0 Alignment check */ \ + | (1 << 3) /* SP Alignment check */ \ + | (1 << 2) /* D-Cache */ \ + | (1 << 0)) /* MMU */ \ + +#define LIMINE_MAIR(fb) ( ((uint64_t)0b11111111 << 0) /* Normal WB RW-allocate non-transient */ \ + | ((uint64_t)(fb) << 8) ) /* Framebuffer type */ + +#define LIMINE_TCR(tsz, pa) ( ((uint64_t)(pa) << 32) /* Intermediate address size */ \ + | ((uint64_t)2 << 30) /* TTBR1 4K granule */ \ + | ((uint64_t)2 << 28) /* TTBR1 Outer shareable */ \ + | ((uint64_t)1 << 26) /* TTBR1 Outer WB RW-Allocate */ \ + | ((uint64_t)1 << 24) /* TTBR1 Inner WB RW-Allocate */ \ + | ((uint64_t)(tsz) << 16) /* Address bits in TTBR1 */ \ + /* TTBR0 4K granule */ \ + | ((uint64_t)2 << 12) /* TTBR0 Outer shareable */ \ + | ((uint64_t)1 << 10) /* TTBR0 Outer WB RW-Allocate */ \ + | ((uint64_t)1 << 8) /* TTBR0 Inner WB RW-Allocate */ \ + | ((uint64_t)(tsz) << 0)) /* Address bits in TTBR0 */ + +#elif defined (__riscv) +#elif defined (__loongarch64) +#else +#error Unknown architecture +#endif + +static uint64_t physical_base, virtual_base, slide, direct_map_offset; +static size_t requests_count; +static void **requests; + +static void set_paging_mode(bool randomise_hhdm_base) { + direct_map_offset = paging_mode_higher_half(paging_mode); + if (randomise_hhdm_base) { + // A quarter of the higher half of wiggle room for KASLR, align to 1GiB steps. + uint64_t mask = ((uint64_t)1 << (paging_mode_va_bits(paging_mode) - 3)) - 1; + direct_map_offset += (rand64() & ~((uint64_t)0x40000000 - 1)) & mask; + } +} + +static uint64_t reported_addr(void *addr) { + return (uint64_t)(uintptr_t)addr + direct_map_offset; +} + +#if defined (__i386__) +static uint64_t reported_addr_64(uint64_t addr) { + return addr + direct_map_offset; +} +#endif + +#define get_phys_addr(addr) ({ \ + __auto_type get_phys_addr__addr = (addr); \ + uintptr_t get_phys_addr__r; \ + if (get_phys_addr__addr & ((uint64_t)1 << 63)) { \ + get_phys_addr__r = physical_base + (get_phys_addr__addr - virtual_base); \ + } else { \ + get_phys_addr__r = get_phys_addr__addr; \ + } \ + get_phys_addr__r; \ +}) + +static struct limine_file get_file(struct file_handle *file, char *cmdline, bool kernel) { + struct limine_file ret = {0}; + + if (file->pxe) { + ret.media_type = LIMINE_MEDIA_TYPE_TFTP; + + ret.tftp_ip = file->pxe_ip; + ret.tftp_port = file->pxe_port; + } else { + struct volume *vol = file->vol; + + if (vol->is_optical) { + ret.media_type = LIMINE_MEDIA_TYPE_OPTICAL; + } + + ret.partition_index = vol->partition; + + ret.mbr_disk_id = mbr_get_id(vol->backing_dev ?: vol); + + if (vol->guid_valid) { + memcpy(&ret.part_uuid, &vol->guid, sizeof(struct limine_uuid)); + } + + if (vol->part_guid_valid) { + memcpy(&ret.gpt_part_uuid, &vol->part_guid, sizeof(struct limine_uuid)); + } + + struct guid gpt_disk_uuid; + if (gpt_get_guid(&gpt_disk_uuid, vol->backing_dev ?: vol) == true) { + memcpy(&ret.gpt_disk_uuid, &gpt_disk_uuid, sizeof(struct limine_uuid)); + } + } + + char *path = ext_mem_alloc(file->path_len); + memcpy(path, file->path, file->path_len); + + ret.path = reported_addr(path); + + void *freadall_ret = freadall_mode(file, MEMMAP_KERNEL_AND_MODULES, !kernel +#if defined (__i386__) + , limine_memcpy_to_64 +#endif + ); +#if defined (__i386__) + ret.address = kernel ? reported_addr(freadall_ret) : reported_addr_64(*(uint64_t *)freadall_ret); +#else + ret.address = reported_addr(freadall_ret); +#endif + + ret.size = file->size; + + ret.string = reported_addr(cmdline); + + return ret; +} + +static void *_get_request(uint64_t id[4]) { + for (size_t i = 0; i < requests_count; i++) { + uint64_t *p = requests[i]; + + if (p[2] != id[2]) { + continue; + } + if (p[3] != id[3]) { + continue; + } + + return p; + } + + return NULL; +} + +#define get_request(REQ) _get_request((uint64_t[4])REQ) + +#define FEAT_START do { +#define FEAT_END } while (0); + +noreturn void limine_load(char *config, char *cmdline) { +#if defined (__x86_64__) || defined (__i386__) + uint32_t eax, ebx, ecx, edx; +#endif + + char *kernel_path = config_get_value(config, 0, "PATH"); + if (kernel_path == NULL) { + kernel_path = config_get_value(config, 0, "KERNEL_PATH"); + } + if (kernel_path == NULL) { + panic(true, "limine: Executable path not specified"); + } + + print("limine: Loading executable `%#`...\n", kernel_path); + + struct file_handle *kernel_file; + if ((kernel_file = uri_open(kernel_path)) == NULL) + panic(true, "limine: Failed to open executable with path `%#`. Is the path correct?", kernel_path); + + char *k_path_copy = ext_mem_alloc(strlen(kernel_path) + 1); + strcpy(k_path_copy, kernel_path); + char *k_resource = NULL, *k_root = NULL, *k_path = NULL, *k_hash = NULL; + uri_resolve(k_path_copy, &k_resource, &k_root, &k_path, &k_hash); + // Copy k_resource and k_root since uri_resolve returns pointers to a static + // buffer that gets overwritten by subsequent uri_open/uri_resolve calls + k_resource = strdup(k_resource); + k_root = strdup(k_root); + char *k_path_ = ext_mem_alloc(strlen(k_path) + 2); + k_path_[0] = '/'; + strcpy(k_path_ + 1, k_path); + k_path = k_path_; + for (size_t i = strlen(k_path) - 1; ; i--) { + if (k_path[i] == '/' || i == 1) { + k_path[i] = 0; + break; + } + k_path[i] = 0; + } + + uint8_t *kernel = freadall(kernel_file, MEMMAP_BOOTLOADER_RECLAIMABLE); + + char *kaslr_s = config_get_value(config, 0, "KASLR"); + bool kaslr = false; + if (kaslr_s != NULL && strcmp(kaslr_s, "yes") == 0) { + kaslr = true; + } + + // ELF loading + uint64_t entry_point = 0; + struct mem_range *ranges; + uint64_t ranges_count; + + uint64_t image_size_before_bss; + bool is_reloc; + + enum executable_format kernel_format = detect_kernel_format(kernel, kernel_file->size); + switch (kernel_format) { + case EXECUTABLE_FORMAT_ELF: + if (!elf64_load(kernel, kernel_file->size, &entry_point, &slide, + MEMMAP_KERNEL_AND_MODULES, kaslr, + &ranges, &ranges_count, + &physical_base, &virtual_base, NULL, + &image_size_before_bss, + &is_reloc)) { + panic(true, "limine: ELF64 load failure"); + } + break; + case EXECUTABLE_FORMAT_PE: + if (!pe64_load(kernel, kernel_file->size, &entry_point, &slide, + MEMMAP_KERNEL_AND_MODULES, kaslr, + &ranges, &ranges_count, + &physical_base, &virtual_base, NULL, + &image_size_before_bss, + &is_reloc)) { + panic(true, "limine: PE64 load failure"); + } + break; + } + + kaslr = kaslr && is_reloc; + + uint64_t limine_requests_start_marker[] = LIMINE_REQUESTS_START_MARKER; + uint64_t limine_requests_end_marker[] = LIMINE_REQUESTS_END_MARKER; + + // Determine base revision + uint64_t limine_base_revision[] = LIMINE_BASE_REVISION(0); + int base_revision = 0; + bool base_revision_found = false; + uint64_t *base_rev_p1_ptr = NULL; + uint64_t *base_rev_p2_ptr = NULL; + for (size_t i = 0; i < ALIGN_DOWN(image_size_before_bss, 8); i += 8) { + uint64_t *p = (void *)(uintptr_t)physical_base + i; + + // Check if start marker hit + if (p[0] == limine_requests_start_marker[0] && p[1] == limine_requests_start_marker[1] + && p[2] == limine_requests_start_marker[2] && p[3] == limine_requests_start_marker[3]) { + base_revision = 0; + base_revision_found = false; + base_rev_p1_ptr = NULL; + base_rev_p2_ptr = NULL; + continue; + } + + // Check if end marker hit + if (p[0] == limine_requests_end_marker[0] && p[1] == limine_requests_end_marker[1]) { + break; + } + + if (p[0] == limine_base_revision[0] && p[1] == limine_base_revision[1]) { + if (base_revision_found) { + panic(true, "limine: Duplicated base revision tag"); + } + base_revision_found = true; + base_revision = p[2]; + if (p[2] <= SUPPORTED_BASE_REVISION) { + // Set to 0 to mean "supported" + base_rev_p2_ptr = &p[2]; + } else { + base_revision = SUPPORTED_BASE_REVISION; + } + base_rev_p1_ptr = &p[1]; + } + } + if (base_rev_p1_ptr != NULL) { + *base_rev_p1_ptr = base_revision; + } + if (base_rev_p2_ptr != NULL) { + *base_rev_p2_ptr = 0; + } + + // Load requests + uint64_t *limine_reqs = NULL; + requests = ext_mem_alloc(MAX_REQUESTS * sizeof(void *)); + requests_count = 0; + if (base_revision == 0 && kernel_format == EXECUTABLE_FORMAT_ELF && elf64_load_section(kernel, kernel_file->size, &limine_reqs, ".limine_reqs", 0, slide)) { + for (size_t i = 0; ; i++) { + if (i >= MAX_REQUESTS) { + panic(true, "limine: Maximum requests exceeded"); + } + if (limine_reqs[i] == 0) { + break; + } + requests[i] = (void *)(uintptr_t)((limine_reqs[i] - virtual_base) + physical_base); + requests_count++; + } + } else { + uint64_t common_magic[2] = { LIMINE_COMMON_MAGIC }; + for (size_t i = 0; i < ALIGN_DOWN(image_size_before_bss, 8); i += 8) { + uint64_t *p = (void *)(uintptr_t)physical_base + i; + + // Check if start marker hit + if (p[0] == limine_requests_start_marker[0] && p[1] == limine_requests_start_marker[1] + && p[2] == limine_requests_start_marker[2] && p[3] == limine_requests_start_marker[3]) { + requests_count = 0; + continue; + } + + // Check if end marker hit + if (p[0] == limine_requests_end_marker[0] && p[1] == limine_requests_end_marker[1]) { + break; + } + + if (p[0] != common_magic[0]) { + continue; + } + if (p[1] != common_magic[1]) { + continue; + } + + if (requests_count == MAX_REQUESTS) { + panic(true, "limine: Maximum requests exceeded"); + } + + // Check for a conflict + if (_get_request(p) != NULL) { + panic(true, "limine: Conflict detected for request ID %X %X", p[2], p[3]); + } + + requests[requests_count++] = p; + } + } + +#if defined (__x86_64__) || defined (__i386__) + // Check if 64 bit CPU + if (!cpuid(0x80000001, 0, &eax, &ebx, &ecx, &edx) || !(edx & (1 << 29))) { + panic(true, "limine: This CPU does not support 64-bit mode."); + } +#endif + + uint64_t hhdm_span_top = get_hhdm_span_top(base_revision); + +#if defined (__x86_64__) || defined (__i386__) + uint64_t maxphyaddr; + if (!cpuid(0x80000008, 0, &eax, &ebx, &ecx, &edx)) { + maxphyaddr = 36; + } else { + maxphyaddr = eax & 0xff; + } + + if (maxphyaddr > 64) { + panic(true, "limine: MAXPHYADDR > 64"); + } + if (maxphyaddr < 64 && hhdm_span_top > (uint64_t)1 << maxphyaddr) { + panic(true, "limine: Top of HHDM exceeds maximum allowable MAXPHYADDR value"); + } +#endif + + printv("limine: Physical base: %X\n", physical_base); + printv("limine: Virtual base: %X\n", virtual_base); + printv("limine: Slide: %X\n", slide); + printv("limine: ELF entry point: %X\n", entry_point); + printv("limine: Base revision: %u\n", base_revision); + printv("limine: Requests count: %U\n", (uint64_t)requests_count); + printv("limine: Top of HHDM: %X\n", hhdm_span_top); + + // Paging Mode + int max_supported_paging_mode, min_supported_paging_mode; + +#if defined (__x86_64__) || defined (__i386__) + max_supported_paging_mode = PAGING_MODE_X86_64_4LVL; + if (cpuid(0x00000007, 0, &eax, &ebx, &ecx, &edx) && (ecx & (1 << 16))) { + printv("limine: CPU has 5-level paging support\n"); + max_supported_paging_mode = PAGING_MODE_X86_64_5LVL; + } + min_supported_paging_mode = PAGING_MODE_X86_64_4LVL; + if (hhdm_span_top >= (uint64_t)1 << (paging_mode_va_bits(min_supported_paging_mode) - 2)) { + min_supported_paging_mode = PAGING_MODE_X86_64_5LVL; + if (min_supported_paging_mode > max_supported_paging_mode) { + goto hhdm_fail; + } + } + if (hhdm_span_top >= (uint64_t)1 << (paging_mode_va_bits(min_supported_paging_mode) - 2)) { + goto hhdm_fail; + } +#elif defined (__aarch64__) + max_supported_paging_mode = PAGING_MODE_AARCH64_4LVL; + min_supported_paging_mode = PAGING_MODE_AARCH64_4LVL; + if (hhdm_span_top >= (uint64_t)1 << (paging_mode_va_bits(min_supported_paging_mode) - 2)) { + goto hhdm_fail; + } + // TODO(qookie): aarch64 also has optional 5 level paging when using 4K pages +#elif defined (__riscv) + max_supported_paging_mode = vmm_max_paging_mode(); + min_supported_paging_mode = PAGING_MODE_RISCV_SV39; + if (hhdm_span_top >= (uint64_t)1 << (paging_mode_va_bits(min_supported_paging_mode) - 2)) { + min_supported_paging_mode = PAGING_MODE_RISCV_SV48; + if (min_supported_paging_mode > max_supported_paging_mode) { + goto hhdm_fail; + } + } + if (hhdm_span_top >= (uint64_t)1 << (paging_mode_va_bits(min_supported_paging_mode) - 2)) { + min_supported_paging_mode = PAGING_MODE_RISCV_SV57; + if (min_supported_paging_mode > max_supported_paging_mode) { + goto hhdm_fail; + } + } + if (hhdm_span_top >= (uint64_t)1 << (paging_mode_va_bits(min_supported_paging_mode) - 2)) { + goto hhdm_fail; + } +#elif defined (__loongarch64) + max_supported_paging_mode = PAGING_MODE_LOONGARCH64_4LVL; + min_supported_paging_mode = PAGING_MODE_LOONGARCH64_4LVL; + if (hhdm_span_top >= (uint64_t)1 << (paging_mode_va_bits(min_supported_paging_mode) - 2)) { + goto hhdm_fail; + } +#else +#error Unknown architecture +#endif + + if (0) { +hhdm_fail: + panic(true, "limine: Unable to allocate higher half direct map (too much memory?)"); + } + + char *user_paging_mode_s = config_get_value(config, 0, "PAGING_MODE"); + + int user_max_paging_mode = PAGING_MODE_MAX; + + char *user_max_paging_mode_s; + if (user_paging_mode_s != NULL) { + user_max_paging_mode_s = user_paging_mode_s; + } else { + user_max_paging_mode_s = config_get_value(config, 0, "MAX_PAGING_MODE"); + } + if (user_max_paging_mode_s != NULL) { +#if defined (__x86_64__) || defined (__i386__) + if (strcasecmp(user_max_paging_mode_s, "4level") == 0) { + user_max_paging_mode = PAGING_MODE_X86_64_4LVL; + } else if (strcasecmp(user_max_paging_mode_s, "5level") == 0) { + user_max_paging_mode = PAGING_MODE_X86_64_5LVL; + } +#elif defined (__aarch64__) + if (strcasecmp(user_max_paging_mode_s, "4level") == 0) { + user_max_paging_mode = PAGING_MODE_AARCH64_4LVL; + } else if (strcasecmp(user_max_paging_mode_s, "5level") == 0) { + user_max_paging_mode = PAGING_MODE_AARCH64_5LVL; + } +#elif defined (__riscv) + if (strcasecmp(user_max_paging_mode_s, "sv39") == 0) { + user_max_paging_mode = PAGING_MODE_RISCV_SV39; + } else if (strcasecmp(user_max_paging_mode_s, "sv48") == 0) { + user_max_paging_mode = PAGING_MODE_RISCV_SV48; + } else if (strcasecmp(user_max_paging_mode_s, "sv57") == 0) { + user_max_paging_mode = PAGING_MODE_RISCV_SV57; + } +#elif defined (__loongarch64) + if (strcasecmp(user_max_paging_mode_s, "4level") == 0) { + user_max_paging_mode = PAGING_MODE_LOONGARCH64_4LVL; + } +#endif + else { + panic(true, "limine: Invalid MAX_PAGING_MODE: `%s`", user_max_paging_mode_s); + } + } + + int user_min_paging_mode = PAGING_MODE_MIN; + + char *user_min_paging_mode_s; + if (user_paging_mode_s != NULL) { + user_min_paging_mode_s = user_paging_mode_s; + } else { + user_min_paging_mode_s = config_get_value(config, 0, "MIN_PAGING_MODE"); + } + if (user_min_paging_mode_s != NULL) { +#if defined (__x86_64__) || defined (__i386__) + if (strcasecmp(user_min_paging_mode_s, "4level") == 0) { + user_min_paging_mode = PAGING_MODE_X86_64_4LVL; + } else if (strcasecmp(user_min_paging_mode_s, "5level") == 0) { + user_min_paging_mode = PAGING_MODE_X86_64_5LVL; + } +#elif defined (__aarch64__) + if (strcasecmp(user_min_paging_mode_s, "4level") == 0) { + user_min_paging_mode = PAGING_MODE_AARCH64_4LVL; + } else if (strcasecmp(user_min_paging_mode_s, "5level") == 0) { + user_min_paging_mode = PAGING_MODE_AARCH64_5LVL; + } +#elif defined (__riscv) + if (strcasecmp(user_min_paging_mode_s, "sv39") == 0) { + user_min_paging_mode = PAGING_MODE_RISCV_SV39; + } else if (strcasecmp(user_min_paging_mode_s, "sv48") == 0) { + user_min_paging_mode = PAGING_MODE_RISCV_SV48; + } else if (strcasecmp(user_min_paging_mode_s, "sv57") == 0) { + user_min_paging_mode = PAGING_MODE_RISCV_SV57; + } +#elif defined (__loongarch64) + if (strcasecmp(user_min_paging_mode_s, "4level") == 0) { + user_min_paging_mode = PAGING_MODE_LOONGARCH64_4LVL; + } +#endif + else { + panic(true, "limine: Invalid MIN_PAGING_MODE: `%s`", user_min_paging_mode_s); + } + } + + if (user_max_paging_mode < user_min_paging_mode) { + panic(true, "limine: MAX_PAGING_MODE is lower than MIN_PAGING_MODE"); + } + + if (user_max_paging_mode < max_supported_paging_mode) { + if (user_max_paging_mode < min_supported_paging_mode) { + panic(true, "limine: User set MAX_PAGING_MODE less than minimum supported paging mode"); + } + max_supported_paging_mode = user_max_paging_mode; + } + if (user_min_paging_mode > min_supported_paging_mode) { + if (user_min_paging_mode > max_supported_paging_mode) { + panic(true, "limine: User set MIN_PAGING_MODE greater than maximum supported paging mode"); + } + min_supported_paging_mode = user_min_paging_mode; + } + +#if defined (__x86_64__) || defined (__i386__) + paging_mode = PAGING_MODE_X86_64_4LVL; +#elif defined (__riscv) + paging_mode = max_supported_paging_mode >= PAGING_MODE_RISCV_SV48 ? PAGING_MODE_RISCV_SV48 : PAGING_MODE_RISCV_SV39; +#elif defined (__aarch64__) + paging_mode = PAGING_MODE_AARCH64_4LVL; +#elif defined (__loongarch64) + paging_mode = PAGING_MODE_LOONGARCH64_4LVL; +#endif + +#if defined (__riscv) +#define paging_mode_limine_to_vmm(x) (PAGING_MODE_RISCV_SV39 + (x)) +#define paging_mode_vmm_to_limine(x) ((x) - PAGING_MODE_RISCV_SV39) +#else +#define paging_mode_limine_to_vmm(x) (x) +#define paging_mode_vmm_to_limine(x) (x) +#endif + + bool paging_mode_set = false; + bool randomise_hhdm_base = false; +FEAT_START + struct limine_paging_mode_request *pm_request = get_request(LIMINE_PAGING_MODE_REQUEST_ID); + if (pm_request == NULL) + break; + + uint64_t target_mode = pm_request->mode; + paging_mode = paging_mode_limine_to_vmm(target_mode); + + int kern_min_mode = PAGING_MODE_MIN, kern_max_mode = paging_mode; + if (pm_request->revision >= 1) { + kern_min_mode = (int)paging_mode_limine_to_vmm(pm_request->min_mode); + kern_max_mode = (int)paging_mode_limine_to_vmm(pm_request->max_mode); + } + + if (paging_mode > max_supported_paging_mode) { + paging_mode = max_supported_paging_mode; + } + if (paging_mode < min_supported_paging_mode) { + paging_mode = min_supported_paging_mode; + } + + if (kern_max_mode < kern_min_mode) { + panic(true, "limine: Executable's paging max_mode lower than min_mode"); + } + + if (paging_mode > kern_max_mode) { + if (kern_max_mode < min_supported_paging_mode) { + panic(true, "limine: Executable's maximum supported paging mode lower than minimum allowable paging mode"); + } + paging_mode = kern_max_mode; + } + if (paging_mode < kern_min_mode) { + if (kern_min_mode > max_supported_paging_mode) { + panic(true, "limine: Executable's minimum supported paging mode higher than maximum allowable paging mode"); + } + paging_mode = kern_min_mode; + } + + char *randomise_hhdm_base_s = config_get_value(config, 0, "RANDOMISE_HHDM_BASE"); + if (randomise_hhdm_base_s == NULL) { + randomise_hhdm_base_s = config_get_value(config, 0, "RANDOMIZE_HHDM_BASE"); + } + if (randomise_hhdm_base_s == NULL) { + randomise_hhdm_base = kaslr; + } else { + randomise_hhdm_base = strcasecmp(randomise_hhdm_base_s, "yes") == 0; + } + + set_paging_mode(randomise_hhdm_base); + paging_mode_set = true; + + struct limine_paging_mode_response *pm_response = + ext_mem_alloc(sizeof(struct limine_paging_mode_response)); + + pm_response->mode = paging_mode_vmm_to_limine(paging_mode); + pm_request->response = reported_addr(pm_response); +FEAT_END + + if (!paging_mode_set) { + set_paging_mode(randomise_hhdm_base); + } + +#if defined (__aarch64__) + uint64_t aa64mmfr0; + asm volatile ("mrs %0, id_aa64mmfr0_el1" : "=r" (aa64mmfr0)); + + uint64_t pa = aa64mmfr0 & 0xF; + + uint64_t tsz = 64 - (paging_mode_va_bits(paging_mode) - 1); +#endif + + struct limine_file *kf = ext_mem_alloc(sizeof(struct limine_file)); + *kf = get_file(kernel_file, cmdline, true); + fclose(kernel_file); + + // Entry point feature +FEAT_START + struct limine_entry_point_request *entrypoint_request = get_request(LIMINE_ENTRY_POINT_REQUEST_ID); + if (entrypoint_request == NULL) { + break; + } + + entry_point = entrypoint_request->entry; + + printv("limine: Entry point at %X\n", entry_point); + + struct limine_entry_point_response *entrypoint_response = + ext_mem_alloc(sizeof(struct limine_entry_point_response)); + + entrypoint_request->response = reported_addr(entrypoint_response); +FEAT_END + + // x86-64 Keep IOMMU feature +#if defined (__x86_64__) || defined (__i386__) + bool keep_iommu = false; +FEAT_START + struct limine_x86_64_keep_iommu_request *keep_iommu_request = + get_request(LIMINE_X86_64_KEEP_IOMMU_REQUEST_ID); + if (keep_iommu_request == NULL) { + break; + } + + struct limine_x86_64_keep_iommu_response *keep_iommu_response = + ext_mem_alloc(sizeof(struct limine_x86_64_keep_iommu_response)); + + keep_iommu_request->response = reported_addr(keep_iommu_response); + keep_iommu = true; +FEAT_END +#endif + + // Bootloader info feature +FEAT_START + struct limine_bootloader_info_request *bootloader_info_request = get_request(LIMINE_BOOTLOADER_INFO_REQUEST_ID); + if (bootloader_info_request == NULL) { + break; // next feature + } + + struct limine_bootloader_info_response *bootloader_info_response = + ext_mem_alloc(sizeof(struct limine_bootloader_info_response)); + + bootloader_info_response->name = reported_addr("Limine"); + bootloader_info_response->version = reported_addr(LIMINE_VERSION); + + bootloader_info_request->response = reported_addr(bootloader_info_response); +FEAT_END + + // Executable Command Line feature +FEAT_START + struct limine_executable_cmdline_request *executable_cmdline_request = get_request(LIMINE_EXECUTABLE_CMDLINE_REQUEST_ID); + if (executable_cmdline_request == NULL) { + break; // next feature + } + + struct limine_executable_cmdline_response *executable_cmdline_response = + ext_mem_alloc(sizeof(struct limine_executable_cmdline_response)); + + executable_cmdline_response->cmdline = reported_addr(cmdline); + + executable_cmdline_request->response = reported_addr(executable_cmdline_response); +FEAT_END + + // Firmware type feature +FEAT_START + struct limine_firmware_type_request *firmware_type_request = get_request(LIMINE_FIRMWARE_TYPE_REQUEST_ID); + if (firmware_type_request == NULL) { + break; // next feature + } + + struct limine_firmware_type_response *firmware_type_response = + ext_mem_alloc(sizeof(struct limine_firmware_type_response)); + + firmware_type_response->firmware_type = +#if defined (UEFI) +#if defined (__i386__) + LIMINE_FIRMWARE_TYPE_EFI32 +#else + LIMINE_FIRMWARE_TYPE_EFI64 +#endif +#else + LIMINE_FIRMWARE_TYPE_X86BIOS +#endif + ; + + firmware_type_request->response = reported_addr(firmware_type_response); +FEAT_END + + // Executable address feature +FEAT_START + struct limine_executable_address_request *executable_address_request = get_request(LIMINE_EXECUTABLE_ADDRESS_REQUEST_ID); + if (executable_address_request == NULL) { + break; // next feature + } + + struct limine_executable_address_response *executable_address_response = + ext_mem_alloc(sizeof(struct limine_executable_address_response)); + + executable_address_response->physical_base = physical_base; + executable_address_response->virtual_base = virtual_base; + + executable_address_request->response = reported_addr(executable_address_response); +FEAT_END + + // HHDM feature +FEAT_START + struct limine_hhdm_request *hhdm_request = get_request(LIMINE_HHDM_REQUEST_ID); + if (hhdm_request == NULL) { + break; // next feature + } + + struct limine_hhdm_response *hhdm_response = + ext_mem_alloc(sizeof(struct limine_hhdm_response)); + + hhdm_response->offset = direct_map_offset; + + hhdm_request->response = reported_addr(hhdm_response); +FEAT_END + + // RSDP feature +FEAT_START + struct limine_rsdp_request *rsdp_request = get_request(LIMINE_RSDP_REQUEST_ID); + if (rsdp_request == NULL) { + break; // next feature + } + + void *rsdp = acpi_get_rsdp(); + if (rsdp == NULL) { + break; + } + + struct limine_rsdp_response *rsdp_response = + ext_mem_alloc(sizeof(struct limine_rsdp_response)); + + rsdp_response->address = (base_revision <= 2 || base_revision >= 4) ? reported_addr(rsdp) : (uintptr_t)rsdp; + + rsdp_request->response = reported_addr(rsdp_response); +FEAT_END + + // SMBIOS feature +FEAT_START + struct limine_smbios_request *smbios_request = get_request(LIMINE_SMBIOS_REQUEST_ID); + if (smbios_request == NULL) { + break; // next feature + } + + void *smbios_entry_32 = NULL, *smbios_entry_64 = NULL; + acpi_get_smbios(&smbios_entry_32, &smbios_entry_64); + if (smbios_entry_32 == NULL && smbios_entry_64 == NULL) { + break; + } + + struct limine_smbios_response *smbios_response = + ext_mem_alloc(sizeof(struct limine_smbios_response)); + + if (smbios_entry_32) { + smbios_response->entry_32 = (base_revision <= 2 || base_revision >= 5) ? reported_addr(smbios_entry_32) : (uintptr_t)smbios_entry_32; + } + if (smbios_entry_64) { + smbios_response->entry_64 = (base_revision <= 2 || base_revision >= 5) ? reported_addr(smbios_entry_64) : (uintptr_t)smbios_entry_64; + } + + smbios_request->response = reported_addr(smbios_response); +FEAT_END + +#if defined (UEFI) + // EFI system table feature +FEAT_START + struct limine_efi_system_table_request *est_request = get_request(LIMINE_EFI_SYSTEM_TABLE_REQUEST_ID); + if (est_request == NULL) { + break; // next feature + } + + struct limine_efi_system_table_response *est_response = + ext_mem_alloc(sizeof(struct limine_efi_system_table_response)); + + est_response->address = (base_revision <= 2 || base_revision >= 5) ? reported_addr(gST) : (uintptr_t)gST; + + est_request->response = reported_addr(est_response); +FEAT_END +#endif + + // Device tree blob feature +FEAT_START + struct limine_dtb_request *dtb_request = get_request(LIMINE_DTB_REQUEST_ID); + if (dtb_request == NULL) { + break; // next feature + } + + void *dtb = get_device_tree_blob(config, 0); + + if (dtb) { + // Delete all /memory@... nodes. + // The executable must use the given UEFI memory map instead. + while (true) { + int offset = fdt_subnode_offset_namelen(dtb, 0, "memory@", 7); + + if (offset == -FDT_ERR_NOTFOUND) { + break; + } + + if (offset < 0) { + panic(true, "limine: failed to find node: '%s'", fdt_strerror(offset)); + } + + int ret = fdt_del_node(dtb, offset); + if (ret < 0) { + panic(true, "limine: failed to delete memory node: '%s'", fdt_strerror(ret)); + } + } + + struct limine_dtb_response *dtb_response = + ext_mem_alloc(sizeof(struct limine_dtb_response)); + dtb_response->dtb_ptr = reported_addr(dtb); + dtb_request->response = reported_addr(dtb_response); + } +FEAT_END + + // Stack size + uint64_t stack_size = 65536; +FEAT_START + struct limine_stack_size_request *stack_size_request = get_request(LIMINE_STACK_SIZE_REQUEST_ID); + if (stack_size_request == NULL) { + break; // next feature + } + + struct limine_stack_size_response *stack_size_response = + ext_mem_alloc(sizeof(struct limine_stack_size_response)); + + if (stack_size_request->stack_size > stack_size) { + stack_size = stack_size_request->stack_size; + } + + stack_size_request->response = reported_addr(stack_size_response); +FEAT_END + + // Executable file +FEAT_START + struct limine_executable_file_request *executable_file_request = get_request(LIMINE_EXECUTABLE_FILE_REQUEST_ID); + if (executable_file_request == NULL) { + break; // next feature + } + + struct limine_executable_file_response *executable_file_response = + ext_mem_alloc(sizeof(struct limine_executable_file_response)); + + executable_file_response->executable_file = reported_addr(kf); + + executable_file_request->response = reported_addr(executable_file_response); +FEAT_END + + // Modules +FEAT_START + struct limine_module_request *module_request = get_request(LIMINE_MODULE_REQUEST_ID); + if (module_request == NULL) { + break; // next feature + } + + size_t module_count; + for (module_count = 0; ; module_count++) { + char *module_file = config_get_value(config, module_count, "MODULE_PATH"); + if (module_file == NULL) + break; + } + + if (module_request->revision >= 1) { + module_count += module_request->internal_module_count; + } + + if (module_count == 0) { + break; + } + + struct limine_module_response *module_response = + ext_mem_alloc(sizeof(struct limine_module_response)); + + module_response->revision = 2; + + struct limine_file *modules = ext_mem_alloc(module_count * sizeof(struct limine_file)); + + size_t final_module_count = 0; + for (size_t i = 0; i < module_count; i++) { + char *module_path; + char *module_cmdline; + bool module_required = true; + bool module_path_allocated = false; + + if (module_request->revision >= 1 && i < module_request->internal_module_count) { + uint64_t *internal_modules = (void *)get_phys_addr(module_request->internal_modules); + struct limine_internal_module *internal_module = (void *)get_phys_addr(internal_modules[i]); + + module_path = (char *)get_phys_addr(internal_module->path); + module_cmdline = (char *)get_phys_addr(internal_module->string); + + if (internal_module->flags & LIMINE_INTERNAL_MODULE_COMPRESSED) { + panic(true, "limine: Compressed internal modules no longer supported"); + } + + // Validate path length to prevent buffer overflow + size_t k_resource_len = strlen(k_resource); + size_t k_root_len = strlen(k_root); + size_t module_path_len = strlen(module_path); + size_t k_path_len = strlen(k_path); + // Format: k_resource + "(" + k_root + "):" + k_path + "/" + module_path + null + size_t total_len = k_resource_len + 1 + k_root_len + 2 + k_path_len + 1 + module_path_len + 1; + if (total_len > 1024) { + panic(true, "limine: Internal module path too long"); + } + + char *module_path_abs = ext_mem_alloc(1024); + char *module_path_abs_p = module_path_abs; + memcpy(module_path_abs_p, k_resource, k_resource_len); + module_path_abs_p += k_resource_len; + *module_path_abs_p++ = '('; + memcpy(module_path_abs_p, k_root, k_root_len); + module_path_abs_p += k_root_len; + memcpy(module_path_abs_p, "):", 2); + module_path_abs_p += 2; + size_t remaining_size = 1024 - (module_path_abs_p - module_path_abs); + if (!get_absolute_path(module_path_abs_p, module_path, k_path, remaining_size)) { + panic(true, "limine: Internal module path too long"); + } + + module_path = module_path_abs; + module_path_allocated = true; + + module_required = internal_module->flags & LIMINE_INTERNAL_MODULE_REQUIRED; + } else { + size_t config_index = i - (module_request->revision >= 1 ? module_request->internal_module_count : 0); + + // Try MODULE_STRING first, then fall back to MODULE_CMDLINE + struct conf_tuple conf_tuple = + config_get_tuple(config, config_index, "MODULE_PATH", "MODULE_STRING"); + + module_path = conf_tuple.value1; + module_cmdline = conf_tuple.value2; + + if (module_cmdline == NULL) { + conf_tuple = config_get_tuple(config, config_index, "MODULE_PATH", "MODULE_CMDLINE"); + module_cmdline = conf_tuple.value2; + } + + // Copy cmdline since conf_tuple uses static buffers + module_cmdline = module_cmdline ? strdup(module_cmdline) : ""; + } + + print("limine: Loading module `%#`...\n", module_path); + + struct file_handle *f; + if ((f = uri_open(module_path)) == NULL) { + if (module_required) { + panic(true, "limine: Failed to open module with path `%#`. Is the path correct?", module_path); + } + printv("limine: Warning: Non-required internal module `%#` not found\n", module_path); + if (module_path_allocated) { + pmm_free(module_path, 1024); + } + continue; + } + if (module_path_allocated) { + pmm_free(module_path, 1024); + } + + struct limine_file *l = &modules[final_module_count++]; + *l = get_file(f, module_cmdline, false); + + fclose(f); + } + + uint64_t *modules_list = ext_mem_alloc(final_module_count * sizeof(uint64_t)); + for (size_t i = 0; i < final_module_count; i++) { + modules_list[i] = reported_addr(&modules[i]); + } + + module_response->module_count = final_module_count; + module_response->modules = reported_addr(modules_list); + + module_request->response = reported_addr(module_response); +FEAT_END + + size_t req_width = 0, req_height = 0, req_bpp = 0; + + char *resolution = config_get_value(config, 0, "RESOLUTION"); + if (resolution != NULL) { + parse_resolution(&req_width, &req_height, &req_bpp, resolution); + } + + struct fb_info *fbs; + size_t fbs_count; + + term_notready(); + + fb_init(&fbs, &fbs_count, req_width, req_height, req_bpp); + if (fbs_count == 0) { + goto no_fb; + } + + for (size_t i = 0; i < fbs_count; i++) { + memmap_alloc_range(fbs[i].framebuffer_addr, + (uint64_t)fbs[i].framebuffer_pitch * fbs[i].framebuffer_height, + MEMMAP_FRAMEBUFFER, 0, false, false, true); + } + + // Framebuffer feature +FEAT_START + struct limine_framebuffer_request *framebuffer_request = get_request(LIMINE_FRAMEBUFFER_REQUEST_ID); + if (framebuffer_request == NULL) { + break; // next feature + } + + if (fbs_count == 0) { + break; + } + + struct limine_framebuffer *fbp = ext_mem_alloc(fbs_count * sizeof(struct limine_framebuffer)); + + struct limine_framebuffer_response *framebuffer_response = + ext_mem_alloc(sizeof(struct limine_framebuffer_response)); + + framebuffer_response->revision = 1; + + uint64_t *fb_list = ext_mem_alloc(fbs_count * sizeof(uint64_t)); + + for (size_t i = 0; i < fbs_count; i++) { + uint64_t *modes_list = ext_mem_alloc(fbs[i].mode_count * sizeof(uint64_t)); + for (size_t j = 0; j < fbs[i].mode_count; j++) { + fbs[i].mode_list[j].memory_model = LIMINE_FRAMEBUFFER_RGB; + modes_list[j] = reported_addr(&fbs[i].mode_list[j]); + } + fbp[i].modes = reported_addr(modes_list); + fbp[i].mode_count = fbs[i].mode_count; + + if (fbs[i].edid != NULL) { + fbp[i].edid_size = sizeof(struct edid_info_struct); + fbp[i].edid = reported_addr(fbs[i].edid); + } + + fbp[i].memory_model = LIMINE_FRAMEBUFFER_RGB; + fbp[i].address = reported_addr((void *)(uintptr_t)fbs[i].framebuffer_addr); + fbp[i].width = fbs[i].framebuffer_width; + fbp[i].height = fbs[i].framebuffer_height; + fbp[i].bpp = fbs[i].framebuffer_bpp; + fbp[i].pitch = fbs[i].framebuffer_pitch; + fbp[i].red_mask_size = fbs[i].red_mask_size; + fbp[i].red_mask_shift = fbs[i].red_mask_shift; + fbp[i].green_mask_size = fbs[i].green_mask_size; + fbp[i].green_mask_shift = fbs[i].green_mask_shift; + fbp[i].blue_mask_size = fbs[i].blue_mask_size; + fbp[i].blue_mask_shift = fbs[i].blue_mask_shift; + + fb_list[i] = reported_addr(&fbp[i]); + } + + framebuffer_response->framebuffer_count = fbs_count; + framebuffer_response->framebuffers = reported_addr(fb_list); + + framebuffer_request->response = reported_addr(framebuffer_response); +FEAT_END + +no_fb: + // Boot time feature +FEAT_START + struct limine_date_at_boot_request *date_at_boot_request = get_request(LIMINE_DATE_AT_BOOT_REQUEST_ID); + if (date_at_boot_request == NULL) { + break; // next feature + } + + struct limine_date_at_boot_response *date_at_boot_response = + ext_mem_alloc(sizeof(struct limine_date_at_boot_response)); + + date_at_boot_response->timestamp = time(); + + date_at_boot_request->response = reported_addr(date_at_boot_response); +FEAT_END + + // Wrap-up stuff before memmap close +#if defined (__x86_64__) || defined (__i386__) + struct gdtr *local_gdt = ext_mem_alloc(sizeof(struct gdtr)); + local_gdt->limit = gdt.limit; + uint64_t local_gdt_base = (uint64_t)gdt.ptr; + local_gdt_base += direct_map_offset; + local_gdt->ptr = local_gdt_base; +#if defined (__i386__) + local_gdt->ptr_hi = local_gdt_base >> 32; +#endif +#endif + +#if defined (__aarch64__) + // Find the most restrictive caching mode from all framebuffers to use + uint64_t fb_attr = (uint64_t)-1; + + for (size_t i = 0; i < fbs_count; i++) { + int el = current_el(); + uint64_t res; + + // Figure out the caching mode used for this particular framebuffer + if (el == 1) { + asm volatile ( + "at s1e1w, %1\n\t" + "isb\n\t" + "mrs %0, par_el1" + : "=r"(res) + : "r"(fbs[i].framebuffer_addr) + : "memory"); + } else if (el == 2) { + asm volatile ( + "at s1e2w, %1\n\t" + "isb\n\t" + "mrs %0, par_el1" + : "=r"(res) + : "r"(fbs[i].framebuffer_addr) + : "memory"); + } else { + panic(false, "Unexpected EL in limine_load"); + } + + if (res & 1) + panic(false, "Address translation for framebuffer failed"); + + uint64_t new_attr = res >> 56; + + // Use whatever we find first + if (fb_attr == (uint64_t)-1) + fb_attr = new_attr; + // Prefer Device memory over Normal memory + else if ((fb_attr & 0b11110000) && !(new_attr & 0b11110000)) + fb_attr = new_attr; + // Prefer tighter Device memory (lower values) + else if (!(fb_attr & 0b11110000) && !(new_attr & 0b11110000) && fb_attr > new_attr) + fb_attr = new_attr; + // Use Normal non-cacheable otherwise (avoid trying to figure out how to downgrade inner vs outer). + else if ((fb_attr & 0b11110000) && (new_attr & 0b11110000)) + fb_attr = 0b01000100; // Inner&outer Non-cacheable + // Otherwise do nothing (fb_attr is already more restrictive than new_attr). + } + + // If no framebuffers are found, just zero out the MAIR entry + if (fb_attr == (uint64_t)-1) + fb_attr = 0; +#endif + + void *stack = ext_mem_alloc(stack_size) + stack_size; + + bool nx_available = true; +#if defined (__x86_64__) || defined (__i386__) + // Check if we have NX + if (!cpuid(0x80000001, 0, &eax, &ebx, &ecx, &edx) || !(edx & (1 << 20))) { + nx_available = false; + } +#endif + + // Bootloader Performance + // rdtsc_usec depends on EFI boot services +FEAT_START + if (usec_at_bootloader_entry == 0) { + break; + } + + struct limine_bootloader_performance_request *perf_request = get_request(LIMINE_BOOTLOADER_PERFORMANCE_REQUEST_ID); + if (perf_request == NULL) { + break; + } + + struct limine_bootloader_performance_response *perf_response = + ext_mem_alloc(sizeof(struct limine_bootloader_performance_response)); + + perf_response->reset_usec = 0; + perf_response->init_usec = usec_at_bootloader_entry; + perf_response->exec_usec = rdtsc_usec(); + perf_request->response = reported_addr(perf_response); +FEAT_END + +#if defined (UEFI) + efi_exit_boot_services(); +#endif + + // EFI memory map +#if defined (UEFI) +FEAT_START + struct limine_efi_memmap_request *efi_memmap_request = get_request(LIMINE_EFI_MEMMAP_REQUEST_ID); + if (efi_memmap_request == NULL) { + break; // next feature + } + + struct limine_efi_memmap_response *efi_memmap_response = + ext_mem_alloc(sizeof(struct limine_efi_memmap_response)); + + efi_memmap_response->memmap = reported_addr(efi_mmap); + efi_memmap_response->memmap_size = efi_mmap_size; + efi_memmap_response->desc_size = efi_desc_size; + efi_memmap_response->desc_version = efi_desc_ver; + + efi_memmap_request->response = reported_addr(efi_memmap_response); +FEAT_END +#endif + + if (base_revision < 3) { + pmm_sanitiser_keep_first_page = false; + pmm_sanitise_entries(memmap, &memmap_entries, true); + } + + if (base_revision >= 4) { + acpi_map_tables(); + if (base_revision >= 5) { + smbios_map_tables(); +#if defined (UEFI) + efi_map_runtime_entries(); +#endif + } + pmm_sanitise_entries(memmap, &memmap_entries, true); + } + + pagemap_t pagemap = {0}; + pagemap = build_pagemap(base_revision, nx_available, ranges, ranges_count, + physical_base, virtual_base, direct_map_offset); + + // MP +FEAT_START + struct limine_mp_request *mp_request = get_request(LIMINE_MP_REQUEST_ID); + if (mp_request == NULL) { + break; // next feature + } + + struct limine_mp_info *mp_info; + size_t cpu_count; +#if defined (__x86_64__) || defined (__i386__) + smp_configure_apic = base_revision >= 5; + uint32_t bsp_lapic_id; + mp_info = init_smp(&cpu_count, &bsp_lapic_id, + paging_mode, + pagemap, mp_request->flags & LIMINE_MP_REQUEST_X86_64_X2APIC, nx_available, + direct_map_offset, true); +#elif defined (__aarch64__) + uint64_t bsp_mpidr; + + mp_info = init_smp(config, &cpu_count, &bsp_mpidr, + pagemap, LIMINE_MAIR(fb_attr), LIMINE_TCR(tsz, pa), LIMINE_SCTLR, + direct_map_offset); +#elif defined (__riscv) + mp_info = init_smp(&cpu_count, pagemap, direct_map_offset); +#elif defined (__loongarch64) + cpu_count = 0; + mp_info = NULL; // TODO: LoongArch MP +#else +#error Unknown architecture +#endif + + if (mp_info == NULL) { + break; + } + + for (size_t i = 0; i < cpu_count; i++) { +#if defined (__x86_64__) || defined (__i386__) + if (mp_info[i].lapic_id == bsp_lapic_id) { + continue; + } +#elif defined (__aarch64__) + if (mp_info[i].mpidr == bsp_mpidr) { + continue; + } +#elif defined (__riscv) + if (mp_info[i].hartid == bsp_hartid) { + continue; + } +#elif defined (__loongarch64) +#else +#error Unknown architecture +#endif + + void *cpu_stack = ext_mem_alloc(stack_size) + stack_size; + mp_info[i].reserved = reported_addr(cpu_stack); + } + + struct limine_mp_response *mp_response = + ext_mem_alloc(sizeof(struct limine_mp_response)); + +#if defined (__x86_64__) || defined (__i386__) + mp_response->flags |= + (mp_request->flags & LIMINE_MP_REQUEST_X86_64_X2APIC) && x2apic_check() ? LIMINE_MP_RESPONSE_X86_64_X2APIC : 0; + mp_response->bsp_lapic_id = bsp_lapic_id; +#elif defined (__aarch64__) + mp_response->bsp_mpidr = bsp_mpidr; +#elif defined (__riscv) + mp_response->bsp_hartid = bsp_hartid; +#elif defined (__loongarch64) +#else +#error Unknown architecture +#endif + + uint64_t *mp_list = ext_mem_alloc(cpu_count * sizeof(uint64_t)); + for (size_t i = 0; i < cpu_count; i++) { + mp_list[i] = reported_addr(&mp_info[i]); + } + + mp_response->cpu_count = cpu_count; + mp_response->cpus = reported_addr(mp_list); + + mp_request->response = reported_addr(mp_response); +FEAT_END + +#if defined (__x86_64__) || defined (__i386__) + // If there was no MP request, the kernel has no way to tell us it supports + // x2APIC. Try to disable it as a courtesy, but do not panic if we cannot + // since the kernel may be able to deal with it itself. + if (get_request(LIMINE_MP_REQUEST_ID) == NULL + && (rdmsr(0x1b) & (1 << 10))) { + if (x2apic_disable()) { + printv("limine: Firmware had x2APIC enabled, reverted to xAPIC mode\n"); + } else { + printv("limine: Firmware has x2APIC enabled and it could not be disabled\n"); + } + } +#endif + +#if defined(__riscv) + // RISC-V BSP Hart ID +FEAT_START + struct limine_riscv_bsp_hartid_request *bsp_request = get_request(LIMINE_RISCV_BSP_HARTID_REQUEST_ID); + if (bsp_request == NULL) { + break; + } + struct limine_riscv_bsp_hartid_response *bsp_response = ext_mem_alloc(sizeof(struct limine_riscv_bsp_hartid_response)); + bsp_response->bsp_hartid = bsp_hartid; + bsp_request->response = reported_addr(bsp_response); +FEAT_END +#endif + + // Memmap +FEAT_START + struct limine_memmap_request *memmap_request = get_request(LIMINE_MEMMAP_REQUEST_ID); + struct limine_memmap_response *memmap_response; + struct limine_memmap_entry *_memmap; + uint64_t *memmap_list; + + if (memmap_request != NULL) { + memmap_response = ext_mem_alloc(sizeof(struct limine_memmap_response)); + _memmap = ext_mem_alloc(sizeof(struct limine_memmap_entry) * MEMMAP_MAX); + memmap_list = ext_mem_alloc(MEMMAP_MAX * sizeof(uint64_t)); + } + + size_t mmap_entries; + struct memmap_entry *mmap = get_memmap(&mmap_entries); + + if (memmap_request == NULL) { + break; // next feature + } + + if (mmap_entries > MEMMAP_MAX) { + panic(false, "limine: Too many memmap entries"); + } + + for (size_t i = 0; i < mmap_entries; i++) { + _memmap[i].base = mmap[i].base; + _memmap[i].length = mmap[i].length; + + switch (mmap[i].type) { + case MEMMAP_USABLE: + _memmap[i].type = LIMINE_MEMMAP_USABLE; + break; + case MEMMAP_RESERVED_MAPPED: + _memmap[i].type = LIMINE_MEMMAP_RESERVED_MAPPED; + break; + case MEMMAP_ACPI_RECLAIMABLE: + _memmap[i].type = LIMINE_MEMMAP_ACPI_RECLAIMABLE; + break; + case MEMMAP_ACPI_NVS: + _memmap[i].type = LIMINE_MEMMAP_ACPI_NVS; + break; + case MEMMAP_BAD_MEMORY: + _memmap[i].type = LIMINE_MEMMAP_BAD_MEMORY; + break; + case MEMMAP_BOOTLOADER_RECLAIMABLE: + _memmap[i].type = LIMINE_MEMMAP_BOOTLOADER_RECLAIMABLE; + break; + case MEMMAP_KERNEL_AND_MODULES: + _memmap[i].type = LIMINE_MEMMAP_EXECUTABLE_AND_MODULES; + break; + case MEMMAP_FRAMEBUFFER: + _memmap[i].type = LIMINE_MEMMAP_FRAMEBUFFER; + break; + default: + case MEMMAP_RESERVED: + _memmap[i].type = LIMINE_MEMMAP_RESERVED; + break; + } + } + + for (size_t i = 0; i < mmap_entries; i++) { + memmap_list[i] = reported_addr(&_memmap[i]); + } + + memmap_response->entry_count = mmap_entries; + memmap_response->entries = reported_addr(memmap_list); + + memmap_request->response = reported_addr(memmap_response); +FEAT_END + +#if defined (__x86_64__) || defined (__i386__) +#if defined (BIOS) + // If we're going 64, we might as well call this BIOS interrupt + // to tell the BIOS that we are entering Long Mode, since it is in + // the specification. + struct rm_regs r = {0}; + r.eax = 0xec00; + r.ebx = 0x02; // Long mode only + rm_int(0x15, &r, &r); +#endif + + if (!keep_iommu) { + iommu_disable_all(); + } + + pic_mask_all(); + io_apic_mask_all(base_revision >= 5); + + if (base_revision >= 5 && lapic_check()) { + lapic_configure_bsp(); + } + + irq_flush_type = IRQ_PIC_APIC_FLUSH; + + uint64_t reported_stack = reported_addr(stack); + + common_spinup(limine_spinup_32, 11, + paging_mode, (uint32_t)(uintptr_t)pagemap.top_level, + (uint32_t)entry_point, (uint32_t)(entry_point >> 32), + (uint32_t)reported_stack, (uint32_t)(reported_stack >> 32), + (uint32_t)(uintptr_t)local_gdt, nx_available, + (uint32_t)direct_map_offset, (uint32_t)(direct_map_offset >> 32), + (uint32_t)base_revision + ); +#elif defined (__aarch64__) + vmm_assert_4k_pages(); + + uint64_t reported_stack = reported_addr(stack); + + enter_in_el1(entry_point, reported_stack, LIMINE_SCTLR, LIMINE_MAIR(fb_attr), LIMINE_TCR(tsz, pa), + (uint64_t)pagemap.top_level[0], + (uint64_t)pagemap.top_level[1], + direct_map_offset); +#elif defined (__riscv) + uint64_t reported_stack = reported_addr(stack); + uint64_t satp = make_satp(pagemap.paging_mode, pagemap.top_level); + + riscv_spinup(entry_point, reported_stack, satp, direct_map_offset); +#elif defined (__loongarch64) + uint64_t reported_stack = reported_addr(stack); + + loongarch_spinup(entry_point, reported_stack, (uint64_t)pagemap.pgd[0], (uint64_t)pagemap.pgd[1], + direct_map_offset); +#else +#error Unknown architecture +#endif +} diff --git a/limine/common/protos/limine.h b/limine/common/protos/limine.h new file mode 100644 index 0000000..54cd788 --- /dev/null +++ b/limine/common/protos/limine.h @@ -0,0 +1,8 @@ +#ifndef PROTOS__LIMINE_H__ +#define PROTOS__LIMINE_H__ + +#include + +noreturn void limine_load(char *config, char *cmdline); + +#endif diff --git a/limine/common/protos/limine_32.asm_x86 b/limine/common/protos/limine_32.asm_x86 new file mode 100644 index 0000000..75819e4 --- /dev/null +++ b/limine/common/protos/limine_32.asm_x86 @@ -0,0 +1,138 @@ +bits 32 + +section .text + +global limine_spinup_32 +limine_spinup_32: + ; If available, set PAT as: + ; PAT0 -> WB (06) + ; PAT1 -> WT (04) + ; PAT2 -> UC- (07) + ; PAT3 -> UC (00) + ; PAT4 -> WP (05) + ; PAT5 -> WC (01) + mov eax, 1 + xor ecx, ecx + cpuid + test edx, 1 << 16 + jz .no_pat + mov eax, 0x00070406 + mov edx, 0x00000105 + mov ecx, 0x277 + wrmsr + .no_pat: + + ; Enable CR4.LA57 + cmp dword [esp+4], 0 ; level5pg + je .no_la57 + mov eax, cr4 + bts eax, 12 + mov cr4, eax + .no_la57: + + ; Enable CR0.WP + mov eax, cr0 + bts eax, 16 + mov cr0, eax + + cld + + mov eax, [esp+8] ; pagemap_top_lv + mov cr3, eax + + ; Enable CR4.PAE + mov eax, cr4 + bts eax, 5 + mov cr4, eax + + ; Set EFER (LME + NX if available), all other bits cleared + mov ecx, 0xc0000080 + xor edx, edx + mov eax, 1 << 8 + cmp dword [esp+32], 0 ; nx_available + je .no_nx + or eax, 1 << 11 + .no_nx: + wrmsr + + ; Enable CR0.PG + mov eax, cr0 + bts eax, 31 + mov cr0, eax + + ; Go 64 + push 0x28 + call .p1 + .p1: + add dword [esp], .mode64 - .p1 + retfd + +bits 64 + .mode64: + mov eax, 0x30 + mov ds, eax + mov es, eax + mov fs, eax + mov gs, eax + mov ss, eax + + ; Load 64-bit GDT + mov eax, [rsp+28] ; local_gdt + lgdt [rax] + + ; Jump to higher half + mov rax, qword [rsp+36] + add rsp, rax + call .p2 + .p2: + add qword [rsp], .hh - .p2 + add qword [rsp], rax + retq + .hh: + + cmp dword [rsp+44], 1 + jb .no_unmap_lower_half + + ; Unmap lower half entirely + mov rsi, cr3 + lea rdi, [rsi + rax] + mov rcx, 256 + xor rax, rax + rep stosq + mov cr3, rsi + + .no_unmap_lower_half: + + ; Push fake return address + mov rsi, [rsp+20] ; stack + sub rsi, 8 + mov qword [rsi], 0 + + ; Prepare iretq frame + mov rax, qword [rsp+12] ; entry_point + push 0x30 + push rsi + push 0x2 + push 0x28 + push rax + + ; Zero out all GPRs + xor eax, eax + xor ebx, ebx + xor ecx, ecx + xor edx, edx + xor esi, esi + xor edi, edi + xor ebp, ebp + xor r8d, r8d + xor r9d, r9d + xor r10d, r10d + xor r11d, r11d + xor r12d, r12d + xor r13d, r13d + xor r14d, r14d + xor r15d, r15d + + iretq + +section .note.GNU-stack noalloc noexec nowrite progbits diff --git a/limine/common/protos/limine_asm.asm_ia32 b/limine/common/protos/limine_asm.asm_ia32 new file mode 100644 index 0000000..a83cc9d --- /dev/null +++ b/limine/common/protos/limine_asm.asm_ia32 @@ -0,0 +1,278 @@ +%ifdef UEFI +extern _GLOBAL_OFFSET_TABLE_ +%endif + +section .data + +local_gdt: + dq 0 + + dq 0x00209b0000000000 + dq 0x0000930000000000 + .top: + +local_gdt_ptr: + dw local_gdt.top - local_gdt - 1 + dd local_gdt + dd 0 + +old_gdt_ptr: + dw 0 + dq 0 + +old_cs: + dq 0 + +interrupt_state: + dd 0 + +old_ds: + dq 0 +old_es: + dq 0 +old_fs: + dq 0 +old_gs: + dq 0 +old_ss: + dq 0 + +%ifdef UEFI +paging_state: + dd 0 + +pae_state: + dd 0 + +old_pagemap: + dd 0 +%endif + +section .text + +; void limine_memcpy_to_64_asm(int paging_mode, void *pagemap, uint64_t dst, void *src, size_t count); + +global limine_memcpy_to_64_asm +limine_memcpy_to_64_asm: + push ebp + mov ebp, esp + + pusha + +%ifdef UEFI + call .get_got + .get_got: + pop ebx + add ebx, _GLOBAL_OFFSET_TABLE_ + $$ - .get_got wrt ..gotpc +%endif + + pushfd + mov eax, dword [esp] + add esp, 4 + and eax, 0x200 +%ifdef UEFI + mov dword [ebx + interrupt_state wrt ..gotoff], eax +%else + mov dword [interrupt_state], eax +%endif + + cli + +%ifdef UEFI + ; Disable paging if needed and save old pagemap + mov eax, cr0 + and eax, (1 << 31) + mov dword [ebx + paging_state wrt ..gotoff], eax + mov eax, cr0 + btr eax, 31 + mov cr0, eax + + mov eax, cr3 + mov dword [ebx + old_pagemap wrt ..gotoff], eax +%endif + + mov eax, [ebp+12] ; pagemap + mov cr3, eax + + ; Enable CR4.LA57 + cmp dword [ebp+8], 0 ; paging_mode + je .no_la57 + mov eax, cr4 + bts eax, 12 + mov cr4, eax + .no_la57: + + ; Enable CR4.PAE +%ifdef UEFI + mov eax, cr4 + and eax, (1 << 5) + mov [ebx + pae_state wrt ..gotoff], eax +%endif + mov eax, cr4 + bts eax, 5 + mov cr4, eax + + ; Enable EFER.LME + mov ecx, 0xc0000080 + rdmsr + bts eax, 8 + wrmsr + + ; Enable CR0.PG + mov eax, cr0 + bts eax, 31 + mov cr0, eax + + ; Save old segments +%ifdef UEFI + mov [ebx + old_ss wrt ..gotoff], ss + mov [ebx + old_gs wrt ..gotoff], gs + mov [ebx + old_fs wrt ..gotoff], fs + mov [ebx + old_es wrt ..gotoff], es + mov [ebx + old_ds wrt ..gotoff], ds +%else + mov [old_ss], ss + mov [old_gs], gs + mov [old_fs], fs + mov [old_es], es + mov [old_ds], ds +%endif + + ; Save old CS + mov eax, cs +%ifdef UEFI + mov dword [ebx + old_cs wrt ..gotoff], eax +%else + mov dword [old_cs], eax +%endif + + ; Save old GDT +%ifdef UEFI + sgdt [ebx + old_gdt_ptr wrt ..gotoff] +%else + sgdt [old_gdt_ptr] +%endif + + ; Load new GDT +%ifdef UEFI + lgdt [ebx + local_gdt_ptr wrt ..gotoff] +%else + lgdt [local_gdt_ptr] +%endif + + ; Go 64 + push 0x08 + call .p1 + .p1: + add dword [esp], .mode64 - .p1 + retf + +bits 64 + .mode64: + mov eax, 0x10 + mov ds, eax + mov es, eax + mov fs, eax + mov gs, eax + mov ss, eax + + mov rdi, qword [rbp+16] + mov esi, dword [rbp+24] + mov ecx, dword [rbp+28] + + rep movsb + + ; Restore old GDT +%ifdef UEFI + lgdt [ebx + old_gdt_ptr wrt ..gotoff] +%else + lgdt [abs old_gdt_ptr] +%endif + + ; Restore old segments +%ifdef UEFI + mov ds, [ebx + old_ds wrt ..gotoff] + mov es, [ebx + old_es wrt ..gotoff] + mov fs, [ebx + old_fs wrt ..gotoff] + mov gs, [ebx + old_gs wrt ..gotoff] + mov ss, [ebx + old_ss wrt ..gotoff] +%else + mov ds, [abs old_ds] + mov es, [abs old_es] + mov fs, [abs old_fs] + mov gs, [abs old_gs] + mov ss, [abs old_ss] +%endif + + ; Go 32 +%ifdef UEFI + push qword [ebx + old_cs wrt ..gotoff] +%else + push qword [abs old_cs] +%endif + call .p2 + .p2: + add qword [rsp], .mode32 - .p2 + retfq + +bits 32 + .mode32: + ; Disable CR0.PG + mov eax, cr0 + btr eax, 31 + mov cr0, eax + + ; Disable EFER.LME + mov ecx, 0xc0000080 + rdmsr + btr eax, 8 + wrmsr + + ; Disable CR4.PAE +%ifdef UEFI + cmp dword [ebx + pae_state wrt ..gotoff], 0 + jne .no_disable_pae +%endif + mov eax, cr4 + btr eax, 5 + mov cr4, eax +%ifdef UEFI + .no_disable_pae: +%endif + + ; Disable CR4.LA57 + mov eax, cr4 + btr eax, 12 + mov cr4, eax + + ; Invalidate pagemap + xor eax, eax + mov cr3, eax + +%ifdef UEFI + mov eax, dword [ebx + old_pagemap wrt ..gotoff] + mov cr3, eax + + cmp dword [ebx + paging_state wrt ..gotoff], 0 + je .no_paging + mov eax, cr0 + bts eax, 31 + mov cr0, eax + .no_paging: +%endif + +%ifdef UEFI + cmp dword [ebx + interrupt_state wrt ..gotoff], 0 +%else + cmp dword [interrupt_state], 0 +%endif + je .no_ints + sti + .no_ints: + + popa + pop ebp + + ret + +section .note.GNU-stack noalloc noexec nowrite progbits diff --git a/limine/common/protos/linux.h b/limine/common/protos/linux.h new file mode 100644 index 0000000..f214c25 --- /dev/null +++ b/limine/common/protos/linux.h @@ -0,0 +1,73 @@ +#ifndef PROTOS__LINUX_H__ +#define PROTOS__LINUX_H__ + +#include + +struct screen_info { + uint8_t orig_x; /* 0x00 */ + uint8_t orig_y; /* 0x01 */ + uint16_t ext_mem_k; /* 0x02 */ + uint16_t orig_video_page; /* 0x04 */ + uint8_t orig_video_mode; /* 0x06 */ + uint8_t orig_video_cols; /* 0x07 */ + uint8_t flags; /* 0x08 */ + uint8_t unused2; /* 0x09 */ + uint16_t orig_video_ega_bx;/* 0x0a */ + uint16_t unused3; /* 0x0c */ + uint8_t orig_video_lines; /* 0x0e */ + uint8_t orig_video_isVGA; /* 0x0f */ + uint16_t orig_video_points;/* 0x10 */ + + /* VESA graphic mode -- linear frame buffer */ + uint16_t lfb_width; /* 0x12 */ + uint16_t lfb_height; /* 0x14 */ + uint16_t lfb_depth; /* 0x16 */ + uint32_t lfb_base; /* 0x18 */ + uint32_t lfb_size; /* 0x1c */ + uint16_t cl_magic, cl_offset; /* 0x20 */ + uint16_t lfb_linelength; /* 0x24 */ + uint8_t red_size; /* 0x26 */ + uint8_t red_pos; /* 0x27 */ + uint8_t green_size; /* 0x28 */ + uint8_t green_pos; /* 0x29 */ + uint8_t blue_size; /* 0x2a */ + uint8_t blue_pos; /* 0x2b */ + uint8_t rsvd_size; /* 0x2c */ + uint8_t rsvd_pos; /* 0x2d */ + uint16_t vesapm_seg; /* 0x2e */ + uint16_t vesapm_off; /* 0x30 */ + uint16_t pages; /* 0x32 */ + uint16_t vesa_attributes; /* 0x34 */ + uint32_t capabilities; /* 0x36 */ + uint32_t ext_lfb_base; /* 0x3a */ + uint8_t _reserved[2]; /* 0x3e */ +} __attribute__((packed)); + +#define VIDEO_TYPE_MDA 0x10 /* Monochrome Text Display */ +#define VIDEO_TYPE_CGA 0x11 /* CGA Display */ +#define VIDEO_TYPE_EGAM 0x20 /* EGA/VGA in Monochrome Mode */ +#define VIDEO_TYPE_EGAC 0x21 /* EGA in Color Mode */ +#define VIDEO_TYPE_VGAC 0x22 /* VGA+ in Color Mode */ +#define VIDEO_TYPE_VLFB 0x23 /* VESA VGA in graphic mode */ + +#define VIDEO_TYPE_PICA_S3 0x30 /* ACER PICA-61 local S3 video */ +#define VIDEO_TYPE_MIPS_G364 0x31 /* MIPS Magnum 4000 G364 video */ +#define VIDEO_TYPE_SGI 0x33 /* Various SGI graphics hardware */ + +#define VIDEO_TYPE_TGAC 0x40 /* DEC TGA */ + +#define VIDEO_TYPE_SUN 0x50 /* Sun frame buffer. */ +#define VIDEO_TYPE_SUNPCI 0x51 /* Sun PCI based frame buffer. */ + +#define VIDEO_TYPE_PMAC 0x60 /* PowerMacintosh frame buffer. */ + +#define VIDEO_TYPE_EFI 0x70 /* EFI graphic mode */ + +#define VIDEO_FLAGS_NOCURSOR (1 << 0) /* The video mode has no cursor set */ + +#define VIDEO_CAPABILITY_SKIP_QUIRKS (1 << 0) +#define VIDEO_CAPABILITY_64BIT_BASE (1 << 1) /* Frame buffer base is 64-bit */ + +noreturn void linux_load(char *config, char *cmdline); + +#endif diff --git a/limine/common/protos/linux_32.asm_x86 b/limine/common/protos/linux_32.asm_x86 new file mode 100644 index 0000000..3756cb5 --- /dev/null +++ b/limine/common/protos/linux_32.asm_x86 @@ -0,0 +1,69 @@ +section .data + +align 16 +linux_gdt: + dq 0 + + dq 0 + + dw 0xffff + dw 0x0000 + db 0x00 + db 10011011b + db 11001111b + db 0x00 + + dw 0xffff + dw 0x0000 + db 0x00 + db 10010011b + db 11001111b + db 0x00 + + .end: + +align 16 +linux_gdt_ptr: + dw (linux_gdt.end - linux_gdt) - 1 + dd 0 + +bits 32 + +section .text + +global linux_spinup +linux_spinup: + call .p0 + .p0: + pop eax + lea ebx, [eax - (linux_spinup.p0 - linux_gdt_ptr)] + lea ecx, [eax - (linux_spinup.p0 - linux_gdt)] + mov [ebx+2], ecx + + lgdt [ebx] + + push 0x10 + call .p1 + .p1: + add dword [esp], .fj - .p1 + retfd + + .fj: + mov eax, 0x18 + mov ds, eax + mov es, eax + mov fs, eax + mov gs, eax + mov ss, eax + + xor ebp, ebp + xor edi, edi + xor ebx, ebx + + mov esi, [esp+8] ; boot_params + + cld + + jmp [esp+4] + +section .note.GNU-stack noalloc noexec nowrite progbits diff --git a/limine/common/protos/linux_64.asm_uefi_x86_64 b/limine/common/protos/linux_64.asm_uefi_x86_64 new file mode 100644 index 0000000..bca9b46 --- /dev/null +++ b/limine/common/protos/linux_64.asm_uefi_x86_64 @@ -0,0 +1,62 @@ +section .data + +align 16 +linux_gdt64: + dq 0 + + dq 0 + + dw 0xffff + dw 0x0000 + db 0x00 + db 10011011b + db 10101111b + db 0x00 + + dw 0xffff + dw 0x0000 + db 0x00 + db 10010011b + db 10001111b + db 0x00 + + .end: + +align 16 +linux_gdt64_ptr: + dw (linux_gdt64.end - linux_gdt64) - 1 + dq linux_gdt64 + +section .text + +bits 64 + +global linux_spinup64 +linux_spinup64: + cli + cld + + lgdt [rel linux_gdt64_ptr] + + lea rbx, [rel .fj] + push 0x10 + push rbx + retfq + + .fj: + mov eax, 0x18 + mov ds, eax + mov es, eax + mov fs, eax + mov gs, eax + mov ss, eax + + mov rax, rdi + + xor ebp, ebp + xor edi, edi + xor ebx, ebx + + jmp rax + +section .note.GNU-stack noalloc noexec nowrite progbits diff --git a/limine/common/protos/linux_risc.c b/limine/common/protos/linux_risc.c new file mode 100644 index 0000000..c1c0956 --- /dev/null +++ b/limine/common/protos/linux_risc.c @@ -0,0 +1,473 @@ +#if defined(__riscv) || defined(__aarch64__) || defined(__loongarch__) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// The following definitions and struct were copied and adapted from Linux +// kernel headers released under GPL-2.0 WITH Linux-syscall-note +// allowing their inclusion in non GPL compliant code. + +#if defined(__riscv) || defined(__aarch64__) +struct linux_header { + uint32_t code0; + uint32_t code1; + uint64_t text_offset; + uint64_t image_size; + uint64_t flags; + uint32_t version; + uint32_t res1; + uint64_t res2; + uint64_t res3; // originally 'magic' field, deprecated + uint32_t magic2; + uint32_t res4; +} __attribute__((packed)); +#elif defined(__loongarch__) +struct linux_header { + uint32_t mz; + uint32_t res0; + uint64_t kernel_entry; + uint64_t image_size; + uint64_t load_offset; + uint64_t res1; + uint64_t res2; + uint64_t res3; + uint32_t magic2; // LINUX_PE_MAGIC + uint32_t pe_offset; +} __attribute__((packed)); +#else +#error "Unknown architecture" +#endif + +struct linux_efi_memreserve { + int size; + int count; + uint64_t next; +}; + +struct linux_efi_boot_memmap { + UINTN map_size; + UINTN desc_size; + uint32_t desc_ver; + UINTN map_key; + UINTN buff_size; + EFI_MEMORY_DESCRIPTOR descs[]; +}; + +struct linux_efi_initrd { + UINTN base; + UINTN size; +}; + +// End of Linux code + +struct boot_param { + void *kernel_base; + size_t kernel_size; + void *module_base; + size_t module_size; + char *cmdline; + void *dtb; + struct linux_efi_boot_memmap *memmap; +}; + +#if defined(__riscv) +#define LINUX_HEADER_MAGIC2 0x05435352 +#define LINUX_HEADER_MAJOR_VER(ver) (((ver) >> 16) & 0xffff) +#define LINUX_HEADER_MINOR_VER(ver) (((ver) >> 0) & 0xffff) +#elif defined(__aarch64__) +#define LINUX_HEADER_MAGIC2 0x644d5241 +#elif defined(__loongarch__) +#define LINUX_HEADER_MAGIC2 0x818223cd +#endif + +static const char *verify_kernel(struct linux_header *header) { + if (header->magic2 != LINUX_HEADER_MAGIC2) { + return "kernel header magic does not match"; + } + + // riscv-specific version requirements +#if defined(__riscv) + printv("linux: boot protocol version %d.%d\n", + LINUX_HEADER_MAJOR_VER(header->version), + LINUX_HEADER_MINOR_VER(header->version)); + if (LINUX_HEADER_MAJOR_VER(header->version) == 0 + && LINUX_HEADER_MINOR_VER(header->version) < 2) { + return "linux: protocols < 0.2 are not supported"; + } +#endif + + return NULL; +} + +static void load_module(struct boot_param *p, char *config) { + char *module_path = config_get_value(config, 0, "MODULE_PATH"); + if (module_path) { + print("linux: Loading module `%#`...\n", module_path); + + struct file_handle *module_file = uri_open(module_path); + if (!module_file) { + panic(true, "linux: failed to open module `%s`. Is the path correct?", module_path); + } + + p->module_size = module_file->size; + p->module_base = ext_mem_alloc_type_aligned( + ALIGN_UP(p->module_size, 4096), + MEMMAP_KERNEL_AND_MODULES, 4096); + + fread(module_file, p->module_base, 0, p->module_size); + fclose(module_file); + printv("linux: loaded module `%s` at %p, size %U\n", module_path, p->module_base, (uint64_t)p->module_size); + } +} + +static void prepare_device_tree_blob(struct boot_param *p) { + void *dtb = p->dtb; + int ret; + + // Delete all /memory@... nodes. Linux will use the given UEFI memory map + // instead. + while (true) { + int offset = fdt_subnode_offset_namelen(dtb, 0, "memory@", 7); + + if (offset == -FDT_ERR_NOTFOUND) { + break; + } + + if (offset < 0) { + panic(true, "linux: failed to find node: '%s'", fdt_strerror(offset)); + } + + ret = fdt_del_node(dtb, offset); + if (ret < 0) { + panic(true, "linux: failed to delete memory node: '%s'", fdt_strerror(ret)); + } + } + + if (p->module_base) { + ret = fdt_set_chosen_uint64(dtb, "linux,initrd-start", (uint64_t)p->module_base); + if (ret < 0) { + panic(true, "linux: cannot set initrd parameter: '%s'", fdt_strerror(ret)); + } + + ret = fdt_set_chosen_uint64(dtb, "linux,initrd-end", (uint64_t)(p->module_base + p->module_size)); + if (ret < 0) { + panic(true, "linux: cannot set initrd parameter: '%s'", fdt_strerror(ret)); + } + } + + // Set the kernel command line arguments. + ret = fdt_set_chosen_string(dtb, "bootargs", p->cmdline); + if (ret < 0) { + panic(true, "linux: failed to set bootargs: '%s'", fdt_strerror(ret)); + } + + // Tell Linux about the UEFI memory map and system table. + ret = fdt_set_chosen_uint64(dtb, "linux,uefi-system-table", (uint64_t)gST); + if (ret < 0) { + panic(true, "linux: failed to set UEFI system table pointer: '%s'", fdt_strerror(ret)); + } + + // This property is not required by mainline Linux, but is required by + // Debian (and derivative) kernels, because Debian has a patch that adds + // this flag, and the existing logic that deals with it will just outright + // fail if any of the properties is missing. We don't care about Debian's + // hardening or whatever, so just always report that secure boot is off. + ret = fdt_set_chosen_uint32(dtb, "linux,uefi-secure-boot", 0); + if (ret < 0) { + panic(true, "linux: failed to set UEFI secure boot state: '%s'", fdt_strerror(ret)); + } +} + +static void add_framebuffer(struct fb_info *fb) { + struct screen_info *screen_info; + + EFI_STATUS alloc_ret = gBS->AllocatePool(EfiLoaderData, sizeof(*screen_info), (void **)&screen_info); + if (alloc_ret != EFI_SUCCESS) { + panic(true, "linux: failed to allocate screen info table"); + } + memset(screen_info, 0, sizeof(*screen_info)); + + screen_info->capabilities = VIDEO_CAPABILITY_64BIT_BASE | VIDEO_CAPABILITY_SKIP_QUIRKS; + screen_info->flags = VIDEO_FLAGS_NOCURSOR; + screen_info->lfb_base = (uint32_t)fb->framebuffer_addr; + screen_info->ext_lfb_base = (uint32_t)(fb->framebuffer_addr >> 32); + screen_info->lfb_size = fb->framebuffer_pitch * fb->framebuffer_height; + screen_info->lfb_width = fb->framebuffer_width; + screen_info->lfb_height = fb->framebuffer_height; + screen_info->lfb_depth = fb->framebuffer_bpp; + screen_info->lfb_linelength = fb->framebuffer_pitch; + screen_info->red_size = fb->red_mask_size; + screen_info->red_pos = fb->red_mask_shift; + screen_info->green_size = fb->green_mask_size; + screen_info->green_pos = fb->green_mask_shift; + screen_info->blue_size = fb->blue_mask_size; + screen_info->blue_pos = fb->blue_mask_shift; + + screen_info->orig_video_isVGA = VIDEO_TYPE_EFI; + + EFI_GUID screen_info_table_guid = {0xe03fc20a, 0x85dc, 0x406e, {0xb9, 0x0e, 0x4a, 0xb5, 0x02, 0x37, 0x1d, 0x95}}; + EFI_STATUS ret = gBS->InstallConfigurationTable(&screen_info_table_guid, screen_info); + + if (ret != EFI_SUCCESS) { + panic(true, "linux: failed to install screen info configuration table: '%X'", (uint64_t)ret); + } +} + +static void prepare_efi_tables(struct boot_param *p, char *config) { + (void)p; + EFI_STATUS ret = 0; + + { + size_t req_width = 0, req_height = 0, req_bpp = 0; + + char *resolution = config_get_value(config, 0, "RESOLUTION"); + if (resolution != NULL) { + parse_resolution(&req_width, &req_height, &req_bpp, resolution); + } + + struct fb_info *fbs; + size_t fbs_count; + + term_notready(); + + fb_init(&fbs, &fbs_count, req_width, req_height, req_bpp); + + // TODO(qookie): Let the user pick a framebuffer if there's > 1 + if (fbs_count > 0) { + add_framebuffer(&fbs[0]); + } + } + + + { + struct linux_efi_memreserve *rsv; + + ret = gBS->AllocatePool(EfiLoaderData, sizeof(*rsv), (void **)&rsv); + if (ret != EFI_SUCCESS) { + panic(true, "linux: failed to allocate memory reservation table"); + } + memset(rsv, 0, sizeof(*rsv)); + + rsv->size = 0; + rsv->count = 0; + rsv->next = 0; + + EFI_GUID memreserve_table_guid = {0x888eb0c6, 0x8ede, 0x4ff5, {0xa8, 0xf0, 0x9a, 0xee, 0x5c, 0xb9, 0x77, 0xc2}}; + ret = gBS->InstallConfigurationTable(&memreserve_table_guid, rsv); + + if (ret != EFI_SUCCESS) { + panic(true, "linux: failed to install memory reservation configuration table: '%X'", (uint64_t)ret); + } + } + + if (p->module_base) { + struct linux_efi_initrd *initrd_table; + + ret = gBS->AllocatePool(EfiLoaderData, sizeof(*initrd_table), (void **)&initrd_table); + if (ret != EFI_SUCCESS) { + panic(true, "linux: failed to allocate Linux initrd table"); + } + + initrd_table->base = (UINTN)p->module_base; + initrd_table->size = p->module_size; + + EFI_GUID initrd_table_guid = { 0x5568e427, 0x68fc, 0x4f3d, { 0xac, 0x74, 0xca, 0x55, 0x52, 0x31, 0xcc, 0x68}}; + ret = gBS->InstallConfigurationTable(&initrd_table_guid, initrd_table); + if (ret != EFI_SUCCESS) { + panic(true, "linux: failed to install initrd\n"); + } + } + + { + size_t buff_size = sizeof(struct linux_efi_boot_memmap) + efi_mmap_size + 4096; + + ret = gBS->AllocatePool(EfiLoaderData, buff_size, (void **)&p->memmap); + if (ret != EFI_SUCCESS) { + panic(true, "linux: failed to allocate UEFI memory map"); + } + + p->memmap->buff_size = buff_size; + + EFI_GUID memmap_table_guid = { 0x800f683f, 0xd08b, 0x423a, { 0xa2, 0x93, 0x96, 0x5c, 0x3c, 0x6f, 0xe2, 0xb4}}; + ret = gBS->InstallConfigurationTable(&memmap_table_guid, p->memmap); + if (ret != EFI_SUCCESS) { + panic(true, "linux: failed to install UEFI memory map"); + } + } + + efi_exit_boot_services(); +} + +static void prepare_mmap(struct boot_param *p) { + { + void *dtb = p->dtb; + int ret = fdt_set_chosen_uint64(dtb, "linux,uefi-mmap-start", (uint64_t)efi_mmap); + if (ret < 0) { + panic(true, "linux: failed to set UEFI memory map pointer: '%s'", fdt_strerror(ret)); + } + + ret = fdt_set_chosen_uint32(dtb, "linux,uefi-mmap-size", efi_mmap_size); + if (ret < 0) { + panic(true, "linux: failed to set UEFI memory map size: '%s'", fdt_strerror(ret)); + } + + ret = fdt_set_chosen_uint32(dtb, "linux,uefi-mmap-desc-size", efi_desc_size); + if (ret < 0) { + panic(true, "linux: failed to set UEFI memory map descriptor size: '%s'", fdt_strerror(ret)); + } + + ret = fdt_set_chosen_uint32(dtb, "linux,uefi-mmap-desc-ver", efi_desc_ver); + if (ret < 0) { + panic(true, "linux: failed to set UEFI memory map descriptor version: '%s'", fdt_strerror(ret)); + } + } + + p->memmap->map_size = efi_mmap_size; + p->memmap->desc_size = efi_desc_size; + p->memmap->desc_ver = efi_desc_ver; + p->memmap->map_key = efi_mmap_key; + + size_t efi_mmap_entry_count = efi_mmap_size / efi_desc_size; + for (size_t i = 0; i < efi_mmap_entry_count; i++) { + EFI_MEMORY_DESCRIPTOR *entry = (void *)efi_mmap + i * efi_desc_size; + + if (entry->Attribute & EFI_MEMORY_RUNTIME) { + // LoongArch kernel requires the virtual address stays in the + // privileged, direct-mapped window + #if defined(__loongarch__) + entry->VirtualStart = entry->PhysicalStart | (0x8ULL << 60); + #else + entry->VirtualStart = entry->PhysicalStart; + #endif + } + } + + memcpy(&p->memmap->descs, efi_mmap, efi_mmap_size); + + EFI_STATUS status = gRT->SetVirtualAddressMap(efi_mmap_size, efi_desc_size, efi_desc_ver, efi_mmap); + if (status != EFI_SUCCESS) { + panic(false, "linux: failed to set UEFI virtual address map: '%X'", (uint64_t)status); + } +} + +noreturn static void jump_to_kernel(struct boot_param *p) { +#if defined(__riscv) + printv("linux: bsp hart %U, device tree blob at %p\n", (uint64_t)bsp_hartid, p->dtb); + + void (*kernel_entry)(uint64_t hartid, uint64_t dtb) = p->kernel_base; + asm ("csrci sstatus, 0x2\n\t" + "csrw sie, zero\n\t"); + kernel_entry(bsp_hartid, (uint64_t)p->dtb); +#elif defined(__aarch64__) + printv("linux: device tree blob at %p\n", p->dtb); + + void (*kernel_entry)(uint64_t dtb, uint64_t res0, uint64_t res1, uint64_t res2) = p->kernel_base; + asm ("msr daifset, 0xF"); + kernel_entry((uint64_t)p->dtb, 0, 0, 0); +#elif defined(__loongarch__) +// LoongArch kernel used to store virtual address in header.kernel_entry +// clearing the high 16bits ensures compatibility +#define TO_PHYS(addr) ((addr) & ((1ULL << 48) - 1)) +#define CSR_DMW_PLV0 1ULL +#define CSR_DMW0_VSEG 0x8000ULL +#define CSR_DMW0_BASE (CSR_DMW0_VSEG << 48) +#define CSR_DMW0_INIT (CSR_DMW0_BASE | CSR_DMW_PLV0) +#define CSR_DMW1_MAT (1 << 4) +#define CSR_DMW1_VSEG 0x9000ULL +#define CSR_DMW1_BASE (CSR_DMW1_VSEG << 48) +#define CSR_DMW1_INIT (CSR_DMW1_BASE | CSR_DMW1_MAT | CSR_DMW_PLV0) +#define CSR_DMW2_VSEG 0xa000ULL +#define CSR_DMW2_MAT (2 << 4) +#define CSR_DMW2_BASE (CSR_DMW2_VSEG << 48) +#define CSR_DMW2_INIT (CSR_DMW2_BASE | CSR_DMW2_MAT | CSR_DMW_PLV0) +#define CSR_DMW3_INIT 0 + + struct linux_header *header = p->kernel_base; + void (*kernel_entry)(uint64_t efi_boot, uint64_t cmdline, uint64_t st); + kernel_entry = p->kernel_base + (TO_PHYS(header->kernel_entry) - header->load_offset); + + asm volatile ("csrxchg $r0, %0, 0x0" :: "r" (0x4) : "memory"); + asm volatile ("csrwr %0, 0x180" :: "r" (CSR_DMW0_INIT) : "memory"); + asm volatile ("csrwr %0, 0x181" :: "r" (CSR_DMW1_INIT) : "memory"); + asm volatile ("csrwr %0, 0x182" :: "r" (CSR_DMW2_INIT) : "memory"); + asm volatile ("csrwr %0, 0x183" :: "r" (CSR_DMW3_INIT) : "memory"); + kernel_entry(1, (uint64_t)p->cmdline, (uint64_t)gST); +#endif + __builtin_unreachable(); +} + +noreturn void linux_load(char *config, char *cmdline) { + struct boot_param p; + memset(&p, 0, sizeof(p)); + p.cmdline = cmdline; + p.dtb = get_device_tree_blob(config, 0x1000); + + struct file_handle *kernel_file; + + char *kernel_path = config_get_value(config, 0, "PATH"); + if (kernel_path == NULL) { + kernel_path = config_get_value(config, 0, "KERNEL_PATH"); + } + if (kernel_path == NULL) { + panic(true, "linux: Kernel path not specified"); + } + + print("linux: Loading kernel `%#`...\n", kernel_path); + + if ((kernel_file = uri_open(kernel_path)) == NULL) { + panic(true, "linux: failed to open kernel `%s`. Is the path correct?", kernel_path); + } + + p.kernel_size = kernel_file->size; + + if (p.kernel_size < sizeof(struct linux_header)) { + panic(true, "linux: kernel too small to contain a valid header"); + } + + struct linux_header tmp_hdr; + fread(kernel_file, &tmp_hdr, 0, sizeof(tmp_hdr)); + + const char *reason = verify_kernel(&tmp_hdr); + if (reason) + panic(true, "linux: invalid kernel image: %s", reason); + + // Use image_size from kernel header for total memory including BSS + size_t kernel_alloc_size = p.kernel_size; + if (tmp_hdr.image_size > kernel_alloc_size) { + kernel_alloc_size = tmp_hdr.image_size; + } + + p.kernel_base = ext_mem_alloc_type_aligned( + ALIGN_UP(kernel_alloc_size, 4096), + MEMMAP_KERNEL_AND_MODULES, 2 * 1024 * 1024); + fread(kernel_file, p.kernel_base, 0, p.kernel_size); + fclose(kernel_file); + printv("linux: loaded kernel `%s` at %p, size %U\n", kernel_path, p.kernel_base, (uint64_t)p.kernel_size); + + load_module(&p, config); + + prepare_device_tree_blob(&p); + + prepare_efi_tables(&p, config); + + prepare_mmap(&p); + + jump_to_kernel(&p); +} + +#endif diff --git a/limine/common/protos/linux_x86.c b/limine/common/protos/linux_x86.c new file mode 100644 index 0000000..ec5520c --- /dev/null +++ b/limine/common/protos/linux_x86.c @@ -0,0 +1,664 @@ +#if defined (__x86_64__) || defined (__i386__) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +noreturn void linux_spinup(void *entry, void *boot_params); +#if defined (UEFI) && defined (__x86_64__) + noreturn void linux_spinup64(void *entry, void *boot_params); +#endif + +// The following definitions and struct were copied and adapted from Linux +// kernel headers released under GPL-2.0 WITH Linux-syscall-note +// allowing their inclusion in non GPL compliant code. + +#define EDD_MBR_SIG_MAX 16 +#define E820_MAX_ENTRIES_ZEROPAGE 128 +#define EDDMAXNR 6 + +struct setup_header { + uint8_t setup_sects; + uint16_t root_flags; + uint32_t syssize; + uint16_t ram_size; + uint16_t vid_mode; + uint16_t root_dev; + uint16_t boot_flag; + uint16_t jump; + uint32_t header; + uint16_t version; + uint32_t realmode_swtch; + uint16_t start_sys_seg; + uint16_t kernel_version; + uint8_t type_of_loader; + uint8_t loadflags; + uint16_t setup_move_size; + uint32_t code32_start; + uint32_t ramdisk_image; + uint32_t ramdisk_size; + uint32_t bootsect_kludge; + uint16_t heap_end_ptr; + uint8_t ext_loader_ver; + uint8_t ext_loader_type; + uint32_t cmd_line_ptr; + uint32_t initrd_addr_max; + uint32_t kernel_alignment; + uint8_t relocatable_kernel; + uint8_t min_alignment; + uint16_t xloadflags; + uint32_t cmdline_size; + uint32_t hardware_subarch; + uint64_t hardware_subarch_data; + uint32_t payload_offset; + uint32_t payload_length; + uint64_t setup_data; + uint64_t pref_address; + uint32_t init_size; + uint32_t handover_offset; + uint32_t kernel_info_offset; +} __attribute__((packed)); + +struct apm_bios_info { + uint16_t version; + uint16_t cseg; + uint32_t offset; + uint16_t cseg_16; + uint16_t dseg; + uint16_t flags; + uint16_t cseg_len; + uint16_t cseg_16_len; + uint16_t dseg_len; +}; + +struct ist_info { + uint32_t signature; + uint32_t command; + uint32_t event; + uint32_t perf_level; +}; + +struct sys_desc_table { + uint16_t length; + uint8_t table[14]; +}; + +struct olpc_ofw_header { + uint32_t ofw_magic; /* OFW signature */ + uint32_t ofw_version; + uint32_t cif_handler; /* callback into OFW */ + uint32_t irq_desc_table; +} __attribute__((packed)); + +struct edid_info { + unsigned char dummy[128]; +}; + +struct efi_info { + uint32_t efi_loader_signature; + uint32_t efi_systab; + uint32_t efi_memdesc_size; + uint32_t efi_memdesc_version; + uint32_t efi_memmap; + uint32_t efi_memmap_size; + uint32_t efi_systab_hi; + uint32_t efi_memmap_hi; +}; + +struct boot_e820_entry { + uint64_t addr; + uint64_t size; + uint32_t type; +} __attribute__((packed)); + +struct edd_device_params { + uint16_t length; + uint16_t info_flags; + uint32_t num_default_cylinders; + uint32_t num_default_heads; + uint32_t sectors_per_track; + uint64_t number_of_sectors; + uint16_t bytes_per_sector; + uint32_t dpte_ptr; /* 0xFFFFFFFF for our purposes */ + uint16_t key; /* = 0xBEDD */ + uint8_t device_path_info_length; /* = 44 */ + uint8_t reserved2; + uint16_t reserved3; + uint8_t host_bus_type[4]; + uint8_t interface_type[8]; + union { + struct { + uint16_t base_address; + uint16_t reserved1; + uint32_t reserved2; + } __attribute__ ((packed)) isa; + struct { + uint8_t bus; + uint8_t slot; + uint8_t function; + uint8_t channel; + uint32_t reserved; + } __attribute__ ((packed)) pci; + /* pcix is same as pci */ + struct { + uint64_t reserved; + } __attribute__ ((packed)) ibnd; + struct { + uint64_t reserved; + } __attribute__ ((packed)) xprs; + struct { + uint64_t reserved; + } __attribute__ ((packed)) htpt; + struct { + uint64_t reserved; + } __attribute__ ((packed)) unknown; + } interface_path; + union { + struct { + uint8_t device; + uint8_t reserved1; + uint16_t reserved2; + uint32_t reserved3; + uint64_t reserved4; + } __attribute__ ((packed)) ata; + struct { + uint8_t device; + uint8_t lun; + uint8_t reserved1; + uint8_t reserved2; + uint32_t reserved3; + uint64_t reserved4; + } __attribute__ ((packed)) atapi; + struct { + uint16_t id; + uint64_t lun; + uint16_t reserved1; + uint32_t reserved2; + } __attribute__ ((packed)) scsi; + struct { + uint64_t serial_number; + uint64_t reserved; + } __attribute__ ((packed)) usb; + struct { + uint64_t eui; + uint64_t reserved; + } __attribute__ ((packed)) i1394; + struct { + uint64_t wwid; + uint64_t lun; + } __attribute__ ((packed)) fibre; + struct { + uint64_t identity_tag; + uint64_t reserved; + } __attribute__ ((packed)) i2o; + struct { + uint32_t array_number; + uint32_t reserved1; + uint64_t reserved2; + } __attribute__ ((packed)) raid; + struct { + uint8_t device; + uint8_t reserved1; + uint16_t reserved2; + uint32_t reserved3; + uint64_t reserved4; + } __attribute__ ((packed)) sata; + struct { + uint64_t reserved1; + uint64_t reserved2; + } __attribute__ ((packed)) unknown; + } device_path; + uint8_t reserved4; + uint8_t checksum; +} __attribute__ ((packed)); + +struct edd_info { + uint8_t device; + uint8_t version; + uint16_t interface_support; + uint16_t legacy_max_cylinder; + uint8_t legacy_max_head; + uint8_t legacy_sectors_per_track; + struct edd_device_params params; +} __attribute__ ((packed)); + +struct boot_params { + struct screen_info screen_info; /* 0x000 */ + struct apm_bios_info apm_bios_info; /* 0x040 */ + uint8_t _pad2[4]; /* 0x054 */ + uint64_t tboot_addr; /* 0x058 */ + struct ist_info ist_info; /* 0x060 */ + uint64_t acpi_rsdp_addr; /* 0x070 */ + uint8_t _pad3[8]; /* 0x078 */ + uint8_t hd0_info[16]; /* obsolete! */ /* 0x080 */ + uint8_t hd1_info[16]; /* obsolete! */ /* 0x090 */ + struct sys_desc_table sys_desc_table; /* obsolete! */ /* 0x0a0 */ + struct olpc_ofw_header olpc_ofw_header; /* 0x0b0 */ + uint32_t ext_ramdisk_image; /* 0x0c0 */ + uint32_t ext_ramdisk_size; /* 0x0c4 */ + uint32_t ext_cmd_line_ptr; /* 0x0c8 */ + uint8_t _pad4[116]; /* 0x0cc */ + struct edid_info edid_info; /* 0x140 */ + struct efi_info efi_info; /* 0x1c0 */ + uint32_t alt_mem_k; /* 0x1e0 */ + uint32_t scratch; /* Scratch field! */ /* 0x1e4 */ + uint8_t e820_entries; /* 0x1e8 */ + uint8_t eddbuf_entries; /* 0x1e9 */ + uint8_t edd_mbr_sig_buf_entries; /* 0x1ea */ + uint8_t kbd_status; /* 0x1eb */ + uint8_t secure_boot; /* 0x1ec */ + uint8_t _pad5[2]; /* 0x1ed */ + /* + * The sentinel is set to a nonzero value (0xff) in header.S. + * + * A bootloader is supposed to only take setup_header and put + * it into a clean boot_params buffer. If it turns out that + * it is clumsy or too generous with the buffer, it most + * probably will pick up the sentinel variable too. The fact + * that this variable then is still 0xff will let kernel + * know that some variables in boot_params are invalid and + * kernel should zero out certain portions of boot_params. + */ + uint8_t sentinel; /* 0x1ef */ + uint8_t _pad6[1]; /* 0x1f0 */ + struct setup_header hdr; /* setup header */ /* 0x1f1 */ + uint8_t _pad7[0x290-0x1f1-sizeof(struct setup_header)]; + uint32_t edd_mbr_sig_buffer[EDD_MBR_SIG_MAX]; /* 0x290 */ + struct boot_e820_entry e820_table[E820_MAX_ENTRIES_ZEROPAGE]; /* 0x2d0 */ + uint8_t _pad8[48]; /* 0xcd0 */ + struct edd_info eddbuf[EDDMAXNR]; /* 0xd00 */ + uint8_t _pad9[276]; /* 0xeec */ +} __attribute__((packed)); + +// End of Linux code + +noreturn void linux_load(char *config, char *cmdline) { + struct file_handle *kernel_file; + + char *kernel_path = config_get_value(config, 0, "PATH"); + if (kernel_path == NULL) { + kernel_path = config_get_value(config, 0, "KERNEL_PATH"); + } + if (kernel_path == NULL) { + panic(true, "linux: Kernel path not specified"); + } + + print("linux: Loading kernel `%#`...\n", kernel_path); + + if ((kernel_file = uri_open(kernel_path)) == NULL) + panic(true, "linux: Failed to open kernel with path `%#`. Is the path correct?", kernel_path); + + // Minimum size check: need at least 0x206 bytes for signature at 0x202 + if (kernel_file->size < 0x206) { + panic(true, "linux: Kernel file too small"); + } + +#if defined (UEFI) && defined (__x86_64__) + bool use_64_bit_proto = false; +#endif + + uint32_t signature; + fread(kernel_file, &signature, 0x202, sizeof(uint32_t)); + + // validate signature + if (signature != 0x53726448) { + panic(true, "linux: Invalid kernel signature"); + } + + size_t setup_code_size = 0; + fread(kernel_file, &setup_code_size, 0x1f1, 1); + + if (setup_code_size == 0) + setup_code_size = 4; + + setup_code_size *= 512; + + size_t real_mode_code_size = 512 + setup_code_size; + + if (real_mode_code_size > kernel_file->size) { + panic(true, "linux: Kernel file too small for real mode code"); + } + + struct boot_params *boot_params = ext_mem_alloc(sizeof(struct boot_params)); + + struct setup_header *setup_header = &boot_params->hdr; + + size_t setup_header_end = ({ + uint8_t x; + fread(kernel_file, &x, 0x201, 1); + 0x202 + x; + }); + + if (setup_header_end > kernel_file->size) { + panic(true, "linux: Kernel file too small for setup header"); + } + + fread(kernel_file, setup_header, 0x1f1, setup_header_end - 0x1f1); + + printv("linux: Boot protocol: %u.%u\n", + setup_header->version >> 8, setup_header->version & 0xff); + + if (setup_header->version < 0x203) { + panic(true, "linux: Protocols < 2.03 are not supported"); + } + + setup_header->cmd_line_ptr = (uint32_t)(uintptr_t)cmdline; + + // vid_mode. 0xffff means "normal" + setup_header->vid_mode = 0xffff; + + if (verbose) { + char *kernel_version = ext_mem_alloc(128); + if (setup_header->kernel_version != 0) { + size_t version_offset = (size_t)setup_header->kernel_version + 0x200; + if (version_offset + 128 <= kernel_file->size) { + fread(kernel_file, kernel_version, version_offset, 128); + kernel_version[127] = '\0'; + print("linux: Kernel version: %s\n", kernel_version); + } + } + pmm_free(kernel_version, 128); + } + + setup_header->type_of_loader = 0xff; + + if (!(setup_header->loadflags & (1 << 0))) { + panic(true, "linux: Kernels that load at 0x10000 are not supported"); + } + + setup_header->loadflags &= ~(1 << 5); // print early messages + + // load kernel + size_t kernel_data_size = kernel_file->size - real_mode_code_size; + size_t kernel_alloc_size = kernel_data_size; + if (setup_header->version >= 0x20a && setup_header->init_size > kernel_alloc_size) { + kernel_alloc_size = setup_header->init_size; + } + uintptr_t kernel_align = 0x100000; + if (setup_header->version >= 0x205 && setup_header->kernel_alignment > kernel_align) { + kernel_align = setup_header->kernel_alignment; + } + uintptr_t kernel_load_addr = ALIGN_UP(0x100000, kernel_align); + for (;;) { + if (memmap_alloc_range(kernel_load_addr, + ALIGN_UP(kernel_alloc_size, 4096), + MEMMAP_BOOTLOADER_RECLAIMABLE, MEMMAP_USABLE, false, false, false)) + break; + + if (kernel_load_addr >= 0xfff00000) { + panic(true, "linux: Failed to allocate memory for kernel"); + } + + kernel_load_addr += kernel_align; + } + + fread(kernel_file, (void *)kernel_load_addr, real_mode_code_size, kernel_file->size - real_mode_code_size); + + fclose(kernel_file); + + /////////////////////////////////////// + // Modules + /////////////////////////////////////// + size_t size_of_all_modules = 0; + + size_t module_count; + for (module_count = 0; ; module_count++) { + char *module_path = config_get_value(config, module_count, "MODULE_PATH"); + if (module_path == NULL) + break; + } + + if (module_count == 0) { + goto no_modules; + } + + struct file_handle **modules = ext_mem_alloc(module_count * sizeof(struct file_handle *)); + + for (size_t i = 0; ; i++) { + char *module_path = config_get_value(config, i, "MODULE_PATH"); + if (module_path == NULL) + break; + + print("linux: Loading module `%#`...\n", module_path); + + struct file_handle *module; + if ((module = uri_open(module_path)) == NULL) + panic(true, "linux: Failed to open module with path `%s`. Is the path correct?", module_path); + + if (__builtin_add_overflow(size_of_all_modules, module->size, &size_of_all_modules)) { + panic(true, "linux: Total module size overflow"); + } + + modules[i] = module; + } + + uintptr_t modules_mem_base; + + if (setup_header->version <= 0x202 || setup_header->initrd_addr_max == 0) { + modules_mem_base = 0x38000000; + } else { + modules_mem_base = (uintptr_t)setup_header->initrd_addr_max + 1; + } + + if (size_of_all_modules > modules_mem_base) { + panic(true, "linux: Total module size exceeds available address space"); + } + modules_mem_base -= size_of_all_modules; + modules_mem_base = ALIGN_DOWN(modules_mem_base, 0x100000); + + for (;;) { + if (modules_mem_base < 0x100000) { +#if defined (UEFI) && defined (__x86_64__) + if ((setup_header->xloadflags & 3) == 3) { + modules_mem_base = (uintptr_t)ext_mem_alloc_type_aligned_mode( + size_of_all_modules, + MEMMAP_BOOTLOADER_RECLAIMABLE, + 0x200000, + true + ); + use_64_bit_proto = true; + break; + } +#endif + panic(true, "linux: Failed to allocate memory for modules"); + } + + if (memmap_alloc_range(modules_mem_base, ALIGN_UP(size_of_all_modules, 0x100000), + MEMMAP_BOOTLOADER_RECLAIMABLE, MEMMAP_USABLE, false, false, false)) + break; + + modules_mem_base -= 0x100000; + } + + uintptr_t _modules_mem_base = modules_mem_base; + for (size_t i = 0; ; i++) { + char *module_path = config_get_value(config, i, "MODULE_PATH"); + if (module_path == NULL) + break; + + fread(modules[i], (void *)_modules_mem_base, 0, modules[i]->size); + + _modules_mem_base += modules[i]->size; + + fclose(modules[i]); + } + + pmm_free(modules, module_count * sizeof(struct file_handle *)); + + setup_header->ramdisk_image = (uint32_t)modules_mem_base; +#if defined (UEFI) && defined (__x86_64__) + boot_params->ext_ramdisk_image = (uint32_t)(modules_mem_base >> 32); +#endif + setup_header->ramdisk_size = (uint32_t)size_of_all_modules; +#if defined (UEFI) && defined (__x86_64__) + boot_params->ext_ramdisk_size = (uint32_t)(size_of_all_modules >> 32); +#endif + +no_modules:; + + /////////////////////////////////////// + // Video + /////////////////////////////////////// + + term_notready(); + + struct screen_info *screen_info = &boot_params->screen_info; + +#if defined (BIOS) + { + char *textmode_str = config_get_value(config, 0, "TEXTMODE"); + bool textmode = textmode_str != NULL && strcmp(textmode_str, "yes") == 0; + if (textmode) { + goto set_textmode; + } + } +#endif + + size_t req_width = 0, req_height = 0, req_bpp = 0; + + char *resolution = config_get_value(config, 0, "RESOLUTION"); + if (resolution != NULL) + parse_resolution(&req_width, &req_height, &req_bpp, resolution); + + struct fb_info *fbs; + size_t fbs_count; +#if defined (UEFI) + gop_force_16 = true; +#endif + fb_init(&fbs, &fbs_count, req_width, req_height, req_bpp); + if (fbs_count == 0) { +#if defined (UEFI) + goto no_fb; +#elif defined (BIOS) +set_textmode:; + vga_textmode_init(false); + + screen_info->orig_video_mode = 3; + screen_info->orig_video_ega_bx = 3; + screen_info->orig_video_lines = 25; + screen_info->orig_video_cols = 80; + screen_info->orig_video_points = 16; + + screen_info->orig_video_isVGA = VIDEO_TYPE_VGAC; +#endif + } else { + screen_info->capabilities = VIDEO_CAPABILITY_64BIT_BASE | VIDEO_CAPABILITY_SKIP_QUIRKS; + screen_info->flags = VIDEO_FLAGS_NOCURSOR; + screen_info->lfb_base = (uint32_t)fbs[0].framebuffer_addr; + screen_info->ext_lfb_base = (uint32_t)(fbs[0].framebuffer_addr >> 32); + screen_info->lfb_size = fbs[0].framebuffer_pitch * fbs[0].framebuffer_height; + screen_info->lfb_width = fbs[0].framebuffer_width; + screen_info->lfb_height = fbs[0].framebuffer_height; + screen_info->lfb_depth = fbs[0].framebuffer_bpp; + screen_info->lfb_linelength = fbs[0].framebuffer_pitch; + screen_info->red_size = fbs[0].red_mask_size; + screen_info->red_pos = fbs[0].red_mask_shift; + screen_info->green_size = fbs[0].green_mask_size; + screen_info->green_pos = fbs[0].green_mask_shift; + screen_info->blue_size = fbs[0].blue_mask_size; + screen_info->blue_pos = fbs[0].blue_mask_shift; + + if (fbs[0].edid != NULL) { + memcpy(&boot_params->edid_info, fbs[0].edid, sizeof(struct edid_info_struct)); + } + +#if defined (BIOS) + screen_info->orig_video_isVGA = VIDEO_TYPE_VLFB; + screen_info->lfb_size = DIV_ROUNDUP(screen_info->lfb_size, 65536); +#elif defined (UEFI) + screen_info->orig_video_isVGA = VIDEO_TYPE_EFI; +#endif + } + +#if defined (UEFI) +no_fb:; +#endif + /////////////////////////////////////// + // RSDP + /////////////////////////////////////// + + boot_params->acpi_rsdp_addr = (uintptr_t)acpi_get_rsdp(); + + /////////////////////////////////////// + // UEFI + /////////////////////////////////////// +#if defined (UEFI) + efi_exit_boot_services(); + +#if defined (__x86_64__) + memcpy(&boot_params->efi_info.efi_loader_signature, "EL64", 4); +#elif defined (__i386__) + memcpy(&boot_params->efi_info.efi_loader_signature, "EL32", 4); +#endif + + boot_params->efi_info.efi_systab = (uint32_t)(uint64_t)(uintptr_t)gST; + boot_params->efi_info.efi_systab_hi = (uint32_t)((uint64_t)(uintptr_t)gST >> 32); + boot_params->efi_info.efi_memmap = (uint32_t)(uint64_t)(uintptr_t)efi_mmap; + boot_params->efi_info.efi_memmap_hi = (uint32_t)((uint64_t)(uintptr_t)efi_mmap >> 32); + boot_params->efi_info.efi_memmap_size = efi_mmap_size; + boot_params->efi_info.efi_memdesc_size = efi_desc_size; + boot_params->efi_info.efi_memdesc_version = efi_desc_ver; +#endif + + /////////////////////////////////////// + // e820 + /////////////////////////////////////// + + struct boot_e820_entry *e820_table = boot_params->e820_table; + + size_t mmap_entries; + struct memmap_entry *mmap = get_raw_memmap(&mmap_entries); + + for (size_t i = 0, j = 0; i < mmap_entries; i++) { + if (mmap[i].type >= 0x1000) { + continue; + } + if (j >= E820_MAX_ENTRIES_ZEROPAGE) { + panic(false, "linux: Too many E820 memory map entries"); + } + e820_table[j].addr = mmap[i].base; + e820_table[j].size = mmap[i].length; + e820_table[j].type = mmap[i].type; + j++; + boot_params->e820_entries = j; + } + + /////////////////////////////////////// + // Spin up + /////////////////////////////////////// + + // Commented out because Linux shouldn't need it and we don't want to + // introduce potential breakages or security weakening. + //iommu_disable_all(); + + irq_flush_type = IRQ_PIC_ONLY_FLUSH; + +#if defined (UEFI) && defined (__x86_64__) + if (use_64_bit_proto == true && (setup_header->xloadflags & 3) == 3) { + flush_irqs(); + linux_spinup64((void *)kernel_load_addr + 0x200, boot_params); + } +#endif + + common_spinup(linux_spinup, 2, (uint32_t)kernel_load_addr, + (uint32_t)(uintptr_t)boot_params); +} + +#endif diff --git a/limine/common/protos/multiboot.h b/limine/common/protos/multiboot.h new file mode 100644 index 0000000..00def66 --- /dev/null +++ b/limine/common/protos/multiboot.h @@ -0,0 +1,17 @@ +#ifndef PROTOS__MULTIBOOT_H__ +#define PROTOS__MULTIBOOT_H__ + +#include +#include + +struct mb_reloc_stub { + char jmp[4]; + uint32_t magic; + uint32_t entry_point; + uint32_t mb_info_target; +}; + +extern symbol multiboot_spinup_32; +extern symbol multiboot_reloc_stub, multiboot_reloc_stub_end; + +#endif diff --git a/limine/common/protos/multiboot1.c b/limine/common/protos/multiboot1.c new file mode 100644 index 0000000..d7c5c20 --- /dev/null +++ b/limine/common/protos/multiboot1.c @@ -0,0 +1,509 @@ +#if defined (__x86_64__) || defined (__i386__) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LIMINE_BRAND "Limine " LIMINE_VERSION + +#define MEMMAP_MAX 256 + +// Returns the size required to store the multiboot info. +static size_t get_multiboot1_info_size( + char *cmdline, + size_t modules_count, size_t modules_cmdlines_size, + uint32_t section_entry_size, uint32_t section_num +) { + return ALIGN_UP(sizeof(struct multiboot1_info), 16) + // base structure + ALIGN_UP(strlen(cmdline) + 1, 16) + // cmdline + ALIGN_UP(sizeof(LIMINE_BRAND), 16) + // bootloader brand + ALIGN_UP(section_entry_size * section_num, 16) + // ELF info + ALIGN_UP(sizeof(struct multiboot1_module) * modules_count, 16) + // modules count + ALIGN_UP(modules_cmdlines_size, 16) + // modules command lines + ALIGN_UP(sizeof(struct multiboot1_mmap_entry) * MEMMAP_MAX, 16); // memory map +} + +static void *mb1_info_alloc(void **mb1_info_raw, size_t size) { + void *ret = *mb1_info_raw; + *mb1_info_raw += ALIGN_UP(size, 16); + return ret; +} + +noreturn void multiboot1_load(char *config, char *cmdline) { + struct file_handle *kernel_file; + + char *kernel_path = config_get_value(config, 0, "PATH"); + if (kernel_path == NULL) { + kernel_path = config_get_value(config, 0, "KERNEL_PATH"); + } + if (kernel_path == NULL) { + panic(true, "multiboot1: Executable path not specified"); + } + + print("multiboot1: Loading executable `%#`...\n", kernel_path); + + if ((kernel_file = uri_open(kernel_path)) == NULL) + panic(true, "multiboot1: Failed to open executable with path `%#`. Is the path correct?", kernel_path); + + uint8_t *kernel = freadall(kernel_file, MEMMAP_KERNEL_AND_MODULES); + + size_t kernel_file_size = kernel_file->size; + + fclose(kernel_file); + + struct multiboot1_header header = {0}; + size_t header_offset = 0; + + // Per Multiboot spec, header must be within first 8192 bytes and 4-byte aligned. + // Ensure we don't read past end of file when checking magic or copying header. + size_t search_limit = 8192; + if (kernel_file_size < sizeof(struct multiboot1_header)) { + panic(true, "multiboot1: Kernel file too small to contain header"); + } + if (search_limit > kernel_file_size - sizeof(struct multiboot1_header)) { + search_limit = kernel_file_size - sizeof(struct multiboot1_header); + } + + for (header_offset = 0; header_offset <= search_limit; header_offset += 4) { + uint32_t v = *(uint32_t *)(kernel + header_offset); + + if (v == MULTIBOOT1_HEADER_MAGIC) { + memcpy(&header, kernel + header_offset, sizeof(header)); + break; + } + } + + if (header.magic != MULTIBOOT1_HEADER_MAGIC) { + panic(true, "multiboot1: Invalid magic"); + } + + if (header.magic + header.flags + header.checksum) + panic(true, "multiboot1: Header checksum is invalid"); + + bool section_hdr_info_valid = false; + struct elf_section_hdr_info section_hdr_info = {0}; + + uint64_t entry_point; + struct elsewhere_range *ranges; + uint64_t ranges_count = 1; + + if (header.flags & (1 << 16)) { + if (header.load_addr > header.header_addr) + panic(true, "multiboot1: Illegal load address"); + + size_t addr_diff = header.header_addr - header.load_addr; + if (addr_diff > header_offset) + panic(true, "multiboot1: Address tag offset underflow"); + + size_t load_src = header_offset - addr_diff; + + size_t load_size; + if (header.load_end_addr) { + if (header.load_end_addr < header.load_addr) + panic(true, "multiboot1: Load end address less than load address"); + load_size = header.load_end_addr - header.load_addr; + } else { + if (load_src > kernel_file_size) + panic(true, "multiboot1: Load source exceeds kernel file size"); + load_size = kernel_file_size - load_src; + } + + uint32_t bss_size = 0; + if (header.bss_end_addr) { + uintptr_t bss_addr = header.load_addr + load_size; + if (header.bss_end_addr < bss_addr) + panic(true, "multiboot1: Illegal bss end address"); + + bss_size = header.bss_end_addr - bss_addr; + } + + if (load_src + load_size > kernel_file_size) { + panic(true, "multiboot1: load_src + load_size exceeds kernel file size"); + } + + size_t full_size = load_size + bss_size; + + void *elsewhere = ext_mem_alloc(full_size); + + memcpy(elsewhere, kernel + load_src, load_size); + + entry_point = header.entry_addr; + + ranges = ext_mem_alloc(sizeof(struct elsewhere_range)); + + ranges->elsewhere = (uintptr_t)elsewhere; + ranges->target = header.load_addr; + ranges->length = full_size; + } else { + int bits = elf_bits(kernel); + + switch (bits) { + case 32: + if (!elf32_load_elsewhere(kernel, kernel_file_size, &entry_point, &ranges)) + panic(true, "multiboot1: ELF32 load failure"); + + section_hdr_info = elf32_section_hdr_info(kernel); + section_hdr_info_valid = true; + break; + case 64: { + if (!elf64_load_elsewhere(kernel, kernel_file_size, &entry_point, &ranges)) + panic(true, "multiboot1: ELF64 load failure"); + + section_hdr_info = elf64_section_hdr_info(kernel); + section_hdr_info_valid = true; + break; + } + default: + panic(true, "multiboot1: Invalid ELF file bitness"); + } + } + + size_t n_modules; + size_t modules_cmdlines_size = 0; + + for (n_modules = 0;; n_modules++) { + struct conf_tuple conf_tuple = config_get_tuple(config, n_modules, "MODULE_PATH", "MODULE_STRING"); + if (!conf_tuple.value1) break; + + char *module_cmdline = conf_tuple.value2; + if (!module_cmdline) module_cmdline = ""; + modules_cmdlines_size += ALIGN_UP(strlen(module_cmdline) + 1, 16); + } + + size_t mb1_info_size = get_multiboot1_info_size( + cmdline, + n_modules, + modules_cmdlines_size, + section_hdr_info_valid ? section_hdr_info.section_entry_size : 0, + section_hdr_info_valid ? section_hdr_info.num : 0 + ); + + // Realloc elsewhere ranges to include mb1 info, modules, and elf sections + struct elsewhere_range *new_ranges = ext_mem_alloc(sizeof(struct elsewhere_range) * + (ranges_count + + 1 /* mb1 info range */ + + n_modules + + (section_hdr_info_valid ? section_hdr_info.num : 0))); + + memcpy(new_ranges, ranges, sizeof(struct elsewhere_range) * ranges_count); + pmm_free(ranges, sizeof(struct elsewhere_range) * ranges_count); + ranges = new_ranges; + + // GRUB allocates boot info at 0x10000, *except* if the kernel happens + // to overlap this region, then it gets moved to right after the + // kernel, or whichever PHDR happens to sit at 0x10000. + // Allocate it wherever, then move it to where GRUB puts it + // afterwards. + + // Elsewhere append mb1 info *after* kernel but *before* modules. + void *mb1_info_raw = ext_mem_alloc(mb1_info_size); + uint64_t mb1_info_final_loc = 0x10000; + + if (!elsewhere_append(true /* flexible target */, + ranges, &ranges_count, + mb1_info_raw, &mb1_info_final_loc, mb1_info_size)) { + panic(true, "multiboot1: Cannot allocate mb1 info"); + } + + size_t mb1_info_slide = (size_t)mb1_info_raw - mb1_info_final_loc; + + struct multiboot1_info *multiboot1_info = + mb1_info_alloc(&mb1_info_raw, sizeof(struct multiboot1_info)); + + if (section_hdr_info_valid == true) { + size_t section_table_size = (size_t)section_hdr_info.section_entry_size * section_hdr_info.num; + if (section_hdr_info.section_offset > kernel_file_size || + section_table_size > kernel_file_size - section_hdr_info.section_offset) { + panic(true, "multiboot1: ELF section headers out of bounds"); + } + + multiboot1_info->elf_sect.num = section_hdr_info.num; + multiboot1_info->elf_sect.size = section_hdr_info.section_entry_size; + multiboot1_info->elf_sect.shndx = section_hdr_info.str_section_idx; + + void *sections = mb1_info_alloc(&mb1_info_raw, section_table_size); + + multiboot1_info->elf_sect.addr = (uintptr_t)sections - mb1_info_slide; + + memcpy(sections, kernel + section_hdr_info.section_offset, section_table_size); + + int bits = elf_bits(kernel); + + for (size_t i = 0; i < section_hdr_info.num; i++) { + if (bits == 64) { + struct elf64_shdr *shdr = (void *)sections + i * section_hdr_info.section_entry_size; + + if (shdr->sh_addr != 0 || shdr->sh_size == 0) { + continue; + } + + if (shdr->sh_offset > kernel_file_size || + shdr->sh_size > kernel_file_size - shdr->sh_offset) { + continue; + } + + uint64_t section = (uint64_t)-1; /* no target preference, use top */ + + if (!elsewhere_append(true /* flexible target */, + ranges, &ranges_count, + kernel + shdr->sh_offset, §ion, shdr->sh_size)) { + panic(true, "multiboot1: Cannot allocate elf sections"); + } + + shdr->sh_addr = section; + } else { + struct elf32_shdr *shdr = (void *)sections + i * section_hdr_info.section_entry_size; + + if (shdr->sh_addr != 0 || shdr->sh_size == 0) { + continue; + } + + if (shdr->sh_offset > kernel_file_size || + shdr->sh_size > kernel_file_size - shdr->sh_offset) { + continue; + } + + uint64_t section = (uint64_t)-1; /* no target preference, use top */ + + if (!elsewhere_append(true /* flexible target */, + ranges, &ranges_count, + kernel + shdr->sh_offset, §ion, shdr->sh_size)) { + panic(true, "multiboot1: Cannot allocate elf sections"); + } + + shdr->sh_addr = section; + } + } + + multiboot1_info->flags |= (1 << 5); + } + + if (n_modules) { + struct multiboot1_module *mods = + mb1_info_alloc(&mb1_info_raw, sizeof(struct multiboot1_module) * n_modules); + + multiboot1_info->mods_count = n_modules; + multiboot1_info->mods_addr = (size_t)mods - mb1_info_slide; + + for (size_t i = 0; i < n_modules; i++) { + struct multiboot1_module *m = mods + i; + + struct conf_tuple conf_tuple = config_get_tuple(config, i, "MODULE_PATH", "MODULE_STRING"); + char *module_path = conf_tuple.value1; + if (module_path == NULL) + panic(true, "multiboot1: Module disappeared unexpectedly"); + + print("multiboot1: Loading module `%#`...\n", module_path); + + struct file_handle *f; + if ((f = uri_open(module_path)) == NULL) + panic(true, "multiboot1: Failed to open module with path `%#`. Is the path correct?", module_path); + + char *module_cmdline = conf_tuple.value2; + if (module_cmdline == NULL) { + module_cmdline = ""; + } + char *lowmem_modstr = mb1_info_alloc(&mb1_info_raw, strlen(module_cmdline) + 1); + strcpy(lowmem_modstr, module_cmdline); + + void *module_addr = freadall(f, MEMMAP_BOOTLOADER_RECLAIMABLE); + uint64_t module_target = (uint64_t)-1; /* no target preference, use top */ + + if (!elsewhere_append(true /* flexible target */, + ranges, &ranges_count, + module_addr, &module_target, f->size)) { + panic(true, "multiboot1: Cannot allocate module"); + } + + m->begin = module_target; + m->end = m->begin + f->size; + m->cmdline = (uint32_t)(size_t)lowmem_modstr - mb1_info_slide; + m->pad = 0; + + fclose(f); + + if (verbose) { + print("multiboot1: Requested module %u:\n", (uint32_t)i); + print(" Path: %s\n", module_path); + print(" String: \"%s\"\n", module_cmdline ?: ""); + print(" Begin: %x\n", m->begin); + print(" End: %x\n", m->end); + } + } + + multiboot1_info->flags |= (1 << 3); + } + + char *lowmem_cmdline = mb1_info_alloc(&mb1_info_raw, strlen(cmdline) + 1); + strcpy(lowmem_cmdline, cmdline); + multiboot1_info->cmdline = (uint32_t)(size_t)lowmem_cmdline - mb1_info_slide; + if (cmdline) + multiboot1_info->flags |= (1 << 2); + + char *bootload_name = LIMINE_BRAND; + char *lowmem_bootname = mb1_info_alloc(&mb1_info_raw, strlen(bootload_name) + 1); + strcpy(lowmem_bootname, bootload_name); + + multiboot1_info->bootloader_name = (uint32_t)(size_t)lowmem_bootname - mb1_info_slide; + multiboot1_info->flags |= (1 << 9); + + term_notready(); + + size_t req_width = 0; + size_t req_height = 0; + size_t req_bpp = 0; +#if defined (BIOS) + { + char *textmode_str = config_get_value(config, 0, "TEXTMODE"); + bool textmode = textmode_str != NULL && strcmp(textmode_str, "yes") == 0; + if (textmode) { + goto textmode; + } + } +#endif + + + if (header.flags & (1 << 2)) { + req_width = header.fb_width; + req_height = header.fb_height; + req_bpp = header.fb_bpp; + + if (header.fb_mode == 0) { +#if defined (UEFI) +modeset:; +#endif + char *resolution = config_get_value(config, 0, "RESOLUTION"); + if (resolution != NULL) + parse_resolution(&req_width, &req_height, &req_bpp, resolution); + + struct fb_info *fbs; + size_t fbs_count; + fb_init(&fbs, &fbs_count, req_width, req_height, req_bpp); + if (fbs_count == 0) { +#if defined (UEFI) + goto skip_modeset; +#elif defined (BIOS) +textmode: + vga_textmode_init(false); + + multiboot1_info->fb_addr = 0xb8000; + multiboot1_info->fb_width = 80; + multiboot1_info->fb_height = 25; + multiboot1_info->fb_bpp = 16; + multiboot1_info->fb_pitch = 2 * 80; + multiboot1_info->fb_type = 2; +#endif + } else { + multiboot1_info->fb_addr = (uint64_t)fbs[0].framebuffer_addr; + multiboot1_info->fb_width = fbs[0].framebuffer_width; + multiboot1_info->fb_height = fbs[0].framebuffer_height; + multiboot1_info->fb_bpp = fbs[0].framebuffer_bpp; + multiboot1_info->fb_pitch = fbs[0].framebuffer_pitch; + multiboot1_info->fb_type = 1; + multiboot1_info->fb_red_mask_size = fbs[0].red_mask_size; + multiboot1_info->fb_red_mask_shift = fbs[0].red_mask_shift; + multiboot1_info->fb_green_mask_size = fbs[0].green_mask_size; + multiboot1_info->fb_green_mask_shift = fbs[0].green_mask_shift; + multiboot1_info->fb_blue_mask_size = fbs[0].blue_mask_size; + multiboot1_info->fb_blue_mask_shift = fbs[0].blue_mask_shift; + } + } else { +#if defined (UEFI) + print("multiboot1: Warning: Cannot use text mode with UEFI\n"); + goto modeset; +#elif defined (BIOS) + goto textmode; +#endif + } + + multiboot1_info->flags |= (1 << 12); + +#if defined (UEFI) +skip_modeset:; +#endif + } else { +#if defined (UEFI) + panic(true, "multiboot1: Cannot use text mode with UEFI."); +#elif defined (BIOS) + vga_textmode_init(false); +#endif + } + + // Load relocation stub where it won't get overwritten (hopefully) + size_t reloc_stub_size = (size_t)multiboot_reloc_stub_end - (size_t)multiboot_reloc_stub; + void *reloc_stub = ext_mem_alloc(reloc_stub_size); + memcpy(reloc_stub, multiboot_reloc_stub, reloc_stub_size); + +#if defined (UEFI) + efi_exit_boot_services(); +#endif + + size_t mb_mmap_count; + struct memmap_entry *raw_memmap = get_raw_memmap(&mb_mmap_count); + + if (mb_mmap_count > MEMMAP_MAX) { + panic(false, "multiboot1: Too many memory map entries."); + } + + size_t mb_mmap_len = mb_mmap_count * sizeof(struct multiboot1_mmap_entry); + struct multiboot1_mmap_entry *mmap = mb1_info_alloc(&mb1_info_raw, mb_mmap_len); + + // Multiboot is bad and passes raw memmap. We do the same to support it. + for (size_t i = 0; i < mb_mmap_count; i++) { + mmap[i].size = sizeof(struct multiboot1_mmap_entry) - 4; + mmap[i].addr = raw_memmap[i].base; + mmap[i].len = raw_memmap[i].length; + mmap[i].type = raw_memmap[i].type; + } + + struct meminfo memory_info = mmap_get_info(mb_mmap_count, raw_memmap); + + // Convert the uppermem and lowermem fields from bytes to + // KiB. + multiboot1_info->mem_lower = memory_info.lowermem / 1024; + multiboot1_info->mem_upper = memory_info.uppermem / 1024; + + multiboot1_info->mmap_length = mb_mmap_len; + multiboot1_info->mmap_addr = (uint32_t)(size_t)mmap - mb1_info_slide; + multiboot1_info->flags |= (1 << 0) | (1 << 6); + + if (rdmsr(0x1b) & (1 << 10)) { + if (x2apic_disable()) { + printv("multiboot1: Firmware had x2APIC enabled, reverted to xAPIC mode\n"); + } else { + printv("multiboot1: Firmware has x2APIC enabled and it could not be disabled\n"); + } + } + + iommu_disable_all(); + + irq_flush_type = IRQ_PIC_ONLY_FLUSH; + + common_spinup(multiboot_spinup_32, 6, + (uint32_t)(uintptr_t)reloc_stub, (uint32_t)0x2badb002, + (uint32_t)mb1_info_final_loc, (uint32_t)entry_point, + (uint32_t)(uintptr_t)ranges, (uint32_t)ranges_count); +} + +#endif diff --git a/limine/common/protos/multiboot1.h b/limine/common/protos/multiboot1.h new file mode 100644 index 0000000..e94e4f5 --- /dev/null +++ b/limine/common/protos/multiboot1.h @@ -0,0 +1,100 @@ +#ifndef PROTOS__MULTIBOOT1_H__ +#define PROTOS__MULTIBOOT1_H__ + +#include +#include +#include + +#define MULTIBOOT1_HEADER_MAGIC 0x1BADB002 + +struct multiboot1_header { + uint32_t magic; + uint32_t flags; + uint32_t checksum; + + uint32_t header_addr; + uint32_t load_addr; + uint32_t load_end_addr; + uint32_t bss_end_addr; + uint32_t entry_addr; + + uint32_t fb_mode; + uint32_t fb_width; + uint32_t fb_height; + uint32_t fb_bpp; +}; + +struct multiboot1_elf_sections { + uint32_t num; + uint32_t size; + uint32_t addr; + uint32_t shndx; +}; + +struct multiboot1_info { + uint32_t flags; + + uint32_t mem_lower; + uint32_t mem_upper; + + uint32_t boot_device; + + uint32_t cmdline; + + uint32_t mods_count; + uint32_t mods_addr; + + struct multiboot1_elf_sections elf_sect; + + uint32_t mmap_length; + uint32_t mmap_addr; + + uint32_t drives_length; + uint32_t drivers_addr; + + uint32_t rom_config_table; + + uint32_t bootloader_name; + + uint32_t apm_table; + + uint32_t vbe_control_info; + uint32_t vbe_mode_info; + uint16_t vbe_mode; + uint16_t vbe_interface_seg; + uint16_t vbe_interface_off; + uint16_t vbe_interface_len; + + uint64_t fb_addr; + uint32_t fb_pitch; + uint32_t fb_width; + uint32_t fb_height; + uint8_t fb_bpp; + uint8_t fb_type; + uint16_t fb_reserved; + + uint8_t fb_red_mask_shift; + uint8_t fb_red_mask_size; + uint8_t fb_green_mask_shift; + uint8_t fb_green_mask_size; + uint8_t fb_blue_mask_shift; + uint8_t fb_blue_mask_size; +}; + +struct multiboot1_module { + uint32_t begin; + uint32_t end; + uint32_t cmdline; + uint32_t pad; +}; + +struct multiboot1_mmap_entry { + uint32_t size; + uint64_t addr; + uint64_t len; + uint32_t type; +} __attribute__((packed)); + +noreturn void multiboot1_load(char *config, char *cmdline); + +#endif diff --git a/limine/common/protos/multiboot2.c b/limine/common/protos/multiboot2.c new file mode 100644 index 0000000..3acbdba --- /dev/null +++ b/limine/common/protos/multiboot2.c @@ -0,0 +1,1024 @@ +#if defined (__x86_64__) || defined (__i386__) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LIMINE_BRAND "Limine " LIMINE_VERSION + +#define MEMMAP_MAX 256 +#define EFI_MEMMAP_MAX 1024 + +/// Returns the size required to store the multiboot2 info. +static size_t get_multiboot2_info_size( + char *cmdline, + size_t modules_size, + uint32_t section_entry_size, uint32_t section_num, + uint32_t smbios_tag_size +) { + return ALIGN_UP(sizeof(struct multiboot2_start_tag), MULTIBOOT_TAG_ALIGN) + // start + ALIGN_UP(sizeof(struct multiboot_tag_string) + strlen(cmdline) + 1, MULTIBOOT_TAG_ALIGN) + // cmdline + ALIGN_UP(sizeof(struct multiboot_tag_string) + sizeof(LIMINE_BRAND), MULTIBOOT_TAG_ALIGN) + // bootloader brand + ALIGN_UP(sizeof(struct multiboot_tag_framebuffer), MULTIBOOT_TAG_ALIGN) + // framebuffer + ALIGN_UP(sizeof(struct multiboot_tag_new_acpi) + sizeof(struct rsdp), MULTIBOOT_TAG_ALIGN) + // new ACPI info + ALIGN_UP(sizeof(struct multiboot_tag_old_acpi) + 20, MULTIBOOT_TAG_ALIGN) + // old ACPI info + ALIGN_UP(sizeof(struct multiboot_tag_elf_sections) + section_entry_size * section_num, MULTIBOOT_TAG_ALIGN) + // ELF info + ALIGN_UP(modules_size, MULTIBOOT_TAG_ALIGN) + // modules + ALIGN_UP(sizeof(struct multiboot_tag_load_base_addr), MULTIBOOT_TAG_ALIGN) + // load base address + ALIGN_UP(smbios_tag_size, MULTIBOOT_TAG_ALIGN) + // SMBIOS + ALIGN_UP(sizeof(struct multiboot_tag_basic_meminfo), MULTIBOOT_TAG_ALIGN) + // basic memory info + ALIGN_UP(sizeof(struct multiboot_tag_mmap) + sizeof(struct multiboot_mmap_entry) * MEMMAP_MAX, MULTIBOOT_TAG_ALIGN) + // MMAP + #if defined (UEFI) + ALIGN_UP(sizeof(struct multiboot_tag_efi_mmap) + (efi_desc_size * EFI_MEMMAP_MAX), MULTIBOOT_TAG_ALIGN) + // EFI MMAP + #if defined (__i386__) + ALIGN_UP(sizeof(struct multiboot_tag_efi32), MULTIBOOT_TAG_ALIGN) + // EFI system table 32 + ALIGN_UP(sizeof(struct multiboot_tag_efi32_ih), MULTIBOOT_TAG_ALIGN) + // EFI image handle 32 + #elif defined (__x86_64__) + ALIGN_UP(sizeof(struct multiboot_tag_efi64), MULTIBOOT_TAG_ALIGN) + // EFI system table 64 + ALIGN_UP(sizeof(struct multiboot_tag_efi64_ih), MULTIBOOT_TAG_ALIGN) + // EFI image handle 64 + #endif + #endif + ALIGN_UP(sizeof(struct multiboot_tag_network) + DHCP_ACK_PACKET_LEN, MULTIBOOT_TAG_ALIGN) + // network info + ALIGN_UP(sizeof(struct multiboot_tag), MULTIBOOT_TAG_ALIGN); // end +} + +#define append_tag(P, TAG) do { \ + (P) += ALIGN_UP((TAG)->size, MULTIBOOT_TAG_ALIGN); \ +} while (0) + +noreturn void multiboot2_load(char *config, char* cmdline) { + struct file_handle *kernel_file; + + char *kernel_path = config_get_value(config, 0, "PATH"); + if (kernel_path == NULL) { + kernel_path = config_get_value(config, 0, "KERNEL_PATH"); + } + if (kernel_path == NULL) { + panic(true, "multiboot2: Executable path not specified"); + } + + print("multiboot2: Loading executable `%#`...\n", kernel_path); + + if ((kernel_file = uri_open(kernel_path)) == NULL) + panic(true, "multiboot2: Failed to open executable with path `%#`. Is the path correct?", kernel_path); + + uint8_t *kernel = freadall(kernel_file, MEMMAP_KERNEL_AND_MODULES); + + size_t kernel_file_size = kernel_file->size; + + fclose(kernel_file); + + struct multiboot_header *header = NULL; + + // Per Multiboot2 spec, header must be within first 32768 bytes and 8-byte aligned. + // Ensure we don't read past end of file when checking magic. + size_t search_limit = MULTIBOOT_SEARCH; + if (kernel_file_size < sizeof(struct multiboot_header)) { + panic(true, "multiboot2: Kernel file too small to contain header"); + } + if (search_limit > kernel_file_size - sizeof(struct multiboot_header)) { + search_limit = kernel_file_size - sizeof(struct multiboot_header); + } + + for (size_t header_offset = 0; header_offset <= search_limit; header_offset += MULTIBOOT_HEADER_ALIGN) { + header = (void *)(kernel + header_offset); + + if (header->magic == MULTIBOOT2_HEADER_MAGIC) { + break; + } + } + + if (header == NULL || header->magic != MULTIBOOT2_HEADER_MAGIC) { + panic(true, "multiboot2: Invalid magic"); + } + + if (header->magic + header->architecture + header->checksum + header->header_length) { + panic(true, "multiboot2: Header checksum is invalid"); + } + + if (header->architecture != MULTIBOOT_ARCHITECTURE_I386) { + panic(true, "multiboot2: Unsupported architecture %u (expected i386)", header->architecture); + } + + size_t header_offset_in_file = (uintptr_t)header - (uintptr_t)kernel; + if (header->header_length > kernel_file_size - header_offset_in_file) { + panic(true, "multiboot2: Header length exceeds kernel file size"); + } + + struct multiboot_header_tag_address *addresstag = NULL; + struct multiboot_header_tag_framebuffer *fbtag = NULL; + + bool has_reloc_header = false; + struct multiboot_header_tag_relocatable reloc_tag = {0}; + + bool is_new_acpi_required = false; + bool is_old_acpi_required = false; + + bool is_elf_info_requested = false; + +#if defined (UEFI) + bool is_framebuffer_required = false; +#endif + + uint64_t entry_point = 0xffffffff; + + // Iterate through the entries... + for (struct multiboot_header_tag *tag = (struct multiboot_header_tag*)(header + 1); // header + 1 to skip the header struct. + tag < (struct multiboot_header_tag *)((uintptr_t)header + header->header_length) && tag->type != MULTIBOOT_HEADER_TAG_END; + tag = (struct multiboot_header_tag *)((uintptr_t)tag + ALIGN_UP(tag->size, MULTIBOOT_TAG_ALIGN))) { + if (tag->size == 0) { + break; + } + bool is_required = !(tag->flags & MULTIBOOT_HEADER_TAG_OPTIONAL); + switch (tag->type) { + case MULTIBOOT_HEADER_TAG_INFORMATION_REQUEST: { + // Iterate the requests and check if they are supported by or not. + struct multiboot_header_tag_information_request *request = (void *)tag; + if (request->size < sizeof(struct multiboot_header_tag_information_request)) { + panic(true, "multiboot2: Invalid information request tag size"); + } + uint32_t size = (request->size - sizeof(struct multiboot_header_tag_information_request)) / sizeof(uint32_t); + + for (uint32_t i = 0; i < size; i++) { + uint32_t r = request->requests[i]; + + switch (r) { + // We already support the following requests: + case MULTIBOOT_TAG_TYPE_CMDLINE: + case MULTIBOOT_TAG_TYPE_BOOT_LOADER_NAME: + case MULTIBOOT_TAG_TYPE_MODULE: + case MULTIBOOT_TAG_TYPE_MMAP: + case MULTIBOOT_TAG_TYPE_SMBIOS: + case MULTIBOOT_TAG_TYPE_BASIC_MEMINFO: + break; + #if defined (UEFI) + case MULTIBOOT_TAG_TYPE_EFI_MMAP: + break; + case MULTIBOOT_TAG_TYPE_EFI32: + case MULTIBOOT_TAG_TYPE_EFI32_IH: + case MULTIBOOT_TAG_TYPE_EFI64: + case MULTIBOOT_TAG_TYPE_EFI64_IH: + break; + #endif + case MULTIBOOT_TAG_TYPE_FRAMEBUFFER: + #if defined (UEFI) + is_framebuffer_required = is_required; + #endif + break; + case MULTIBOOT_TAG_TYPE_ACPI_NEW: + is_new_acpi_required = is_required; + break; + case MULTIBOOT_TAG_TYPE_ACPI_OLD: + is_old_acpi_required = is_required; + break; + case MULTIBOOT_TAG_TYPE_ELF_SECTIONS: + is_elf_info_requested = is_required; + break; + case MULTIBOOT_TAG_TYPE_LOAD_BASE_ADDR: + case MULTIBOOT_TAG_TYPE_NETWORK: + break; + default: + if (is_required) + panic(true, "multiboot2: Requested tag `%d` which is not supported", r); + break; + } + } + break; + } + case MULTIBOOT_HEADER_TAG_CONSOLE_FLAGS: { +#if defined (UEFI) + struct multiboot_header_tag_console_flags *flags = (void *)tag; + if ((flags->console_flags & (1 << 1)) && (flags->console_flags & (1 << 0))) { + panic(true, "multiboot2: OS requested EGA text mode, but UEFI does not support it"); + } +#endif + break; + } + case MULTIBOOT_HEADER_TAG_FRAMEBUFFER: { + fbtag = (void *)tag; + break; + } + case MULTIBOOT_HEADER_TAG_ENTRY_ADDRESS: { + struct multiboot_header_tag_entry_address *entrytag = (void *)tag; + entry_point = entrytag->entry_addr; + break; + } + case MULTIBOOT_HEADER_TAG_ADDRESS: { + addresstag = (void *)tag; + break; + } + // We always align the modules ;^) + case MULTIBOOT_HEADER_TAG_MODULE_ALIGN: + break; + case MULTIBOOT_HEADER_TAG_EFI_BS: + print("multiboot2: Warning: EFI_BS tag requested but not supported\n"); + if (is_required) { + panic(true, "multiboot2: EFI_BS tag is required but not supported"); + } + break; + + case MULTIBOOT_HEADER_TAG_RELOCATABLE: { + has_reloc_header = true; + struct multiboot_header_tag_relocatable *reloc_tag_ptr = (void *)tag; + reloc_tag = *reloc_tag_ptr; + break; + } + case MULTIBOOT_HEADER_TAG_ENTRY_ADDRESS_EFI64: { + // Ignore EFI64 entry address tag as we do not support it. + break; + } + + default: + if (is_required) { + if (tag->type <= 10 /* max specified ID */) { + panic(true, "multiboot2: Unsupported header tag type: %u\n", tag->type); + } else { + panic(true, "multiboot2: Unknown custom header tag type: %u\n", tag->type); + } + } else { + if (tag->type > 10) { + print("multiboot2: Unknown custom header tag type: %u\n", tag->type); + } + } + } + } + + bool section_hdr_info_valid = false; + struct elf_section_hdr_info section_hdr_info = {0}; + + struct elsewhere_range *ranges; + uint64_t ranges_count = 1; + + if (addresstag != NULL) { + size_t header_offset = (size_t)header - (size_t)kernel; + + uintptr_t load_src, load_addr; + if (addresstag->load_addr != (uint32_t)-1) { + if (addresstag->load_addr > addresstag->header_addr) { + panic(true, "multiboot2: Illegal load address"); + } + + size_t addr_diff = addresstag->header_addr - addresstag->load_addr; + if (addr_diff > header_offset) { + panic(true, "multiboot2: Address tag offset underflow"); + } + load_src = header_offset - addr_diff; + load_addr = addresstag->load_addr; + } else { + if (header_offset > addresstag->header_addr) { + panic(true, "multiboot2: Header offset exceeds header address"); + } + load_src = 0; + load_addr = addresstag->header_addr - header_offset; + } + + size_t load_size; + if (addresstag->load_end_addr != 0) { + if (addresstag->load_end_addr < load_addr) { + panic(true, "multiboot2: Load end address less than load address"); + } + load_size = addresstag->load_end_addr - load_addr; + } else { + if (load_src > kernel_file_size) { + panic(true, "multiboot2: Load source exceeds kernel file size"); + } + load_size = kernel_file_size - load_src; + } + + size_t bss_size = 0; + if (addresstag->bss_end_addr != 0) { + uintptr_t bss_addr = load_addr + load_size; + if (addresstag->bss_end_addr < bss_addr) { + panic(true, "multiboot2: Illegal bss end address"); + } + + bss_size = addresstag->bss_end_addr - bss_addr; + } + + if (load_src + load_size > kernel_file_size) { + panic(true, "multiboot2: load_src + load_size exceeds kernel file size"); + } + + size_t full_size = load_size + bss_size; + + void *elsewhere = ext_mem_alloc(full_size); + + memcpy(elsewhere, kernel + load_src, load_size); + + if (entry_point == 0xffffffff) { + panic(true, "multiboot2: Using address tag but entry address tag missing"); + } + + ranges = ext_mem_alloc(sizeof(struct elsewhere_range)); + + ranges->elsewhere = (uintptr_t)elsewhere; + ranges->target = load_addr; + ranges->length = full_size; + } else { + uint64_t e; + int bits = elf_bits(kernel); + + switch (bits) { + case 32: + if (!elf32_load_elsewhere(kernel, kernel_file_size, &e, &ranges)) + panic(true, "multiboot2: ELF32 load failure"); + + section_hdr_info = elf32_section_hdr_info(kernel); + section_hdr_info_valid = true; + break; + case 64: { + if (!elf64_load_elsewhere(kernel, kernel_file_size, &e, &ranges)) + panic(true, "multiboot2: ELF64 load failure"); + + section_hdr_info = elf64_section_hdr_info(kernel); + section_hdr_info_valid = true; + break; + } + default: + panic(true, "multiboot2: Invalid ELF file bitness"); + } + + if (entry_point == 0xffffffff) { + entry_point = e; + } + } + + int64_t reloc_slide = 0; + + if (has_reloc_header) { + if (reloc_tag.align == 0) { + panic(true, "multiboot2: Relocatable tag has align=0"); + } + + bool reloc_ascend; + uint64_t relocated_base; + + switch (reloc_tag.preference) { + default: + case 0: case 1: // No preference / prefer lowest + reloc_ascend = true; + relocated_base = ALIGN_UP(reloc_tag.min_addr, reloc_tag.align); + if (relocated_base + ranges->length > reloc_tag.max_addr) { + goto reloc_fail; + } + break; + case 2: // Prefer highest + reloc_ascend = false; + if (ranges->length > reloc_tag.max_addr) { + goto reloc_fail; + } + relocated_base = ALIGN_DOWN(reloc_tag.max_addr - ranges->length, reloc_tag.align); + if (relocated_base < reloc_tag.min_addr) { + goto reloc_fail; + } + break; + } + + for (;;) { + if (check_usable_memory(relocated_base, relocated_base + ranges->length)) { + break; + } + + if (reloc_ascend) { + relocated_base += reloc_tag.align; + if (relocated_base + ranges->length > reloc_tag.max_addr) { + goto reloc_fail; + } + } else { + if (relocated_base - reloc_tag.min_addr < reloc_tag.align) { + goto reloc_fail; + } + relocated_base -= reloc_tag.align; + } + } + + reloc_slide = (int64_t)relocated_base - ranges->target; + + entry_point += reloc_slide; + + ranges->target = relocated_base; + } + + if (!check_usable_memory(ranges->target, ranges->target + ranges->length)) { +reloc_fail: + panic(true, "multiboot2: Could not find viable load address for executable"); + } + + // Get the load base address (AKA the lowest target in the ranges) + uint64_t load_base_addr = ranges->target; + + size_t modules_size = 0; + size_t n_modules; + + for (n_modules = 0;; n_modules++) { + struct conf_tuple conf_tuple = config_get_tuple(config, n_modules, "MODULE_PATH", "MODULE_STRING"); + if (!conf_tuple.value1) break; + + char *module_cmdline = conf_tuple.value2; + if (!module_cmdline) module_cmdline = ""; + modules_size += ALIGN_UP(sizeof(struct multiboot_tag_module) + strlen(module_cmdline) + 1, MULTIBOOT_TAG_ALIGN); + } + + struct smbios_entry_point_32* smbios_entry_32 = NULL; + struct smbios_entry_point_64* smbios_entry_64 = NULL; + + acpi_get_smbios((void **)&smbios_entry_32, (void **)&smbios_entry_64); + + uint32_t smbios_tag_size = 0; + + if (smbios_entry_32 != NULL) + smbios_tag_size += ALIGN_UP(sizeof(struct multiboot_tag_smbios) + smbios_entry_32->length, MULTIBOOT_TAG_ALIGN); + if (smbios_entry_64 != NULL) + smbios_tag_size += ALIGN_UP(sizeof(struct multiboot_tag_smbios) + smbios_entry_64->length, MULTIBOOT_TAG_ALIGN); + + size_t mb2_info_size = get_multiboot2_info_size( + cmdline, + modules_size, + section_hdr_info_valid ? section_hdr_info.section_entry_size : 0, + section_hdr_info_valid ? section_hdr_info.num : 0, + smbios_tag_size + ); + + size_t info_idx = 0; + + // Realloc elsewhere ranges to include mb2 info, modules, and elf sections + struct elsewhere_range *new_ranges = ext_mem_alloc(sizeof(struct elsewhere_range) * + (ranges_count + + 1 /* mb2 info range */ + + n_modules + + (section_hdr_info_valid ? section_hdr_info.num : 0))); + + memcpy(new_ranges, ranges, sizeof(struct elsewhere_range) * ranges_count); + pmm_free(ranges, sizeof(struct elsewhere_range) * ranges_count); + ranges = new_ranges; + + // GRUB allocates boot info at 0x10000, *except* if the kernel happens + // to overlap this region, then it gets moved to right after the + // kernel, or whichever PHDR happens to sit at 0x10000. + // Allocate it wherever, then move it to where GRUB puts it + // afterwards. + + // Elsewhere append mb2 info *after* kernel but *before* modules. + uint8_t *mb2_info = ext_mem_alloc(mb2_info_size); + uint64_t mb2_info_final_loc = 0x10000; + + if (!elsewhere_append(true /* flexible target */, + ranges, &ranges_count, + mb2_info, &mb2_info_final_loc, mb2_info_size)) { + panic(true, "multiboot2: Cannot allocate mb2 info"); + } + + struct multiboot2_start_tag *mbi_start = (struct multiboot2_start_tag *)mb2_info; + info_idx += sizeof(struct multiboot2_start_tag); + + ////////////////////////////////////////////// + // Create ELF info tag + ////////////////////////////////////////////// + if (section_hdr_info_valid == false) { + if (is_elf_info_requested) { + panic(true, "multiboot2: Cannot return ELF file information"); + } + } else { + size_t section_table_size = (size_t)section_hdr_info.section_entry_size * section_hdr_info.num; + if (section_hdr_info.section_offset > kernel_file_size || + section_table_size > kernel_file_size - section_hdr_info.section_offset) { + panic(true, "multiboot2: ELF section headers out of bounds"); + } + + uint32_t size = sizeof(struct multiboot_tag_elf_sections) + section_table_size; + struct multiboot_tag_elf_sections *tag = (struct multiboot_tag_elf_sections*)(mb2_info + info_idx); + + tag->type = MULTIBOOT_TAG_TYPE_ELF_SECTIONS; + tag->size = size; + + tag->num = section_hdr_info.num; + tag->entsize = section_hdr_info.section_entry_size; + tag->shndx = section_hdr_info.str_section_idx; + + memcpy(tag->sections, kernel + section_hdr_info.section_offset, section_table_size); + + int bits = elf_bits(kernel); + + if ((bits == 64 && section_hdr_info.section_entry_size < sizeof(struct elf64_shdr)) || + (bits == 32 && section_hdr_info.section_entry_size < sizeof(struct elf32_shdr))) { + panic(true, "multiboot2: ELF section entry size too small"); + } + + for (size_t i = 0; i < section_hdr_info.num; i++) { + if (bits == 64) { + struct elf64_shdr *shdr = (void *)tag->sections + i * section_hdr_info.section_entry_size; + + if (shdr->sh_addr != 0) { + shdr->sh_addr += reloc_slide; + continue; + } + if (shdr->sh_size == 0) { + continue; + } + + if (shdr->sh_offset > kernel_file_size || + shdr->sh_size > kernel_file_size - shdr->sh_offset) { + continue; + } + + uint64_t section = (uint64_t)-1; /* no target preference, use top */ + + if (!elsewhere_append(true /* flexible target */, + ranges, &ranges_count, + kernel + shdr->sh_offset, §ion, shdr->sh_size)) { + panic(true, "multiboot2: Cannot allocate elf sections"); + } + + shdr->sh_addr = section; + } else { + struct elf32_shdr *shdr = (void *)tag->sections + i * section_hdr_info.section_entry_size; + + if (shdr->sh_addr != 0) { + shdr->sh_addr += (int32_t)reloc_slide; + continue; + } + if (shdr->sh_size == 0) { + continue; + } + + if (shdr->sh_offset > kernel_file_size || + shdr->sh_size > kernel_file_size - shdr->sh_offset) { + continue; + } + + uint64_t section = (uint64_t)-1; /* no target preference, use top */ + + if (!elsewhere_append(true /* flexible target */, + ranges, &ranges_count, + kernel + shdr->sh_offset, §ion, shdr->sh_size)) { + panic(true, "multiboot2: Cannot allocate elf sections"); + } + + shdr->sh_addr = section; + } + } + + append_tag(info_idx, tag); + } + + ////////////////////////////////////////////// + // Create load base address tag + ////////////////////////////////////////////// + if (has_reloc_header) { + uint32_t size = sizeof(struct multiboot_tag_load_base_addr); + struct multiboot_tag_load_base_addr *tag = (void *)(mb2_info + info_idx); + + tag->type = MULTIBOOT_TAG_TYPE_LOAD_BASE_ADDR; + tag->size = size; + + tag->load_base_addr = load_base_addr; + + append_tag(info_idx, tag); + } + + ////////////////////////////////////////////// + // Create modules tag + ////////////////////////////////////////////// + for (size_t i = 0; i < n_modules; i++) { + struct conf_tuple conf_tuple = config_get_tuple(config, i, "MODULE_PATH", "MODULE_STRING"); + char *module_path = conf_tuple.value1; + if (!module_path) panic(true, "multiboot2: Module disappeared unexpectedly"); + + print("multiboot2: Loading module `%#`...\n", module_path); + + struct file_handle *f; + if ((f = uri_open(module_path)) == NULL) + panic(true, "multiboot2: Failed to open module with path `%#`. Is the path correct?", module_path); + + // Module commandline can be null, so we guard against that and make the + // string "". + char *module_cmdline = conf_tuple.value2; + if (!module_cmdline) module_cmdline = ""; + + void *module_addr = freadall(f, MEMMAP_BOOTLOADER_RECLAIMABLE); + uint64_t module_target = (uint64_t)-1; + + if (!elsewhere_append(true /* flexible target */, + ranges, &ranges_count, + module_addr, &module_target, f->size)) { + panic(true, "multiboot2: Cannot allocate module"); + } + + struct multiboot_tag_module *module_tag = (struct multiboot_tag_module *)(mb2_info + info_idx); + + module_tag->type = MULTIBOOT_TAG_TYPE_MODULE; + module_tag->size = sizeof(struct multiboot_tag_module) + strlen(module_cmdline) + 1; + module_tag->mod_start = module_target; + module_tag->mod_end = module_tag->mod_start + f->size; + strcpy(module_tag->cmdline, module_cmdline); // Copy over the command line + + fclose(f); + + if (verbose) { + print("multiboot2: Requested module %u:\n", (uint32_t)i); + print(" Path: %s\n", module_path); + print(" String: \"%s\"\n", module_cmdline ?: ""); + print(" Begin: %x\n", module_tag->mod_start); + print(" End: %x\n", module_tag->mod_end); + } + + append_tag(info_idx, module_tag); + } + + ////////////////////////////////////////////// + // Create command line tag + ////////////////////////////////////////////// + { + uint32_t size = sizeof(struct multiboot_tag_string) + strlen(cmdline) + 1; + struct multiboot_tag_string *tag = (struct multiboot_tag_string *)(mb2_info + info_idx); + + tag->type = MULTIBOOT_TAG_TYPE_CMDLINE; + tag->size = size; + + strcpy(tag->string, cmdline); + append_tag(info_idx, tag); + } + + ////////////////////////////////////////////// + // Create bootloader name tag + ////////////////////////////////////////////// + { + uint32_t size = sizeof(struct multiboot_tag_string) + sizeof(LIMINE_BRAND); + struct multiboot_tag_string *tag = (struct multiboot_tag_string *)(mb2_info + info_idx); + + tag->type = MULTIBOOT_TAG_TYPE_BOOT_LOADER_NAME; + tag->size = size; + + strcpy(tag->string, LIMINE_BRAND); + append_tag(info_idx, tag); + } + + ////////////////////////////////////////////// + // Create EFI image handle tag + ////////////////////////////////////////////// +#if defined (UEFI) + { + #if defined (__i386__) + struct multiboot_tag_efi32_ih *tag = (struct multiboot_tag_efi32_ih *)(mb2_info + info_idx); + + tag->type = MULTIBOOT_TAG_TYPE_EFI32_IH; + tag->size = sizeof(struct multiboot_tag_efi32_ih); + #elif defined (__x86_64__) + struct multiboot_tag_efi64_ih *tag = (struct multiboot_tag_efi64_ih *)(mb2_info + info_idx); + + tag->type = MULTIBOOT_TAG_TYPE_EFI64_IH; + tag->size = sizeof(struct multiboot_tag_efi64_ih); + #endif + + tag->pointer = (uintptr_t)efi_image_handle; + append_tag(info_idx, tag); + } +#endif + + ////////////////////////////////////////////// + // Create framebuffer tag + ////////////////////////////////////////////// + { + struct multiboot_tag_framebuffer *tag = (struct multiboot_tag_framebuffer *)(mb2_info + info_idx); + + tag->common.type = MULTIBOOT_TAG_TYPE_FRAMEBUFFER; + + term_notready(); + + size_t req_width = 0; + size_t req_height = 0; + size_t req_bpp = 0; +#if defined (BIOS) + { + char *textmode_str = config_get_value(config, 0, "TEXTMODE"); + bool textmode = textmode_str != NULL && strcmp(textmode_str, "yes") == 0; + if (textmode) { + goto textmode; + } + } +#endif + + if (fbtag) { + req_width = fbtag->width; + req_height = fbtag->height; + req_bpp = fbtag->depth; + +#if defined (UEFI) +modeset:; +#endif + char *resolution = config_get_value(config, 0, "RESOLUTION"); + if (resolution != NULL) + parse_resolution(&req_width, &req_height, &req_bpp, resolution); + + struct fb_info *fbs; + size_t fbs_count; + fb_init(&fbs, &fbs_count, req_width, req_height, req_bpp); + if (fbs_count == 0) { +#if defined (BIOS) +textmode: + vga_textmode_init(false); + + tag->common.framebuffer_addr = 0xb8000; + tag->common.framebuffer_pitch = 2 * 80; + tag->common.framebuffer_width = 80; + tag->common.framebuffer_height = 25; + tag->common.framebuffer_bpp = 16; + tag->common.framebuffer_type = MULTIBOOT_FRAMEBUFFER_TYPE_EGA_TEXT; + tag->common.size = sizeof(struct multiboot_tag_framebuffer_common); +#elif defined (UEFI) + if (is_framebuffer_required) { + panic(true, "multiboot2: Failed to set video mode"); + } else { + goto skip_modeset; + } +#endif + } else { + tag->common.framebuffer_addr = fbs[0].framebuffer_addr; + tag->common.framebuffer_pitch = fbs[0].framebuffer_pitch; + tag->common.framebuffer_width = fbs[0].framebuffer_width; + tag->common.framebuffer_height = fbs[0].framebuffer_height; + tag->common.framebuffer_bpp = fbs[0].framebuffer_bpp; + tag->common.framebuffer_type = MULTIBOOT_FRAMEBUFFER_TYPE_RGB; // We only support RGB for VBE + tag->common.size = sizeof(struct multiboot_tag_framebuffer); + + tag->framebuffer_red_field_position = fbs[0].red_mask_shift; + tag->framebuffer_red_mask_size = fbs[0].red_mask_size; + tag->framebuffer_green_field_position = fbs[0].green_mask_shift; + tag->framebuffer_green_mask_size = fbs[0].green_mask_size; + tag->framebuffer_blue_field_position = fbs[0].blue_mask_shift; + tag->framebuffer_blue_mask_size = fbs[0].blue_mask_size; + } + } else { +#if defined (UEFI) + print("multiboot2: Warning: Cannot use text mode with UEFI\n"); + goto modeset; +#elif defined (BIOS) + goto textmode; +#endif + } + + append_tag(info_idx, &tag->common); + +#if defined (UEFI) +skip_modeset:; +#endif + } + + ////////////////////////////////////////////// + // Create new ACPI info tag + ////////////////////////////////////////////// + { + void *new_rsdp = acpi_get_rsdp_v2(); + + if (new_rsdp != NULL) { + uint32_t size = sizeof(struct multiboot_tag_new_acpi) + sizeof(struct rsdp); // XSDP is 36 bytes wide + struct multiboot_tag_new_acpi *tag = (struct multiboot_tag_new_acpi *)(mb2_info + info_idx); + + tag->type = MULTIBOOT_TAG_TYPE_ACPI_NEW; + tag->size = size; + + memcpy(tag->rsdp, new_rsdp, sizeof(struct rsdp)); + append_tag(info_idx, tag); + } else if (is_new_acpi_required) { + panic(true, "multiboot2: XSDP requested but not found"); + } + } + + ////////////////////////////////////////////// + // Create old ACPI info tag + ////////////////////////////////////////////// + { + void *old_rsdp = acpi_get_rsdp_v1(); + + if (old_rsdp != NULL) { + uint32_t size = sizeof(struct multiboot_tag_old_acpi) + 20; // RSDP is 20 bytes wide + struct multiboot_tag_old_acpi *tag = (struct multiboot_tag_old_acpi *)(mb2_info + info_idx); + + tag->type = MULTIBOOT_TAG_TYPE_ACPI_OLD; + tag->size = size; + + memcpy(tag->rsdp, old_rsdp, 20); + append_tag(info_idx, tag); + } else if (is_old_acpi_required) { + panic(true, "multiboot2: RSDP requested but not found"); + } + } + + ////////////////////////////////////////////// + // Create SMBIOS tag + ////////////////////////////////////////////// + { + // NOTE: The multiboot2 specification does not say anything about if both + // smbios 32 and 64 bit entry points are present, then we pass both of them + smbios + // support for grub2 is unimplemented. So, we are going to assume they expect us to + // pass both of them if available. Oh well... + if (smbios_entry_32 != NULL) { + struct multiboot_tag_smbios *tag = (struct multiboot_tag_smbios *)(mb2_info + info_idx); + + tag->type = MULTIBOOT_TAG_TYPE_SMBIOS; + tag->size = sizeof(struct multiboot_tag_smbios) + smbios_entry_32->length; + + tag->major = smbios_entry_32->major_version; + tag->minor = smbios_entry_32->minor_version; + + memset(tag->reserved, 0, 6); + memcpy(tag->tables, smbios_entry_32, smbios_entry_32->length); + + append_tag(info_idx, tag); + } + + if (smbios_entry_64 != NULL) { + struct multiboot_tag_smbios *tag = (struct multiboot_tag_smbios *)(mb2_info + info_idx); + + tag->type = MULTIBOOT_TAG_TYPE_SMBIOS; + tag->size = sizeof(struct multiboot_tag_smbios) + smbios_entry_64->length; + + tag->major = smbios_entry_64->major_version; + tag->minor = smbios_entry_64->minor_version; + + memset(tag->reserved, 0, 6); + memcpy(tag->tables, smbios_entry_64, smbios_entry_64->length); + + append_tag(info_idx, tag); + } + } + + ////////////////////////////////////////////// + // Create EFI system table info tag + ////////////////////////////////////////////// +#if defined (UEFI) + { + #if defined (__i386__) + uint32_t size = sizeof(struct multiboot_tag_efi32); + struct multiboot_tag_efi32 *tag = (void *)(mb2_info + info_idx); + + tag->type = MULTIBOOT_TAG_TYPE_EFI32; + #elif defined (__x86_64__) + uint32_t size = sizeof(struct multiboot_tag_efi64); + struct multiboot_tag_efi64 *tag = (void *)(mb2_info + info_idx); + + tag->type = MULTIBOOT_TAG_TYPE_EFI64; + #endif + + tag->size = size; + tag->pointer = (uintptr_t)gST; + + append_tag(info_idx, tag); + } +#endif + + // Load relocation stub where it won't get overwritten (hopefully) + size_t reloc_stub_size = (size_t)multiboot_reloc_stub_end - (size_t)multiboot_reloc_stub; + void *reloc_stub = ext_mem_alloc(reloc_stub_size); + memcpy(reloc_stub, multiboot_reloc_stub, reloc_stub_size); + +#if defined (UEFI) + efi_exit_boot_services(); +#endif + + size_t mb_mmap_count; + struct memmap_entry *raw_memmap = get_raw_memmap(&mb_mmap_count); + + ////////////////////////////////////////////// + // Create memory map tag + ////////////////////////////////////////////// + { + if (mb_mmap_count > MEMMAP_MAX) { + panic(false, "multiboot2: too many memory map entries"); + } + + // Create the normal memory map tag. + uint32_t mmap_size = sizeof(struct multiboot_tag_mmap) + sizeof(struct multiboot_mmap_entry) * mb_mmap_count; + struct multiboot_tag_mmap *mmap_tag = (struct multiboot_tag_mmap *)(mb2_info + info_idx); + + mmap_tag->type = MULTIBOOT_TAG_TYPE_MMAP; + mmap_tag->entry_size = sizeof(struct multiboot_mmap_entry); + mmap_tag->entry_version = 0; + mmap_tag->size = mmap_size; + + for (size_t i = 0; i < mb_mmap_count; i++) { + struct multiboot_mmap_entry *entry = &mmap_tag->entries[i]; + entry->addr = raw_memmap[i].base; + entry->len = raw_memmap[i].length; + entry->type = raw_memmap[i].type; + entry->zero = 0; + } + + append_tag(info_idx, mmap_tag); + } + + ////////////////////////////////////////////// + // Create basic memory info tag + ////////////////////////////////////////////// + { + struct meminfo meminfo = mmap_get_info(mb_mmap_count, raw_memmap); + struct multiboot_tag_basic_meminfo *tag = (struct multiboot_tag_basic_meminfo *)(mb2_info + info_idx); + + tag->type = MULTIBOOT_TAG_TYPE_BASIC_MEMINFO; + tag->size = sizeof(struct multiboot_tag_basic_meminfo); + + // Convert the uppermem and lowermem fields from bytes to + // KiB. + tag->mem_upper = (uint32_t)(meminfo.uppermem / 1024); + tag->mem_lower = (uint32_t)(meminfo.lowermem / 1024); + + append_tag(info_idx, tag); + } + + ////////////////////////////////////////////// + // Create EFI memory map tag + ////////////////////////////////////////////// +#if defined (UEFI) + { + if ((efi_mmap_size / efi_desc_size) > EFI_MEMMAP_MAX) { + panic(false, "multiboot2: too many EFI memory map entries"); + } + + // Create the EFI memory map tag. + uint32_t size = sizeof(struct multiboot_tag_efi_mmap) + efi_mmap_size; + struct multiboot_tag_efi_mmap *mmap_tag = (struct multiboot_tag_efi_mmap *)(mb2_info + info_idx); + + mmap_tag->type = MULTIBOOT_TAG_TYPE_EFI_MMAP; + mmap_tag->descr_vers = efi_desc_ver; + mmap_tag->descr_size = efi_desc_size; + mmap_tag->size = size; + + // Copy over the EFI memory map. + memcpy(mmap_tag->efi_mmap, efi_mmap, efi_mmap_size); + append_tag(info_idx, mmap_tag); + } +#endif + + ////////////////////////////////////////////// + // Create network info tag + ////////////////////////////////////////////// + { + if (cached_dhcp_ack_valid) { + struct multiboot_tag_network *tag = (struct multiboot_tag_network *)(mb2_info + info_idx); + + tag->type = MULTIBOOT_TAG_TYPE_NETWORK; + tag->size = sizeof(struct multiboot_tag_network) + DHCP_ACK_PACKET_LEN; + + // Copy over the DHCP packet. + memcpy(tag->dhcpack, cached_dhcp_packet, DHCP_ACK_PACKET_LEN); + append_tag(info_idx, tag); + } + } + + ////////////////////////////////////////////// + // Create end tag + ////////////////////////////////////////////// + { + struct multiboot_tag *end_tag = (struct multiboot_tag *)(mb2_info + info_idx); + end_tag->type = MULTIBOOT_TAG_TYPE_END; + end_tag->size = sizeof(struct multiboot_tag); + + append_tag(info_idx, end_tag); + } + + mbi_start->size = info_idx; + mbi_start->reserved = 0x00; + + if (rdmsr(0x1b) & (1 << 10)) { + if (x2apic_disable()) { + printv("multiboot2: Firmware had x2APIC enabled, reverted to xAPIC mode\n"); + } else { + printv("multiboot2: Firmware has x2APIC enabled and it could not be disabled\n"); + } + } + + iommu_disable_all(); + + irq_flush_type = IRQ_PIC_ONLY_FLUSH; + + common_spinup(multiboot_spinup_32, 6, + (uint32_t)(uintptr_t)reloc_stub, (uint32_t)0x36d76289, + (uint32_t)mb2_info_final_loc, (uint32_t)entry_point, + (uint32_t)(uintptr_t)ranges, (uint32_t)ranges_count); +} + +#endif diff --git a/limine/common/protos/multiboot2.h b/limine/common/protos/multiboot2.h new file mode 100644 index 0000000..1965a7f --- /dev/null +++ b/limine/common/protos/multiboot2.h @@ -0,0 +1,418 @@ +/* multiboot2.h - Multiboot 2 header file. Copied from the multiboot2 specs / +/ Copyright (C) 1999,2003,2007,2008,2009,2010 Free Software Foundation, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL ANY + * DEVELOPER OR DISTRIBUTOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR + * IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef PROTOS__MULTIBOOT2_H__ +#define PROTOS__MULTIBOOT2_H__ + +#include +#include + +noreturn void multiboot2_load(char *config, char *cmdline); + +/* How many bytes from the start of the file we search for the header. */ +#define MULTIBOOT_SEARCH 32768 +#define MULTIBOOT_HEADER_ALIGN 8 + +/* The magic field should contain this. */ +#define MULTIBOOT2_HEADER_MAGIC 0xe85250d6 + +/* This should be in %eax. */ +#define MULTIBOOT2_BOOTLOADER_MAGIC 0x36d76289 + +/* Alignment of multiboot modules. */ +#define MULTIBOOT_MOD_ALIGN 0x00001000 + +/* Alignment of the multiboot info structure. */ +#define MULTIBOOT_INFO_ALIGN 0x00000008 + +/* Flags set in the ’flags’ member of the multiboot header. */ + +#define MULTIBOOT_TAG_ALIGN 8 +#define MULTIBOOT_TAG_TYPE_END 0 +#define MULTIBOOT_TAG_TYPE_CMDLINE 1 +#define MULTIBOOT_TAG_TYPE_BOOT_LOADER_NAME 2 +#define MULTIBOOT_TAG_TYPE_MODULE 3 +#define MULTIBOOT_TAG_TYPE_BASIC_MEMINFO 4 +#define MULTIBOOT_TAG_TYPE_BOOTDEV 5 +#define MULTIBOOT_TAG_TYPE_MMAP 6 +#define MULTIBOOT_TAG_TYPE_VBE 7 +#define MULTIBOOT_TAG_TYPE_FRAMEBUFFER 8 +#define MULTIBOOT_TAG_TYPE_ELF_SECTIONS 9 +#define MULTIBOOT_TAG_TYPE_APM 10 +#define MULTIBOOT_TAG_TYPE_EFI32 11 +#define MULTIBOOT_TAG_TYPE_EFI64 12 +#define MULTIBOOT_TAG_TYPE_SMBIOS 13 +#define MULTIBOOT_TAG_TYPE_ACPI_OLD 14 +#define MULTIBOOT_TAG_TYPE_ACPI_NEW 15 +#define MULTIBOOT_TAG_TYPE_NETWORK 16 +#define MULTIBOOT_TAG_TYPE_EFI_MMAP 17 +#define MULTIBOOT_TAG_TYPE_EFI_BS 18 +#define MULTIBOOT_TAG_TYPE_EFI32_IH 19 +#define MULTIBOOT_TAG_TYPE_EFI64_IH 20 +#define MULTIBOOT_TAG_TYPE_LOAD_BASE_ADDR 21 + +#define MULTIBOOT_HEADER_TAG_END 0 +#define MULTIBOOT_HEADER_TAG_INFORMATION_REQUEST 1 +#define MULTIBOOT_HEADER_TAG_ADDRESS 2 +#define MULTIBOOT_HEADER_TAG_ENTRY_ADDRESS 3 +#define MULTIBOOT_HEADER_TAG_CONSOLE_FLAGS 4 +#define MULTIBOOT_HEADER_TAG_FRAMEBUFFER 5 +#define MULTIBOOT_HEADER_TAG_MODULE_ALIGN 6 +#define MULTIBOOT_HEADER_TAG_EFI_BS 7 +#define MULTIBOOT_HEADER_TAG_ENTRY_ADDRESS_EFI32 8 +#define MULTIBOOT_HEADER_TAG_ENTRY_ADDRESS_EFI64 9 +#define MULTIBOOT_HEADER_TAG_RELOCATABLE 10 + +#define MULTIBOOT_ARCHITECTURE_I386 0 +#define MULTIBOOT_ARCHITECTURE_MIPS32 4 +#define MULTIBOOT_HEADER_TAG_OPTIONAL 1 + +#define MULTIBOOT_LOAD_PREFERENCE_NONE 0 +#define MULTIBOOT_LOAD_PREFERENCE_LOW 1 +#define MULTIBOOT_LOAD_PREFERENCE_HIGH 2 + +#define MULTIBOOT_CONSOLE_FLAGS_CONSOLE_REQUIRED 1 +#define MULTIBOOT_CONSOLE_FLAGS_EGA_TEXT_SUPPORTED 2 + +struct multiboot_header +{ + /* Must be MULTIBOOT_MAGIC - see above. */ + uint32_t magic; + + /* ISA */ + uint32_t architecture; + + /* Total header length. */ + uint32_t header_length; + + /* The above fields plus this one must equal 0 mod 2^32. */ + uint32_t checksum; +}; + +struct multiboot_header_tag +{ + uint16_t type; + uint16_t flags; + uint32_t size; +}; + +struct multiboot_header_tag_information_request +{ + uint16_t type; + uint16_t flags; + uint32_t size; + uint32_t requests[0]; +}; + +struct multiboot2_start_tag { + uint32_t size; + uint32_t reserved; +}; + +struct multiboot_header_tag_address +{ + uint16_t type; + uint16_t flags; + uint32_t size; + uint32_t header_addr; + uint32_t load_addr; + uint32_t load_end_addr; + uint32_t bss_end_addr; +}; + +struct multiboot_header_tag_entry_address +{ + uint16_t type; + uint16_t flags; + uint32_t size; + uint32_t entry_addr; +}; + +struct multiboot_header_tag_console_flags +{ + uint16_t type; + uint16_t flags; + uint32_t size; + uint32_t console_flags; +}; + +struct multiboot_header_tag_framebuffer +{ + uint16_t type; + uint16_t flags; + uint32_t size; + uint32_t width; + uint32_t height; + uint32_t depth; +}; + +struct multiboot_header_tag_module_align +{ + uint16_t type; + uint16_t flags; + uint32_t size; +}; + +struct multiboot_header_tag_relocatable +{ + uint16_t type; + uint16_t flags; + uint32_t size; + uint32_t min_addr; + uint32_t max_addr; + uint32_t align; + uint32_t preference; +}; + +struct multiboot_color +{ + uint8_t red; + uint8_t green; + uint8_t blue; +}; + +struct multiboot_mmap_entry +{ + uint64_t addr; + uint64_t len; +#define MULTIBOOT_MEMORY_AVAILABLE 1 +#define MULTIBOOT_MEMORY_RESERVED 2 +#define MULTIBOOT_MEMORY_ACPI_RECLAIMABLE 3 +#define MULTIBOOT_MEMORY_NVS 4 +#define MULTIBOOT_MEMORY_BADRAM 5 + uint32_t type; + uint32_t zero; +}; +typedef struct multiboot_mmap_entry multiboot_memory_map_t; + +struct multiboot_tag +{ + uint32_t type; + uint32_t size; +}; + +struct multiboot_tag_string +{ + uint32_t type; + uint32_t size; + char string[0]; +}; + +struct multiboot_tag_module +{ + uint32_t type; + uint32_t size; + uint32_t mod_start; + uint32_t mod_end; + char cmdline[0]; +}; + +struct multiboot_tag_basic_meminfo +{ + uint32_t type; + uint32_t size; + uint32_t mem_lower; + uint32_t mem_upper; +}; + +struct multiboot_tag_bootdev +{ + uint32_t type; + uint32_t size; + uint32_t biosdev; + uint32_t slice; + uint32_t part; +}; + +struct multiboot_tag_mmap +{ + uint32_t type; + uint32_t size; + uint32_t entry_size; + uint32_t entry_version; + struct multiboot_mmap_entry entries[0]; +}; + +struct multiboot_vbe_info_block +{ + uint8_t external_specification[512]; +}; + +struct multiboot_vbe_mode_info_block +{ + uint8_t external_specification[256]; +}; + +struct multiboot_tag_vbe +{ + uint32_t type; + uint32_t size; + + uint16_t vbe_mode; + uint16_t vbe_interface_seg; + uint16_t vbe_interface_off; + uint16_t vbe_interface_len; + + struct multiboot_vbe_info_block vbe_control_info; + struct multiboot_vbe_mode_info_block vbe_mode_info; +}; + +struct multiboot_tag_framebuffer_common +{ + uint32_t type; + uint32_t size; + + uint64_t framebuffer_addr; + uint32_t framebuffer_pitch; + uint32_t framebuffer_width; + uint32_t framebuffer_height; + uint8_t framebuffer_bpp; +#define MULTIBOOT_FRAMEBUFFER_TYPE_INDEXED 0 +#define MULTIBOOT_FRAMEBUFFER_TYPE_RGB 1 +#define MULTIBOOT_FRAMEBUFFER_TYPE_EGA_TEXT 2 + uint8_t framebuffer_type; + uint16_t reserved; +}; + +struct multiboot_tag_framebuffer +{ + struct multiboot_tag_framebuffer_common common; + + union + { + struct + { + uint16_t framebuffer_palette_num_colors; + struct multiboot_color framebuffer_palette[0]; + }; + struct + { + uint8_t framebuffer_red_field_position; + uint8_t framebuffer_red_mask_size; + uint8_t framebuffer_green_field_position; + uint8_t framebuffer_green_mask_size; + uint8_t framebuffer_blue_field_position; + uint8_t framebuffer_blue_mask_size; + }; + }; +}; + +struct multiboot_tag_elf_sections +{ + uint32_t type; + uint32_t size; + uint32_t num; + uint32_t entsize; + uint32_t shndx; + char sections[0]; +}; + +struct multiboot_tag_apm +{ + uint32_t type; + uint32_t size; + uint16_t version; + uint16_t cseg; + uint32_t offset; + uint16_t cseg_16; + uint16_t dseg; + uint16_t flags; + uint16_t cseg_len; + uint16_t cseg_16_len; + uint16_t dseg_len; +}; + +struct multiboot_tag_efi32 +{ + uint32_t type; + uint32_t size; + uint32_t pointer; +}; + +struct multiboot_tag_efi64 +{ + uint32_t type; + uint32_t size; + uint64_t pointer; +}; + +struct multiboot_tag_smbios +{ + uint32_t type; + uint32_t size; + uint8_t major; + uint8_t minor; + uint8_t reserved[6]; + uint8_t tables[0]; +}; + +struct multiboot_tag_old_acpi +{ + uint32_t type; + uint32_t size; + uint8_t rsdp[0]; +}; + +struct multiboot_tag_new_acpi +{ + uint32_t type; + uint32_t size; + uint8_t rsdp[0]; +}; + +struct multiboot_tag_network +{ + uint32_t type; + uint32_t size; + uint8_t dhcpack[0]; +}; + +struct multiboot_tag_efi_mmap +{ + uint32_t type; + uint32_t size; + uint32_t descr_size; + uint32_t descr_vers; + uint8_t efi_mmap[0]; +}; + +struct multiboot_tag_efi32_ih +{ + uint32_t type; + uint32_t size; + uint32_t pointer; +}; + +struct multiboot_tag_efi64_ih +{ + uint32_t type; + uint32_t size; + uint64_t pointer; +}; + +struct multiboot_tag_load_base_addr +{ + uint32_t type; + uint32_t size; + uint32_t load_base_addr; +}; + +#endif diff --git a/limine/common/protos/multiboot_32.asm_x86 b/limine/common/protos/multiboot_32.asm_x86 new file mode 100644 index 0000000..89e4f17 --- /dev/null +++ b/limine/common/protos/multiboot_32.asm_x86 @@ -0,0 +1,23 @@ +bits 32 + +section .text +global multiboot_spinup_32: +multiboot_spinup_32: + sub esp, 6 + mov word [esp], 0x3ff + mov dword [esp+2], 0 + lidt [esp] + add esp, 6 + + add esp, 4 ; return address + + pop ebx ; reloc_stub + pop esi ; magic + pop edi ; protocol_info + pop ecx ; entry_point + pop eax ; elf_ranges + pop edx ; elf_ranges_count + + jmp ebx + +section .note.GNU-stack noalloc noexec nowrite progbits diff --git a/limine/common/protos/multiboot_reloc.asm_x86 b/limine/common/protos/multiboot_reloc.asm_x86 new file mode 100644 index 0000000..6ffc148 --- /dev/null +++ b/limine/common/protos/multiboot_reloc.asm_x86 @@ -0,0 +1,66 @@ +section .data + +bits 32 + +global multiboot_reloc_stub +multiboot_reloc_stub: + jmp .code + + times 4-($-multiboot_reloc_stub) db 0 + + ; EBX = self + ; ESI = magic value + ; EDI = protocol info + ; ECX = entry point + ; EAX = ranges + ; EDX = ranges count + + .code: + mov esp, ebx + add esp, .mini_stack_top - multiboot_reloc_stub + + push edi + push esi + push ecx + + .ranges_loop: + test edx, edx ; Loop until we're done + jz .ranges_loop_out + + mov esi, [eax] ; ESI = range.elsewhere + mov edi, [eax+8] ; EDI = range.target + mov ecx, [eax+16] ; ECX = range.length + rep movsb ; Copy range to target location + + add eax, 24 ; Move to the next range + + dec edx + jmp .ranges_loop + + .ranges_loop_out: + ; We're done relocating! + pop ecx + pop esi + pop edi + + push ecx + + mov eax, esi ; EAX = magic value + mov ebx, edi ; EBX = protocol info + xor ecx, ecx + xor edx, edx + xor esi, esi + xor edi, edi + xor ebp, ebp + + ret + + align 16 + .mini_stack: + times 3 dq 0 + .mini_stack_top: + +global multiboot_reloc_stub_end +multiboot_reloc_stub_end: + +section .note.GNU-stack noalloc noexec nowrite progbits diff --git a/limine/common/pxe/pxe.h b/limine/common/pxe/pxe.h new file mode 100644 index 0000000..49d094b --- /dev/null +++ b/limine/common/pxe/pxe.h @@ -0,0 +1,111 @@ +#ifndef PXE_H +#define PXE_H + +#include +#include + +#define DHCP_ACK_PACKET_LEN 296 + +extern uint8_t cached_dhcp_packet[DHCP_ACK_PACKET_LEN]; +extern bool cached_dhcp_ack_valid; + +#if defined (BIOS) + +struct volume *pxe_bind_volume(void); +void pxe_init(void); +int pxe_call(uint16_t opcode, uint16_t buf_seg, uint16_t buf_off); + +#define MAC_ADDR_LEN 16 +typedef uint8_t MAC_ADDR_t[MAC_ADDR_LEN]; + +struct bootph { + uint8_t opcode; + uint8_t Hardware; + uint8_t Hardlen; + uint8_t Gatehops; + uint32_t ident; + uint16_t seconds; + uint16_t Flags; + uint32_t cip; + uint32_t yip; + uint32_t sip; + uint32_t gip; + MAC_ADDR_t CAddr; + uint8_t Sname[64]; + uint8_t bootfile[128]; + union bootph_vendor { + uint8_t d[1024]; + struct bootph_vendor_v { + uint8_t magic[4]; + uint32_t flags; + uint8_t pad[52]; + } v; + } vendor; +}; + + struct PXENV_UNDI_GET_INFORMATION { + uint16_t Status; + uint16_t BaseIo; + uint16_t IntNumber; + uint16_t MaxTranUnit; + uint16_t HwType; + uint16_t HwAddrLen; + uint8_t CurrentNodeAddress[16]; + uint8_t PermNodeAddress[16]; + uint16_t ROMAddress; + uint16_t RxBufCt; + uint16_t TxBufCt; + }; + +#define PXE_SIGNATURE "PXENV+" +struct pxenv { + uint8_t signature[6]; + uint16_t version; + uint8_t length; + uint8_t checksum; + uint32_t rm_entry; + uint32_t pm_offset; + uint16_t pm_selector; + uint16_t stack_seg; + uint16_t stack_size; + uint16_t bc_code_seg; + uint16_t bc_code_size; + uint16_t bc_data_seg; + uint16_t bc_data_size; + uint16_t undi_data_seg; + uint16_t undi_data_size; + uint16_t undi_code_seg; + uint16_t undi_code_size; + uint32_t pxe_ptr; +} __attribute__((packed)); + +#define PXE_BANGPXE_SIGNATURE "!PXE" +struct bangpxe { + uint8_t signature[4]; + uint8_t length; + uint8_t chksum; + uint8_t rev; + uint8_t reserved; + uint32_t undiromid; + uint32_t baseromid; + uint32_t rm_entry; + uint32_t pm_entry; +} __attribute__((packed)); + +#define PXENV_GET_CACHED_INFO 0x0071 +#define PXENV_PACKET_TYPE_CACHED_REPLY 3 +struct pxenv_get_cached_info { + uint16_t status; + uint16_t packet_type; + uint16_t buffer_size; + uint32_t buffer; + uint16_t buffer_limit; +} __attribute__((packed)); + +#elif defined (UEFI) + +struct volume *pxe_bind_volume(EFI_HANDLE efi_handle, EFI_PXE_BASE_CODE *pxe_base_code); + +#endif + +#endif diff --git a/limine/common/pxe/pxe.s2.c b/limine/common/pxe/pxe.s2.c new file mode 100644 index 0000000..43762ad --- /dev/null +++ b/limine/common/pxe/pxe.s2.c @@ -0,0 +1,72 @@ +#include +#include +#include +#include +#include +#if defined (BIOS) +#include +#elif defined (UEFI) +#include +#endif + +#if defined (BIOS) + +void set_pxe_fp(uint32_t fp); + +struct volume *pxe_bind_volume(void) { + struct volume *volume = ext_mem_alloc(sizeof(struct volume)); + + volume->pxe = true; + + return volume; +} + +void pxe_init(void) { + //pxe installation check + struct rm_regs r = { 0 }; + r.ebx = 0; + r.ecx = 0; + r.eax = 0x5650; + r.es = 0; + + rm_int(0x1a, &r, &r); + if ((r.eax & 0xffff) != 0x564e) { + panic(false, "PXE installation check failed"); + } + + struct pxenv* pxenv = { 0 }; + + pxenv = (struct pxenv*)((r.es << 4) + (r.ebx & 0xffff)); + if (memcmp(pxenv->signature, PXE_SIGNATURE, sizeof(pxenv->signature)) != 0) { + panic(false, "PXENV structure signature corrupted"); + } + + if (pxenv->version < 0x201) { + //we won't support pxe < 2.1, grub does this too and it seems to work fine + panic(false, "pxe version too old"); + } + + struct bangpxe* bangpxe = (struct bangpxe*)((((pxenv->pxe_ptr & 0xffff0000) >> 16) << 4) + (pxenv->pxe_ptr & 0xffff)); + + if (memcmp(bangpxe->signature, PXE_BANGPXE_SIGNATURE, + sizeof(bangpxe->signature)) + != 0) { + panic(false, "!pxe signature corrupted"); + } + set_pxe_fp(bangpxe->rm_entry); + printv("pxe: Successfully initialized\n"); +} + +#elif defined (UEFI) + +struct volume *pxe_bind_volume(EFI_HANDLE efi_handle, EFI_PXE_BASE_CODE *pxe_base_code) { + struct volume *volume = ext_mem_alloc(sizeof(struct volume)); + + volume->efi_handle = efi_handle; + volume->pxe_base_code = pxe_base_code; + volume->pxe = true; + + return volume; +} + +#endif diff --git a/limine/common/pxe/pxe_asm.s2.asm_bios_ia32 b/limine/common/pxe/pxe_asm.s2.asm_bios_ia32 new file mode 100644 index 0000000..2935402 --- /dev/null +++ b/limine/common/pxe/pxe_asm.s2.asm_bios_ia32 @@ -0,0 +1,99 @@ +section .realmode + +global pxe_call +global set_pxe_fp + +set_pxe_fp: + mov eax, [esp + 4] + mov [pxe_call.pxe_fp], eax + ret + +pxe_call: + ; Save GDT in case BIOS overwrites it + sgdt [.gdt] + + ; Save IDT + sidt [.idt] + + ; Load BIOS IVT + lidt [.rm_idt] + + ; Save non-scratch GPRs + push ebx + push esi + push edi + push ebp + + lea ebp, [esp + 20] + + ; Jump to real mode + jmp 0x08:.bits16 + .bits16: + bits 16 + mov ax, 0x10 + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + mov ss, ax + mov eax, cr0 + and al, 0xfe + mov cr0, eax + jmp 0x00:.cszero + .cszero: + xor ax, ax + mov ss, ax + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + + sti + + push word [bp + 4] + push word [bp + 8] + push word [bp + 0] + call far [.pxe_fp] + add sp, 6 + + cli + + ; Restore GDT + o32 lgdt [cs:.gdt] + + ; Restore IDT + o32 lidt [cs:.idt] + + ; Jump back to pmode + mov ebx, cr0 + or bl, 1 + mov cr0, ebx + jmp 0x18:.bits32 + .bits32: + bits 32 + mov bx, 0x20 + mov ds, bx + mov es, bx + mov fs, bx + mov gs, bx + mov ss, bx + + and eax, 0xffff + + ; Restore non-scratch GPRs + pop ebp + pop edi + pop esi + pop ebx + + ; Exit + ret + +align 16 + .pxe_fp: dd 0 + .gdt: dq 0 + .idt: dq 0 + .rm_idt: dw 0x3ff + dd 0 + +section .note.GNU-stack noalloc noexec nowrite progbits diff --git a/limine/common/pxe/tftp.h b/limine/common/pxe/tftp.h new file mode 100644 index 0000000..99c5861 --- /dev/null +++ b/limine/common/pxe/tftp.h @@ -0,0 +1,46 @@ +#ifndef TFTP_H +#define TFTP_H + +#include +#include +#include + +#if defined (BIOS) + +#define UNDI_GET_INFORMATION 0xC + +#define TFTP_OPEN 0x0020 +struct pxenv_open { + uint16_t status; + uint32_t sip; + uint32_t gip; + uint8_t name[128]; + uint16_t port; + uint16_t packet_size; + } __attribute__((packed)); + +#define TFTP_READ 0x22 +struct pxenv_read { + uint16_t status; + uint16_t pn; + uint16_t bsize; + uint16_t boff; + uint16_t bseg; +} __attribute__((packed)); + +#define TFTP_GET_FILE_SIZE 0x25 +struct pxenv_get_file_size { + uint16_t status; + uint32_t sip; + uint32_t gip; + uint8_t name[128]; + uint32_t file_size; +} __attribute__((packed)); + +#define TFTP_CLOSE 0x21 + +#endif + +struct file_handle *tftp_open(struct volume *part, const char *server_addr, const char *name); + +#endif diff --git a/limine/common/pxe/tftp.s2.c b/limine/common/pxe/tftp.s2.c new file mode 100644 index 0000000..7847a73 --- /dev/null +++ b/limine/common/pxe/tftp.s2.c @@ -0,0 +1,267 @@ +#include +#include +#if defined (BIOS) +# include +#elif defined (UEFI) +# include +#endif +#include +#include +#include +#include + +// cache the dhcp packet +uint8_t cached_dhcp_packet[DHCP_ACK_PACKET_LEN] = { 0 }; +bool cached_dhcp_ack_valid = false; + +#if defined (BIOS) + +static uint32_t get_boot_server_info(void) { + struct pxenv_get_cached_info cachedinfo = { 0 }; + cachedinfo.packet_type = PXENV_PACKET_TYPE_CACHED_REPLY; + int ret = pxe_call(PXENV_GET_CACHED_INFO, ((uint16_t)rm_seg(&cachedinfo)), (uint16_t)rm_off(&cachedinfo)); + if (ret || cachedinfo.buffer == 0) { + panic(false, "tftp: Failed to get DHCP cached info"); + } + struct bootph *ph = (struct bootph*)(void *) (((((uint32_t)cachedinfo.buffer) >> 16) << 4) + (((uint32_t)cachedinfo.buffer) & 0xFFFF)); + if (!cached_dhcp_ack_valid) { + size_t copy_len = cachedinfo.buffer_size < DHCP_ACK_PACKET_LEN + ? cachedinfo.buffer_size : DHCP_ACK_PACKET_LEN; + memcpy(cached_dhcp_packet, ph, copy_len); + cached_dhcp_ack_valid = true; + } + return ph->sip; +} + +static uint32_t parse_ip_addr(const char *server_addr) { + uint32_t out; + + if (!server_addr || !strlen(server_addr)) { + return get_boot_server_info(); + } + + if (inet_pton(server_addr, &out)) { + panic(true, "tftp: Invalid IPv4 address: \"%s\"", server_addr); + } + + return out; +} + +struct file_handle *tftp_open(struct volume *part, const char *server_addr, const char *name) { + uint32_t server_ip = parse_ip_addr(server_addr); + const uint16_t server_port = 69; // This couldn't be changed previously either + int ret = 0; + + (void)part; + + struct PXENV_UNDI_GET_INFORMATION undi_info = { 0 }; + ret = pxe_call(UNDI_GET_INFORMATION, ((uint16_t)rm_seg(&undi_info)), (uint16_t)rm_off(&undi_info)); + if (ret) { + return NULL; + } + + //TODO figure out a more proper way to do this. + if (undi_info.MaxTranUnit < 48) { + print("tftp: Invalid MTU (%u), too small for TFTP headers\n", undi_info.MaxTranUnit); + return NULL; + } + uint16_t mtu = undi_info.MaxTranUnit - 48; + + size_t name_len = strlen(name); + if (name_len >= 128) { + print("tftp: Filename too long (max 127 chars)\n"); + return NULL; + } + + struct pxenv_get_file_size fsize = { + .status = 0, + .sip = server_ip, + }; + memcpy(fsize.name, name, name_len + 1); + ret = pxe_call(TFTP_GET_FILE_SIZE, ((uint16_t)rm_seg(&fsize)), (uint16_t)rm_off(&fsize)); + if (ret) { + return NULL; + } + + struct file_handle *handle = ext_mem_alloc(sizeof(struct file_handle)); + + handle->size = fsize.file_size; + handle->is_memfile = true; + + handle->pxe = true; + handle->pxe_ip = server_ip; + handle->pxe_port = server_port; + + handle->path = ext_mem_alloc(1 + name_len + 1); + handle->path[0] = '/'; + memcpy(&handle->path[1], name, name_len); + handle->path_len = 1 + name_len + 1; + + struct pxenv_open open = { + .status = 0, + .sip = server_ip, + .port = (server_port >> 8) | (server_port << 8), + .packet_size = mtu + }; + memcpy(open.name, name, name_len + 1); + + ret = pxe_call(TFTP_OPEN, ((uint16_t)rm_seg(&open)), (uint16_t)rm_off(&open)); + if (ret) { + print("tftp: Failed to open file %x or bad packet size", open.status); + pmm_free(handle->path, handle->path_len); + pmm_free(handle, sizeof(struct file_handle)); + return NULL; + } + + // Validate server's negotiated packet size doesn't exceed our buffer + if (open.packet_size > mtu) { + print("tftp: Server requested packet size %u exceeds our MTU %u\n", open.packet_size, mtu); + uint16_t close = 0; + pxe_call(TFTP_CLOSE, ((uint16_t)rm_seg(&close)), (uint16_t)rm_off(&close)); + pmm_free(handle->path, handle->path_len); + pmm_free(handle, sizeof(struct file_handle)); + return NULL; + } + + uint16_t alloc_mtu = mtu; // Save original MTU for allocation/free + uint8_t *buf = conv_mem_alloc(alloc_mtu); + + mtu = open.packet_size; + handle->fd = ext_mem_alloc(handle->size); + + size_t progress = 0; + bool slow = false; + + while (progress < handle->size) { + struct pxenv_read read = { + .boff = ((uint16_t)rm_off(buf)), + .bseg = ((uint16_t)rm_seg(buf)), + }; + + ret = pxe_call(TFTP_READ, ((uint16_t)rm_seg(&read)), (uint16_t)rm_off(&read)); + if (ret) { + panic(false, "tftp: Read failure"); + } + + // Validate read size doesn't overflow the buffer (use alloc_mtu, not server's mtu) + if (read.bsize > alloc_mtu || progress + read.bsize > handle->size) { + panic(false, "tftp: Server sent more data than expected"); + } + + // Prevent infinite loop from zero-byte reads + if (read.bsize == 0 && progress < handle->size) { + panic(false, "tftp: Server returned zero bytes before transfer complete"); + } + + memcpy(handle->fd + progress, buf, read.bsize); + + progress += read.bsize; + + if (read.bsize < mtu && !slow && progress < handle->size) { + slow = true; + print("tftp: Server is sending the file in smaller packets (it sent %d bytes), download might take longer.\n", read.bsize); + } + } + + uint16_t close = 0; + ret = pxe_call(TFTP_CLOSE, ((uint16_t)rm_seg(&close)), (uint16_t)rm_off(&close)); + if (ret) { + panic(false, "tftp: Close failure"); + } + + pmm_free(buf, alloc_mtu); + + return handle; +} + +#elif defined (UEFI) + +static EFI_IP_ADDRESS *parse_ip_addr(struct volume *part, const char *server_addr) { + static EFI_IP_ADDRESS out; + + if (!server_addr || !strlen(server_addr)) { + EFI_PXE_BASE_CODE_PACKET* packet; + if (part->pxe_base_code->Mode->PxeReplyReceived) packet = &part->pxe_base_code->Mode->PxeReply; + else if (part->pxe_base_code->Mode->ProxyOfferReceived) packet = &part->pxe_base_code->Mode->ProxyOffer; + else packet = &part->pxe_base_code->Mode->DhcpAck; + memcpy(out.Addr, packet->Dhcpv4.BootpSiAddr, 4); + if (!cached_dhcp_ack_valid) { + memcpy(cached_dhcp_packet, packet, DHCP_ACK_PACKET_LEN); + cached_dhcp_ack_valid = true; + } + } else { + if (inet_pton(server_addr, &out.Addr)) { + panic(true, "tftp: Invalid IPv4 address: \"%s\"", server_addr); + } + } + + return &out; +} + +struct file_handle *tftp_open(struct volume *part, const char *server_addr, const char *name) { + if (part == NULL || !part->pxe_base_code) { + return NULL; + } + + EFI_IP_ADDRESS *ip = parse_ip_addr(part, server_addr); + + uint64_t file_size; + EFI_STATUS status; + + status = part->pxe_base_code->Mtftp( + part->pxe_base_code, + EFI_PXE_BASE_CODE_TFTP_GET_FILE_SIZE, + NULL, + false, + &file_size, + NULL, + ip, + (uint8_t *)name, + NULL, + false); + + if (status) { + return NULL; + } + + struct file_handle *handle = ext_mem_alloc(sizeof(struct file_handle)); + + handle->efi_part_handle = part->efi_handle; + handle->size = file_size; + handle->is_memfile = true; + + handle->pxe = true; + handle->pxe_ip = *(uint32_t *)ip->Addr; + handle->pxe_port = 69; + + size_t name_len = strlen(name); + handle->path = ext_mem_alloc(1 + name_len + 1); + handle->path[0] = '/'; + memcpy(&handle->path[1], name, name_len); + handle->path_len = 1 + name_len + 1; + + handle->fd = ext_mem_alloc(handle->size); + + status = part->pxe_base_code->Mtftp( + part->pxe_base_code, + EFI_PXE_BASE_CODE_TFTP_READ_FILE, + handle->fd, + false, + &file_size, + NULL, + ip, + (uint8_t *)name, + NULL, + false); + + if (status) { + pmm_free(handle->fd, handle->size); + pmm_free(handle->path, handle->path_len); + pmm_free(handle, sizeof(struct file_handle)); + return NULL; + } + + return handle; +} + +#endif diff --git a/limine/common/stb_image.patch b/limine/common/stb_image.patch new file mode 100644 index 0000000..093eca2 --- /dev/null +++ b/limine/common/stb_image.patch @@ -0,0 +1,90 @@ +--- common/lib/stb_image.h 2023-03-05 18:57:03.930649341 +0100 ++++ common/lib/stb_image.h 2023-03-05 18:55:53.767318782 +0100 +@@ -127,6 +127,54 @@ + #ifndef STBI_INCLUDE_STB_IMAGE_H + #define STBI_INCLUDE_STB_IMAGE_H + ++#include ++#include ++#include ++#include ++ ++#define STBI_ASSERT(x) ++ ++#define STBI_MALLOC(x) ({ \ ++ size_t STBI_MALLOC_alloc_size = (x); \ ++ STBI_MALLOC_alloc_size += 16; \ ++ void *STBI_MALLOC_buf = ext_mem_alloc(STBI_MALLOC_alloc_size); \ ++ size_t *STBI_MALLOC_alloc_size_ptr = STBI_MALLOC_buf; \ ++ *STBI_MALLOC_alloc_size_ptr = STBI_MALLOC_alloc_size; \ ++ STBI_MALLOC_buf + 16; \ ++}) ++ ++#define STBI_FREE(x) do { \ ++ void *STBI_FREE_buf = (x); \ ++ if (STBI_FREE_buf == NULL) { \ ++ break; \ ++ } \ ++ STBI_FREE_buf -= 16; \ ++ size_t *STBI_FREE_alloc_size_ptr = STBI_FREE_buf; \ ++ size_t STBI_FREE_alloc_size = *STBI_FREE_alloc_size_ptr; \ ++ pmm_free(STBI_FREE_buf, STBI_FREE_alloc_size); \ ++} while (0) ++ ++#define STBI_REALLOC(x, y) ({ \ ++ void *STBI_REALLOC_buf = (x); \ ++ size_t STBI_REALLOC_alloc_size = (y); \ ++ void *STBI_REALLOC_new_buf = STBI_MALLOC(STBI_REALLOC_alloc_size); \ ++ if (STBI_REALLOC_buf != NULL) { \ ++ size_t STBI_REALLOC_old_size = (*(size_t *)((void *)STBI_REALLOC_buf - 16)) - 16; \ ++ memcpy(STBI_REALLOC_new_buf, STBI_REALLOC_buf, \ ++ MIN(STBI_REALLOC_alloc_size, STBI_REALLOC_old_size)); \ ++ STBI_FREE(STBI_REALLOC_buf); \ ++ } \ ++ STBI_REALLOC_new_buf; \ ++}) ++ ++#define STBI_NO_THREAD_LOCALS ++#define STBI_NO_STDIO ++#define STBI_NO_SIMD ++#define STBI_NO_LINEAR ++#define STBI_ONLY_JPEG ++#define STBI_ONLY_PNG ++#define STBI_ONLY_BMP ++ + // DOCUMENTATION + // + // Limitations: +@@ -381,7 +429,7 @@ + STBI_rgb_alpha = 4 + }; + +-#include ++// #include + typedef unsigned char stbi_uc; + typedef unsigned short stbi_us; + +@@ -584,8 +632,8 @@ + + #include + #include // ptrdiff_t on osx +-#include +-#include ++// #include ++// #include + #include + + #if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) +@@ -1574,10 +1622,12 @@ + STBIDEF void stbi_ldr_to_hdr_scale(float scale) { stbi__l2h_scale = scale; } + #endif + ++/* + static float stbi__h2l_gamma_i=1.0f/2.2f, stbi__h2l_scale_i=1.0f; + + STBIDEF void stbi_hdr_to_ldr_gamma(float gamma) { stbi__h2l_gamma_i = 1/gamma; } + STBIDEF void stbi_hdr_to_ldr_scale(float scale) { stbi__h2l_scale_i = 1/scale; } ++*/ + + + ////////////////////////////////////////////////////////////////////////////// diff --git a/limine/common/sys/a20.h b/limine/common/sys/a20.h new file mode 100644 index 0000000..8e320b5 --- /dev/null +++ b/limine/common/sys/a20.h @@ -0,0 +1,9 @@ +#ifndef SYS__A20_H__ +#define SYS__A20_H__ + +#include + +bool a20_check(void); +bool a20_enable(void); + +#endif diff --git a/limine/common/sys/a20.s2.c b/limine/common/sys/a20.s2.c new file mode 100644 index 0000000..0059cbf --- /dev/null +++ b/limine/common/sys/a20.s2.c @@ -0,0 +1,92 @@ +#if defined (BIOS) + +#include +#include +#include +#include +#include +#include + +#define A20_KBC_TIMEOUT 50000 + +static bool a20_kbc_wait(uint8_t mask, uint8_t expected) { + for (int i = 0; i < A20_KBC_TIMEOUT; i++) { + if ((inb(0x64) & mask) == expected) + return true; + } + return false; +} + +bool a20_check(void) { + bool ret = false; + uint16_t orig = mminw(0x7dfe); + + mmoutw(0x7dfe, 0x1234); + + if (mminw(0x7dfe) != mminw(0x7dfe + 0x100000)) { + ret = true; + goto out; + } + + mmoutw(0x7dfe, ~mminw(0x7dfe)); + + if (mminw(0x7dfe) != mminw(0x7dfe + 0x100000)) { + ret = true; + goto out; + } + +out: + mmoutw(0x7dfe, orig); + return ret; +} + +// Keyboard controller method code below taken from: +// https://wiki.osdev.org/A20_Line + +bool a20_enable(void) { + if (a20_check()) + return true; + + // BIOS method + struct rm_regs r = {0}; + r.eax = 0x2401; + rm_int(0x15, &r, &r); + + if (a20_check()) + return true; + + // Keyboard controller method (with timeout for systems without KBC) + if (!a20_kbc_wait(2, 0)) goto kbc_failed; + outb(0x64, 0xad); + if (!a20_kbc_wait(2, 0)) goto kbc_failed; + outb(0x64, 0xd0); + if (!a20_kbc_wait(1, 1)) goto kbc_failed; + uint8_t b = inb(0x60); + if (!a20_kbc_wait(2, 0)) goto kbc_failed; + outb(0x64, 0xd1); + if (!a20_kbc_wait(2, 0)) goto kbc_failed; + outb(0x60, b | 2); + if (!a20_kbc_wait(2, 0)) goto kbc_failed; + outb(0x64, 0xae); + if (!a20_kbc_wait(2, 0)) goto kbc_failed; + + if (a20_check()) + return true; + +kbc_failed: + + // Fast A20 method + b = inb(0x92); + if ((b & 0x02) == 0) { + b &= ~0x01; + b |= 0x02; + outb(0x92, b); + } + + if (a20_check()) + return true; + + return false; +} + +#endif diff --git a/limine/common/sys/cpu.h b/limine/common/sys/cpu.h new file mode 100644 index 0000000..f8ee3b3 --- /dev/null +++ b/limine/common/sys/cpu.h @@ -0,0 +1,432 @@ +#ifndef SYS__CPU_H__ +#define SYS__CPU_H__ + +#include +#include +#include + +#if defined(__x86_64__) || defined(__i386__) + +static inline bool cpuid(uint32_t leaf, uint32_t subleaf, + uint32_t *eax, uint32_t *ebx, uint32_t *ecx, uint32_t *edx) { + uint32_t cpuid_max; + asm volatile ("cpuid" + : "=a" (cpuid_max) + : "a" (leaf & 0x80000000) + : "ebx", "ecx", "edx"); + if (leaf > cpuid_max) + return false; + asm volatile ("cpuid" + : "=a" (*eax), "=b" (*ebx), "=c" (*ecx), "=d" (*edx) + : "a" (leaf), "c" (subleaf)); + return true; +} + +static inline void outb(uint16_t port, uint8_t value) { + asm volatile ("outb %%al, %1" : : "a" (value), "Nd" (port) : "memory"); +} + +static inline void outw(uint16_t port, uint16_t value) { + asm volatile ("outw %%ax, %1" : : "a" (value), "Nd" (port) : "memory"); +} + +static inline void outd(uint16_t port, uint32_t value) { + asm volatile ("outl %%eax, %1" : : "a" (value), "Nd" (port) : "memory"); +} + +static inline uint8_t inb(uint16_t port) { + uint8_t value; + asm volatile ("inb %1, %%al" : "=a" (value) : "Nd" (port) : "memory"); + return value; +} + +static inline uint16_t inw(uint16_t port) { + uint16_t value; + asm volatile ("inw %1, %%ax" : "=a" (value) : "Nd" (port) : "memory"); + return value; +} + +static inline uint32_t ind(uint16_t port) { + uint32_t value; + asm volatile ("inl %1, %%eax" : "=a" (value) : "Nd" (port) : "memory"); + return value; +} + +static inline void mmoutb(uintptr_t addr, uint8_t value) { + asm volatile ( + "movb %1, (%0)" + : + : "r" (addr), "ir" (value) + : "memory" + ); +} + +static inline void mmoutw(uintptr_t addr, uint16_t value) { + asm volatile ( + "movw %1, (%0)" + : + : "r" (addr), "ir" (value) + : "memory" + ); +} + +static inline void mmoutd(uintptr_t addr, uint32_t value) { + asm volatile ( + "movl %1, (%0)" + : + : "r" (addr), "ir" (value) + : "memory" + ); +} + +#if defined (__x86_64__) +static inline void mmoutq(uintptr_t addr, uint64_t value) { + asm volatile ( + "movq %1, (%0)" + : + : "r" (addr), "r" (value) + : "memory" + ); +} +#endif + +static inline uint8_t mminb(uintptr_t addr) { + uint8_t ret; + asm volatile ( + "movb (%1), %0" + : "=r" (ret) + : "r" (addr) + : "memory" + ); + return ret; +} + +static inline uint16_t mminw(uintptr_t addr) { + uint16_t ret; + asm volatile ( + "movw (%1), %0" + : "=r" (ret) + : "r" (addr) + : "memory" + ); + return ret; +} + +static inline uint32_t mmind(uintptr_t addr) { + uint32_t ret; + asm volatile ( + "movl (%1), %0" + : "=r" (ret) + : "r" (addr) + : "memory" + ); + return ret; +} + +#if defined (__x86_64__) +static inline uint64_t mminq(uintptr_t addr) { + uint64_t ret; + asm volatile ( + "movq (%1), %0" + : "=r" (ret) + : "r" (addr) + : "memory" + ); + return ret; +} +#endif + +static inline uint64_t rdmsr(uint32_t msr) { + uint32_t edx, eax; + asm volatile ("rdmsr" + : "=a" (eax), "=d" (edx) + : "c" (msr) + : "memory"); + return ((uint64_t)edx << 32) | eax; +} + +static inline void wrmsr(uint32_t msr, uint64_t value) { + uint32_t edx = value >> 32; + uint32_t eax = (uint32_t)value; + asm volatile ("wrmsr" + : + : "a" (eax), "d" (edx), "c" (msr) + : "memory"); +} + +static inline uint64_t rdtsc(void) { + uint32_t edx, eax; + asm volatile ("rdtsc" : "=a" (eax), "=d" (edx) :: "memory"); + return ((uint64_t)edx << 32) | eax; +} + +static inline uint64_t tsc_freq_arch(void) { + uint32_t eax, ebx, ecx, edx; + if (!cpuid(0x15, 0, &eax, &ebx, &ecx, &edx)) + return 0; + if (eax == 0 || ebx == 0 || ecx == 0) + return 0; + return (uint64_t)ecx * ebx / eax; +} + +#define rdrand(type) ({ \ + type rdrand__ret; \ + asm volatile ( \ + "1: " \ + "rdrand %0;" \ + "jnc 1b;" \ + : "=r" (rdrand__ret) \ + ); \ + rdrand__ret; \ +}) + +#define rdseed(type) ({ \ + type rdseed__ret; \ + asm volatile ( \ + "1: " \ + "rdseed %0;" \ + "jnc 1b;" \ + : "=r" (rdseed__ret) \ + ); \ + rdseed__ret; \ +}) + +#define write_cr(reg, val) do { \ + asm volatile ("mov %0, %%cr" reg :: "r" (val) : "memory"); \ +} while (0) + +#define read_cr(reg) ({ \ + size_t read_cr__cr; \ + asm volatile ("mov %%cr" reg ", %0" : "=r" (read_cr__cr) :: "memory"); \ + read_cr__cr; \ +}) + +#define locked_read(var) ({ \ + typeof(*var) locked_read__ret = 0; \ + asm volatile ( \ + "lock xadd %0, %1" \ + : "+r" (locked_read__ret) \ + : "m" (*(var)) \ + : "memory" \ + ); \ + locked_read__ret; \ +}) + +#define locked_write(var, val) do { \ + __auto_type locked_write__ret = val; \ + asm volatile ( \ + "lock xchg %0, %1" \ + : "+r" ((locked_write__ret)) \ + : "m" (*(var)) \ + : "memory" \ + ); \ +} while (0) + +#elif defined (__aarch64__) + +static inline uint64_t rdtsc(void) { + uint64_t v; + asm volatile ("mrs %0, cntpct_el0" : "=r" (v)); + return v; +} + +static inline uint64_t tsc_freq_arch(void) { + uint64_t v; + asm volatile ("mrs %0, cntfrq_el0" : "=r" (v)); + return v; +} + +#define locked_read(var) ({ \ + typeof(*var) locked_read__ret = 0; \ + asm volatile ( \ + "ldar %0, %1" \ + : "=r" (locked_read__ret) \ + : "m" (*(var)) \ + : "memory" \ + ); \ + locked_read__ret; \ +}) + +static inline size_t icache_line_size(void) { + uint64_t ctr; + asm volatile ("mrs %0, ctr_el0" : "=r"(ctr)); + + return 4 << (ctr & 0b1111); +} + +static inline size_t dcache_line_size(void) { + uint64_t ctr; + asm volatile ("mrs %0, ctr_el0" : "=r"(ctr)); + + return 4 << ((ctr >> 16) & 0b1111); +} + +static inline bool is_icache_pipt(void) { + uint64_t ctr; + asm volatile ("mrs %0, ctr_el0" : "=r"(ctr)); + + return ((ctr >> 14) & 0b11) == 0b11; +} + +// Clean D-Cache to Point of Coherency +static inline void clean_dcache_poc(uintptr_t start, uintptr_t end) { + size_t dsz = dcache_line_size(); + + uintptr_t addr = start & ~(dsz - 1); + while (addr < end) { + asm volatile ("dc cvac, %0" :: "r"(addr) : "memory"); + addr += dsz; + } + + asm volatile ("dsb sy\n\tisb"); +} + +// Invalidate I-Cache to Point of Unification +static inline void inval_icache_pou(uintptr_t start, uintptr_t end) { + if (!is_icache_pipt()) { + asm volatile ("ic ialluis" ::: "memory"); + asm volatile ("dsb sy\n\tisb"); + return; + } + + size_t isz = icache_line_size(); + + uintptr_t addr = start & ~(isz - 1); + while (addr < end) { + asm volatile ("ic ivau, %0" :: "r"(addr) : "memory"); + addr += isz; + } + + asm volatile ("dsb sy\n\tisb"); +} + +static inline int current_el(void) { + uint64_t v; + + asm volatile ("mrs %0, currentel" : "=r"(v)); + v = (v >> 2) & 0b11; + + return v; +} + +#elif defined (__riscv) + +static inline uint64_t rdtsc(void) { + uint64_t v; + asm volatile ("rdtime %0" : "=r"(v)); + return v; +} + +uint64_t riscv_time_base_frequency(void); + +static inline uint64_t tsc_freq_arch(void) { + return riscv_time_base_frequency(); +} + +#define csr_read(csr) ({\ + size_t v;\ + asm volatile ("csrr %0, " csr : "=r"(v));\ + v;\ +}) + +#define csr_write(csr, v) ({\ + size_t old;\ + asm volatile ("csrrw %0, " csr ", %1" : "=r"(old) : "r"(v));\ + old;\ +}) + +#define make_satp(mode, ppn) (((size_t)(mode) << 60) | ((size_t)(ppn) >> 12)) + +#define locked_read(var) ({ \ + typeof(*var) locked_read__ret; \ + asm volatile ( \ + "ld %0, (%1); fence r, rw" \ + : "=r"(locked_read__ret) \ + : "r"(var) \ + : "memory" \ + ); \ + locked_read__ret; \ +}) + +extern size_t bsp_hartid; + +struct riscv_hart { + struct riscv_hart *next; + const char *isa_string; + size_t hartid; + uint32_t acpi_uid; + uint8_t mmu_type; + uint8_t flags; +}; + +#define RISCV_HART_COPROC ((uint8_t)1 << 0) // is a coprocessor +#define RISCV_HART_HAS_MMU ((uint8_t)1 << 1) // `mmu_type` field is valid + +extern struct riscv_hart *hart_list; +extern struct riscv_hart *bsp_hart; + +bool riscv_check_isa_extension_for(size_t hartid, const char *ext, size_t *maj, size_t *min); + +static inline bool riscv_check_isa_extension(const char *ext, size_t *maj, size_t *min) { + return riscv_check_isa_extension_for(bsp_hartid, ext, maj, min); +} + +void init_riscv(const char *config); + +#elif defined (__loongarch64) + +static inline uint64_t rdtsc(void) { + uint64_t v; + asm volatile ("rdtime.d %0, $zero" : "=r" (v)); + return v; +} + +static inline uint32_t loongarch_cpucfg(uint32_t reg) { + uint32_t v; + asm volatile ("cpucfg %0, %1" : "=r" (v) : "r" (reg)); + return v; +} + +static inline uint64_t tsc_freq_arch(void) { + uint32_t cc_freq = loongarch_cpucfg(4); + uint32_t cc_cfg = loongarch_cpucfg(5); + uint32_t cc_mul = cc_cfg & 0xFFFF; + uint32_t cc_div = (cc_cfg >> 16) & 0xFFFF; + if (cc_freq == 0 || cc_mul == 0 || cc_div == 0) { + return 0; + } + return (uint64_t)cc_freq * cc_mul / cc_div; +} + +#else +#error Unknown architecture +#endif + +extern uint64_t tsc_freq; +void calibrate_tsc(void); + +static inline uint64_t rdtsc_usec(void) { + uint64_t exec_ticks = rdtsc(); + if (tsc_freq == 0) { + return 0; + } + return exec_ticks / tsc_freq * 1000000 + + exec_ticks % tsc_freq * 1000000 / tsc_freq; +} + +static inline void stall(uint64_t us) { +#if defined(BIOS) + if (tsc_freq == 0) { + // ~1 us per inb on ISA/LPC bus + for (uint64_t i = 0; i < us; i++) { + inb(0x80); + } + return; + } +#endif + uint64_t ticks = (tsc_freq * us + 999999) / 1000000; + uint64_t next_stop = rdtsc() + ticks; + while (rdtsc() < next_stop); +} + +#endif diff --git a/limine/common/sys/cpu.s2.c b/limine/common/sys/cpu.s2.c new file mode 100644 index 0000000..d4d6ec8 --- /dev/null +++ b/limine/common/sys/cpu.s2.c @@ -0,0 +1,26 @@ +#include +#include +#include +#if defined(UEFI) +#include +#include +#endif + +uint64_t tsc_freq = 0; + +void calibrate_tsc(void) { + tsc_freq = tsc_freq_arch(); + if (tsc_freq != 0) { + return; + } + +#if defined(UEFI) + uint64_t tsc_start = rdtsc(); + gBS->Stall(1000); + uint64_t tsc_end = rdtsc(); + + if (tsc_end > tsc_start) { + tsc_freq = (tsc_end - tsc_start) * 1000ULL; + } +#endif +} diff --git a/limine/common/sys/cpu_riscv.c b/limine/common/sys/cpu_riscv.c new file mode 100644 index 0000000..1491146 --- /dev/null +++ b/limine/common/sys/cpu_riscv.c @@ -0,0 +1,427 @@ + +#if defined(__riscv) + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// ACPI RISC-V Hart Capabilities Table +struct rhct { + struct sdt header; + uint32_t flags; + uint64_t time_base_frequency; + uint32_t nodes_len; + uint32_t nodes_offset; + uint8_t nodes[]; +} __attribute__((packed)); + +#define RHCT_ISA_STRING 0 +#define RHCT_CMO 1 +#define RHCT_MMU 2 +#define RHCT_HART_INFO 65535 + +struct rhct_header { + uint16_t type; // node type + uint16_t size; // node size (bytes) + uint16_t revision; // node revision +} __attribute__((packed)); + +// One `struct rhct_hart_info` structure exists per hart in the system. +// The `offsets` array points to other entries in the RHCT associated with the +// hart. +struct rhct_hart_info { + struct rhct_header header; + uint16_t offsets_len; + uint32_t acpi_processor_uid; + uint32_t offsets[]; +} __attribute__((packed)); + +struct rhct_isa_string { + struct rhct_header header; + uint16_t isa_string_len; + const char isa_string[]; +} __attribute__((packed)); + +#define RISCV_MMU_TYPE_SV39 0 +#define RISCV_MMU_TYPE_SV48 1 +#define RISCV_MMU_TYPE_SV57 2 + +struct rhct_mmu { + struct rhct_header header; + uint8_t reserved0; + uint8_t mmu_type; +} __attribute__((packed)); + +void *riscv_fdt = NULL; + +size_t bsp_hartid; +struct riscv_hart *hart_list = NULL; +struct riscv_hart *bsp_hart; +static const char *current_config = NULL; + +static uint64_t cached_time_base_freq = 0; + +uint64_t riscv_time_base_frequency(void) { + return cached_time_base_freq; +} + +static struct riscv_hart *riscv_get_hart(size_t hartid) { + for (struct riscv_hart *hart = hart_list; hart != NULL; hart = hart->next) { + if (hart->hartid == hartid) { + return hart; + } + } + panic(false, "no `struct riscv_hart` for hartid %U", (uint64_t)hartid); +} + +static inline struct rhct_hart_info *rhct_get_hart_info(struct rhct *rhct, uint32_t acpi_uid) { + uint32_t offset = rhct->nodes_offset; + for (uint32_t i = 0; i < rhct->nodes_len; i++) { + if (offset + sizeof(struct rhct_header) > rhct->header.length) { + return NULL; + } + struct rhct_hart_info *node = (void *)((uintptr_t)rhct + offset); + if (node->header.type == RHCT_HART_INFO && node->acpi_processor_uid == acpi_uid) { + return node; + } + if (node->header.size == 0) { + return NULL; + } + offset += node->header.size; + } + return NULL; +} + +static void init_riscv_acpi(void) { + struct madt *madt = acpi_get_table("APIC", 0); + struct rhct *rhct = acpi_get_table("RHCT", 0); + if (madt == NULL || rhct == NULL) { + panic(false, "riscv: requires `APIC` and `RHCT` ACPI tables"); + } + + cached_time_base_freq = rhct->time_base_frequency; + + for (uint8_t *madt_ptr = (uint8_t *)madt->madt_entries_begin; + (uintptr_t)madt_ptr + 1 < (uintptr_t)madt + madt->header.length; madt_ptr += *(madt_ptr + 1)) { + if (*(madt_ptr + 1) == 0) { + break; + } + if (*madt_ptr != 0x18) { + continue; + } + struct madt_riscv_intc *intc = (struct madt_riscv_intc *)madt_ptr; + + // Ignore harts we can't do anything with. + if (!(intc->flags & MADT_RISCV_INTC_ENABLED || + intc->flags & MADT_RISCV_INTC_ONLINE_CAPABLE)) { + continue; + } + + uint32_t acpi_uid = intc->acpi_processor_uid; + size_t hartid = intc->hartid; + + struct rhct_hart_info *hart_info = rhct_get_hart_info(rhct, acpi_uid); + if (hart_info == NULL) { + panic(false, "riscv: missing rhct node for hartid %U", (uint64_t)hartid); + } + + const char *isa_string = NULL; + uint8_t mmu_type = 0; + uint8_t flags = 0; + + for (uint32_t i = 0; i < hart_info->offsets_len; i++) { + uint32_t node_offset = hart_info->offsets[i]; + if (node_offset + sizeof(struct rhct_header) > rhct->header.length) { + continue; + } + const struct rhct_header *node = (void *)((uintptr_t)rhct + node_offset); + if (node->size < sizeof(struct rhct_header) || + node_offset + node->size > rhct->header.length) { + continue; + } + switch (node->type) { + case RHCT_ISA_STRING: { + if (node->size < sizeof(struct rhct_isa_string)) + break; + struct rhct_isa_string *isa_node = (struct rhct_isa_string *)node; + // Validate string is within node bounds and null-terminated + uint16_t max_str_len = node->size - sizeof(struct rhct_isa_string); + if (isa_node->isa_string_len > max_str_len) + break; + if (isa_node->isa_string_len == 0 || + isa_node->isa_string[isa_node->isa_string_len - 1] != '\0') + break; + isa_string = isa_node->isa_string; + break; + } + case RHCT_MMU: + if (node->size < sizeof(struct rhct_mmu)) + break; + mmu_type = ((struct rhct_mmu *)node)->mmu_type; + flags |= RISCV_HART_HAS_MMU; + break; + } + } + + if (isa_string == NULL) { + print("riscv: missing isa string for hartid %U, skipping.\n", (uint64_t)hartid); + continue; + } + + if (strncmp("rv64", isa_string, 4) && strncmp("rv32", isa_string, 4)) { + print("riscv: skipping hartid %U with invalid isa string: %s\n", (uint64_t)hartid, isa_string); + continue; + } + + struct riscv_hart *hart = ext_mem_alloc(sizeof(struct riscv_hart)); + if (hart == NULL) { + panic(false, "out of memory"); + } + + hart->hartid = hartid; + hart->acpi_uid = acpi_uid; + hart->isa_string = isa_string; + hart->mmu_type = mmu_type; + hart->flags = flags; + + hart->next = hart_list; + hart_list = hart; + + if (hart->hartid == bsp_hartid) { + bsp_hart = hart; + } + } +} + +static void init_riscv_fdt(const void *fdt) { + if (fdt_check_header(fdt)) { + panic(false, "riscv: invalid device tree"); + } + + int cpus = fdt_path_offset(fdt, "/cpus"); + if (cpus < 0) { + panic(false, "riscv: missing `/cpus` node"); + } + + int len; + const void *tbf = fdt_getprop(fdt, cpus, "timebase-frequency", &len); + if (tbf != NULL) { + if (len == 8) { + cached_time_base_freq = fdt64_ld(tbf); + } else if (len == 4) { + cached_time_base_freq = fdt32_ld(tbf); + } + } + + int node; + fdt_for_each_subnode(node, fdt, cpus) { + const void *prop; + + if (!(prop = fdt_getprop(fdt, node, "device_type", NULL)) || strcmp(prop, "cpu")) { + continue; + } + + if (!(prop = fdt_getprop(fdt, node, "reg", NULL))) { + continue; + } + size_t hartid = fdt32_ld(prop); + + uint8_t flags = 0; + uint8_t mmu_type = 0; + if ((prop = fdt_getprop(fdt, node, "mmu-type", NULL))) { + if (!strcmp(prop, "riscv,sv39")) { + mmu_type = RISCV_MMU_TYPE_SV39; + flags |= RISCV_HART_HAS_MMU; + } else if (!strcmp(prop, "riscv,sv48")) { + mmu_type = RISCV_MMU_TYPE_SV48; + flags |= RISCV_HART_HAS_MMU; + } else if (!strcmp(prop, "riscv,sv57")) { + mmu_type = RISCV_MMU_TYPE_SV57; + flags |= RISCV_HART_HAS_MMU; + } + } + + const char *isa_string = fdt_getprop(fdt, node, "riscv,isa", NULL); + if (isa_string == NULL) { + print("riscv: missing isa string for hartid %U, skipping.\n", (uint64_t)hartid); + continue; + } + + if (strncmp("rv64", isa_string, 4) && strncmp("rv32", isa_string, 4)) { + print("riscv: skipping hartid %U with invalid isa string: %s\n", (uint64_t)hartid, isa_string); + continue; + } + + struct riscv_hart *hart = ext_mem_alloc(sizeof(struct riscv_hart)); + if (hart == NULL) { + panic(false, "out of memory"); + } + + hart->hartid = hartid; + hart->acpi_uid = 0; + hart->isa_string = isa_string; + hart->mmu_type = mmu_type; + hart->flags = flags; + + hart->next = hart_list; + hart_list = hart; + + if (hart->hartid == bsp_hartid) { + bsp_hart = hart; + } + } +} + +void init_riscv(const char *config) { + if (current_config == config && hart_list != NULL) { + return; + } + + while (hart_list != NULL) { + void *cur_hart = hart_list; + hart_list = hart_list->next; + pmm_free(cur_hart, sizeof(struct riscv_hart)); + } + bsp_hart = NULL; + + if (riscv_fdt != NULL) { + pmm_free(riscv_fdt, fdt_totalsize(riscv_fdt)); + riscv_fdt = NULL; + } + + bool prioritise_dtb = false; + if (config != NULL) { + prioritise_dtb = config_get_value(config, 0, "dtb_path"); + } + if (!prioritise_dtb) { + prioritise_dtb = config_get_value(NULL, 0, "global_dtb"); + } + + if (!prioritise_dtb && acpi_get_rsdp()) { + init_riscv_acpi(); + } else { + riscv_fdt = get_device_tree_blob(config, 0); + if (riscv_fdt != NULL) { + init_riscv_fdt(riscv_fdt); + } else { + panic(false, "riscv: requires DTB or ACPI"); + } + } + + if (cached_time_base_freq != 0) { + tsc_freq = cached_time_base_freq; + } + + if (bsp_hart == NULL) { + panic(false, "riscv: missing `struct riscv_hart` for BSP"); + } + + if (strncasecmp(bsp_hart->isa_string, "rv64i", 5)) { + panic(false, "unsupported cpu: %s", bsp_hart->isa_string); + } + + for (struct riscv_hart *hart = hart_list; hart != NULL; hart = hart->next) { + if (hart != bsp_hart && strcmp(bsp_hart->isa_string, hart->isa_string)) { + hart->flags |= RISCV_HART_COPROC; + } + } + + current_config = config; +} + +struct isa_extension { + const char *name; + size_t name_len; + uint32_t ver_maj; + uint32_t ver_min; +}; + +// Parse the next sequence of digit characters into an integer. +static bool parse_number(const char **s, size_t *_n) { + size_t n = 0; + bool parsed = false; + while (isdigit(**s)) { + n *= 10; + n += *(*s)++ - '0'; + parsed = true; + } + *_n = n; + return parsed; +} + +// Parse the next extension from an ISA string. +static bool parse_extension(const char **s, struct isa_extension *ext) { + if (**s == '\0') { + return false; + } + + const char *name = *s; + size_t name_len = 1; + if (**s == 's' || **s == 'S' || **s == 'x' || **s == 'X' || **s == 'z' || **s == 'Z') { + while (isalpha((*s)[name_len])) { + name_len++; + } + } + *s += name_len; + + size_t maj = 0, min = 0; + if (parse_number(s, &maj)) { + if (**s == 'p') { + *s += 1; + parse_number(s, &min); + } + } + + while (**s == '_') { + *s += 1; + } + + if (ext) { + ext->name = name; + ext->name_len = name_len; + ext->ver_maj = maj; + ext->ver_min = min; + } + return true; +} + +static bool extension_matches(const struct isa_extension *ext, const char *name) { + for (size_t i = 0; i < ext->name_len; i++) { + const char c1 = tolower(ext->name[i]); + const char c2 = tolower(*name++); + if (c2 == '\0' || c1 != c2) { + return false; + } + } + // Make sure `name` is not longer. + return *name == '\0'; +} + +bool riscv_check_isa_extension_for(size_t hartid, const char *name, size_t *maj, size_t *min) { + // Skip the `rv{32,64}` prefix so it's not parsed as extensions. + const char *isa_string = riscv_get_hart(hartid)->isa_string + 4; + + struct isa_extension ext; + while (parse_extension(&isa_string, &ext)) { + if (!extension_matches(&ext, name)) { + continue; + } + if (maj) { + *maj = ext.ver_maj; + } + if (min) { + *min = ext.ver_min; + } + return true; + } + + return false; +} + +#endif diff --git a/limine/common/sys/dummy_isr.asm_ia32 b/limine/common/sys/dummy_isr.asm_ia32 new file mode 100644 index 0000000..cb9123d --- /dev/null +++ b/limine/common/sys/dummy_isr.asm_ia32 @@ -0,0 +1,12 @@ +section .text + +extern lapic_eoi + +global dummy_isr +dummy_isr: + pusha + call lapic_eoi + popa + iretd + +section .note.GNU-stack noalloc noexec nowrite progbits diff --git a/limine/common/sys/dummy_isr.asm_x86_64 b/limine/common/sys/dummy_isr.asm_x86_64 new file mode 100644 index 0000000..2618ce9 --- /dev/null +++ b/limine/common/sys/dummy_isr.asm_x86_64 @@ -0,0 +1,28 @@ +section .text + +extern lapic_eoi + +global dummy_isr +dummy_isr: + push rax + push rcx + push rdx + push rsi + push rdi + push r8 + push r9 + push r10 + push r11 + call lapic_eoi + pop r11 + pop r10 + pop r9 + pop r8 + pop rdi + pop rsi + pop rdx + pop rcx + pop rax + iretq + +section .note.GNU-stack noalloc noexec nowrite progbits diff --git a/limine/common/sys/e820.h b/limine/common/sys/e820.h new file mode 100644 index 0000000..b908625 --- /dev/null +++ b/limine/common/sys/e820.h @@ -0,0 +1,13 @@ +#ifndef SYS__E820_H__ +#define SYS__E820_H__ + +#include +#include +#include + +extern struct memmap_entry e820_map[]; +extern size_t e820_entries; + +void init_e820(void); + +#endif diff --git a/limine/common/sys/e820.s2.c b/limine/common/sys/e820.s2.c new file mode 100644 index 0000000..82a2222 --- /dev/null +++ b/limine/common/sys/e820.s2.c @@ -0,0 +1,44 @@ +#if defined (BIOS) + +#include +#include +#include +#include +#include +#include +#include + +#define MAX_E820_ENTRIES 256 + +struct memmap_entry e820_map[MAX_E820_ENTRIES]; +size_t e820_entries = 0; + +void init_e820(void) { + struct rm_regs r = {0}; + + for (size_t i = 0; i < MAX_E820_ENTRIES; i++) { + struct memmap_entry entry; + + r.eax = 0xe820; + r.ecx = 24; + r.edx = 0x534d4150; + r.edi = (uint32_t)&entry; + rm_int(0x15, &r, &r); + + if (r.eflags & EFLAGS_CF) { + e820_entries = i; + return; + } + + e820_map[i] = entry; + + if (!r.ebx) { + e820_entries = ++i; + return; + } + } + + panic(false, "Too many E820 entries!"); +} + +#endif diff --git a/limine/common/sys/exceptions.s2.c b/limine/common/sys/exceptions.s2.c new file mode 100644 index 0000000..cbe68f8 --- /dev/null +++ b/limine/common/sys/exceptions.s2.c @@ -0,0 +1,50 @@ +#include +#include +#include +#include + +#if defined (BIOS) + +static const char *exception_names[] = { + "Division", + "Debug", + "NMI", + "Breakpoint", + "Overflow", + "Bound range exceeded", + "Invalid opcode", + "Device not available", + "Double fault", + "???", + "Invalid TSS", + "Segment not present", + "Stack-segment fault", + "General protection fault", + "Page fault", + "???", + "x87", + "Alignment check", + "Machine check", + "SIMD", + "Virtualisation", + "???", + "???", + "???", + "???", + "???", + "???", + "???", + "???", + "???", + "Security" +}; + +void except(uint32_t exception, uint32_t error_code, uint32_t ebp, uint32_t eip) { + (void)ebp; + const char *exception_name = exception < SIZEOF_ARRAY(exception_names) + ? exception_names[exception] + : "Unknown"; + panic(false, "%s exception at %x. Error code: %x", exception_name, eip, error_code); +} + +#endif diff --git a/limine/common/sys/gdt.h b/limine/common/sys/gdt.h new file mode 100644 index 0000000..d749baf --- /dev/null +++ b/limine/common/sys/gdt.h @@ -0,0 +1,29 @@ +#ifndef SYS__GDT_H__ +#define SYS__GDT_H__ + +#include + +struct gdtr { + uint16_t limit; +#if defined (__x86_64__) + uint64_t ptr; +#elif defined (__i386__) + uint32_t ptr; + uint32_t ptr_hi; +#endif +} __attribute__((packed)); + +struct gdt_desc { + uint16_t limit; + uint16_t base_low; + uint8_t base_mid; + uint8_t access; + uint8_t granularity; + uint8_t base_hi; +} __attribute__((packed)); + +extern struct gdtr gdt; + +void init_gdt(void); + +#endif diff --git a/limine/common/sys/gdt.s2.c b/limine/common/sys/gdt.s2.c new file mode 100644 index 0000000..a7d564a --- /dev/null +++ b/limine/common/sys/gdt.s2.c @@ -0,0 +1,95 @@ +#if defined (__x86_64__) || defined (__i386__) + +#include +#include +#include +#include + +static struct gdt_desc gdt_descs[] = { + {0}, + + { + .limit = 0xffff, + .base_low = 0x0000, + .base_mid = 0x00, + .access = 0b10011011, + .granularity = 0b00000000, + .base_hi = 0x00 + }, + + { + .limit = 0xffff, + .base_low = 0x0000, + .base_mid = 0x00, + .access = 0b10010011, + .granularity = 0b00000000, + .base_hi = 0x00 + }, + + { + .limit = 0xffff, + .base_low = 0x0000, + .base_mid = 0x00, + .access = 0b10011011, + .granularity = 0b11001111, + .base_hi = 0x00 + }, + + { + .limit = 0xffff, + .base_low = 0x0000, + .base_mid = 0x00, + .access = 0b10010011, + .granularity = 0b11001111, + .base_hi = 0x00 + }, + + { + .limit = 0x0000, + .base_low = 0x0000, + .base_mid = 0x00, + .access = 0b10011011, + .granularity = 0b00100000, + .base_hi = 0x00 + }, + + { + .limit = 0x0000, + .base_low = 0x0000, + .base_mid = 0x00, + .access = 0b10010011, + .granularity = 0b00000000, + .base_hi = 0x00 + }, + + { // 0x38: TSS descriptor low (base 0, limit 0) + .limit = 0x0000, + .base_low = 0x0000, + .base_mid = 0x00, + .access = 0x89, + .granularity = 0x00, + .base_hi = 0x00 + }, + {0} // 0x40: TSS descriptor high (base upper = 0) +}; + +#if defined (BIOS) +__attribute__((section(".realmode"))) +#endif +struct gdtr gdt = { + sizeof(gdt_descs) - 1, + (uintptr_t)gdt_descs, +#if defined (__i386__) + 0 +#endif +}; + +#if defined (UEFI) +void init_gdt(void) { + struct gdt_desc *gdt_copy = ext_mem_alloc(sizeof(gdt_descs)); + memcpy(gdt_copy, gdt_descs, sizeof(gdt_descs)); + gdt.ptr = (uintptr_t)gdt_copy; +} +#endif + +#endif diff --git a/limine/common/sys/idt.c b/limine/common/sys/idt.c new file mode 100644 index 0000000..4669cf9 --- /dev/null +++ b/limine/common/sys/idt.c @@ -0,0 +1,71 @@ +#if defined (__x86_64__) || defined (__i386__) + +#include +#include +#include +#include +#include +#include +#include +#include + +static struct idt_entry *dummy_idt = NULL; + +void dummy_isr(void); + +void init_flush_irqs(void) { + size_t dummy_idt_size = 256 * sizeof(struct idt_entry); + dummy_idt = ext_mem_alloc(dummy_idt_size); + + for (size_t i = 0; i < 256; i++) { + dummy_idt[i].offset_lo = (uint16_t)(uintptr_t)dummy_isr; + dummy_idt[i].type_attr = 0x8e; +#if defined (__i386__) + dummy_idt[i].selector = 0x18; + dummy_idt[i].offset_hi = (uint16_t)((uintptr_t)dummy_isr >> 16); +#elif defined (__x86_64__) + dummy_idt[i].selector = 0x28; + dummy_idt[i].offset_mid = (uint16_t)((uintptr_t)dummy_isr >> 16); + dummy_idt[i].offset_hi = (uint32_t)((uintptr_t)dummy_isr >> 32); +#endif + } +} + +int irq_flush_type = IRQ_NO_FLUSH; + +void flush_irqs(void) { + switch (irq_flush_type) { + case IRQ_PIC_ONLY_FLUSH: + pic_flush(); + // FALLTHRU + case IRQ_NO_FLUSH: + return; + case IRQ_PIC_APIC_FLUSH: + break; + default: + panic(false, "Invalid IRQ flush type"); + } + + struct idtr old_idt; + asm volatile ("sidt %0" : "=m"(old_idt) :: "memory"); + + struct idtr new_idt = { + 256 * sizeof(struct idt_entry) - 1, + (uintptr_t)dummy_idt + }; + asm volatile ("lidt %0" :: "m"(new_idt) : "memory"); + + // Flush the legacy PIC so we know the remaining ints come from the LAPIC + pic_flush(); + + asm volatile ("sti" ::: "memory"); + + // Delay a while to make sure we catch ALL pending IRQs + stall(10000); + + asm volatile ("cli" ::: "memory"); + + asm volatile ("lidt %0" :: "m"(old_idt) : "memory"); +} + +#endif diff --git a/limine/common/sys/idt.h b/limine/common/sys/idt.h new file mode 100644 index 0000000..a769131 --- /dev/null +++ b/limine/common/sys/idt.h @@ -0,0 +1,59 @@ +#ifndef SYS__IDT_H__ +#define SYS__IDT_H__ + +#include + +#if defined (__i386__) + +struct idtr { + uint16_t limit; + uint32_t ptr; +} __attribute__((packed)); + +struct idt_entry { + uint16_t offset_lo; + uint16_t selector; + uint8_t unused; + uint8_t type_attr; + uint16_t offset_hi; +} __attribute__((packed)); + +#elif defined (__x86_64__) + +struct idtr { + uint16_t limit; + uint64_t ptr; +} __attribute__((packed)); + +struct idt_entry { + uint16_t offset_lo; + uint16_t selector; + uint8_t ist; + uint8_t type_attr; + uint16_t offset_mid; + uint32_t offset_hi; + uint32_t reserved; +} __attribute__((packed)); + +#endif + +#if defined (BIOS) + +extern struct idtr idt; + +void init_idt(void); + +#endif + +enum { + IRQ_NO_FLUSH, + IRQ_PIC_ONLY_FLUSH, + IRQ_PIC_APIC_FLUSH +}; + +extern int irq_flush_type; + +void init_flush_irqs(void); +void flush_irqs(void); + +#endif diff --git a/limine/common/sys/idt.s2.c b/limine/common/sys/idt.s2.c new file mode 100644 index 0000000..33c2d73 --- /dev/null +++ b/limine/common/sys/idt.s2.c @@ -0,0 +1,35 @@ +#include +#include +#include +#include + +#if defined (BIOS) + +static struct idt_entry idt_entries[32]; + +__attribute__((section(".realmode"))) +struct idtr idt = { + sizeof(idt_entries) - 1, + (uintptr_t)idt_entries +}; + +static void register_interrupt_handler(size_t vec, void *handler, uint8_t type) { + uint32_t p = (uint32_t)handler; + + idt_entries[vec].offset_lo = (uint16_t)p; + idt_entries[vec].selector = 0x18; + idt_entries[vec].unused = 0; + idt_entries[vec].type_attr = type; + idt_entries[vec].offset_hi = (uint16_t)(p >> 16); +} + +extern void *exceptions[]; + +void init_idt(void) { + for (size_t i = 0; i < SIZEOF_ARRAY(idt_entries); i++) + register_interrupt_handler(i, exceptions[i], 0x8e); + + asm volatile ("lidt %0" :: "m"(idt) : "memory"); +} + +#endif diff --git a/limine/common/sys/int_thunks.s2.asm_bios_ia32 b/limine/common/sys/int_thunks.s2.asm_bios_ia32 new file mode 100644 index 0000000..715f721 --- /dev/null +++ b/limine/common/sys/int_thunks.s2.asm_bios_ia32 @@ -0,0 +1,41 @@ +section .text + +extern except + +%macro raise_exception 1 +align 16 +raise_exception_%1: + cld +%if %1 == 8 || %1 == 10 || %1 == 11 || %1 == 12 || %1 == 13 || %1 == 14 || %1 == 17 || %1 == 30 + pop eax +%else + xor eax, eax +%endif + push ebp + mov ebp, esp + push eax + push %1 + call except +%endmacro + +%assign i 0 +%rep 32 +raise_exception i +%assign i i+1 +%endrep + +section .rodata + +%macro raise_exception_getaddr 1 +dd raise_exception_%1 +%endmacro + +global exceptions +exceptions: +%assign i 0 +%rep 32 +raise_exception_getaddr i +%assign i i+1 +%endrep + +section .note.GNU-stack noalloc noexec nowrite progbits diff --git a/limine/common/sys/iommu.c b/limine/common/sys/iommu.c new file mode 100644 index 0000000..16cca3a --- /dev/null +++ b/limine/common/sys/iommu.c @@ -0,0 +1,144 @@ +#if defined (__x86_64__) || defined (__i386__) + +#include +#include +#include +#include +#include +#include +#include + +// Intel VT-d registers +#define VTD_GCMD_REG 0x18 +#define VTD_GSTS_REG 0x1C + +// GSTS/GCMD bit positions +#define VTD_GSTS_TES (1u << 31) // Translation Enable Status +#define VTD_GSTS_QIES (1u << 26) // Queued Invalidation Enable Status +#define VTD_GSTS_IRES (1u << 25) // Interrupt Remapping Enable Status + +// Mask to clear one-shot command bits when reading GSTS for use as GCMD base. +// One-shot bits auto-clear after hardware processes them and must not be +// carried over from GSTS into GCMD writes: +// Bit 30: SRTP (Set Root Table Pointer) +// Bit 29: SFL (Set Fault Log) +// Bit 27: WBF (Write Buffer Flush) +// Bit 24: SIRTP (Set Interrupt Remap Table Pointer) +// All other bits (TE, EAFL, QIE, IRE, CFI) are persistent toggles. +#define VTD_GCMD_ONESHOT_MASK 0x96FFFFFF + +static void vtd_disable_unit(uintptr_t reg_base) { + uint32_t sts = mmind(reg_base + VTD_GSTS_REG); + + // Disable DMA translation first (most urgent: prevents stale lookups) + if (sts & VTD_GSTS_TES) { + uint32_t gcmd = (sts & VTD_GCMD_ONESHOT_MASK) & ~VTD_GSTS_TES; + mmoutd(reg_base + VTD_GCMD_REG, gcmd); + + while ((sts = mmind(reg_base + VTD_GSTS_REG)) & VTD_GSTS_TES) { + asm volatile ("pause"); + } + } + + // Disable interrupt remapping (depends on QIE, so disable before QIE) + if (sts & VTD_GSTS_IRES) { + uint32_t gcmd = (sts & VTD_GCMD_ONESHOT_MASK) & ~VTD_GSTS_IRES; + mmoutd(reg_base + VTD_GCMD_REG, gcmd); + + while ((sts = mmind(reg_base + VTD_GSTS_REG)) & VTD_GSTS_IRES) { + asm volatile ("pause"); + } + } + + // Disable queued invalidation last (was prerequisite for IRE) + if (sts & VTD_GSTS_QIES) { + uint32_t gcmd = (sts & VTD_GCMD_ONESHOT_MASK) & ~VTD_GSTS_QIES; + mmoutd(reg_base + VTD_GCMD_REG, gcmd); + + while ((sts = mmind(reg_base + VTD_GSTS_REG)) & VTD_GSTS_QIES) { + asm volatile ("pause"); + } + } +} + +static void vtd_disable_all(void) { + struct sdt *dmar = acpi_get_table("DMAR", 0); + if (dmar == NULL) { + return; + } + + // DMAR header is 48 bytes: 36 (SDT) + 1 (width) + 1 (flags) + 10 (reserved) + uint8_t *ptr = (uint8_t *)dmar + 48; + uint8_t *end = (uint8_t *)dmar + dmar->length; + + while (ptr + 4 <= end) { + uint16_t type = *(uint16_t *)(ptr + 0); + uint16_t length = *(uint16_t *)(ptr + 2); + + if (length < 4 || ptr + length > end) { + break; + } + + // Type 0 = DRHD (DMA Remapping Hardware Unit Definition) + if (type == 0 && length >= 16) { + uint64_t reg_base; + memcpy(®_base, ptr + 8, sizeof(reg_base)); + vtd_disable_unit((uintptr_t)reg_base); + } + + ptr += length; + } +} + +// AMD IOMMU control register (64-bit at offset 0x18) +#define AMDVI_CONTROL_REG 0x18 + +static void amdvi_disable_unit(uintptr_t mmio_base) { + // Read low 32 bits of the 64-bit control register + uint32_t ctrl_lo = mmind(mmio_base + AMDVI_CONTROL_REG); + + if (!(ctrl_lo & (1u << 0))) { + return; // IOMMU not enabled + } + + // Clear IommuEn (bit 0) — the master enable for all IOMMU functionality. + // Takes effect immediately, no polling needed (unlike Intel VT-d). + ctrl_lo &= ~(1u << 0); + mmoutd(mmio_base + AMDVI_CONTROL_REG, ctrl_lo); +} + +static void amdvi_disable_all(void) { + struct sdt *ivrs = acpi_get_table("IVRS", 0); + if (ivrs == NULL) { + return; + } + + // IVRS header is 48 bytes: 36 (SDT) + 4 (IVinfo) + 8 (reserved) + uint8_t *ptr = (uint8_t *)ivrs + 48; + uint8_t *end = (uint8_t *)ivrs + ivrs->length; + + while (ptr + 4 <= end) { + uint8_t type = *(uint8_t *)(ptr + 0); + uint16_t length = *(uint16_t *)(ptr + 2); + + if (length < 4 || ptr + length > end) { + break; + } + + // IVHD types: 0x10 (basic), 0x11 (extended), 0x40 (extended, newer) + if ((type == 0x10 || type == 0x11 || type == 0x40) && length >= 16) { + uint64_t mmio_base; + memcpy(&mmio_base, ptr + 8, sizeof(mmio_base)); + amdvi_disable_unit((uintptr_t)mmio_base); + } + + ptr += length; + } +} + +void iommu_disable_all(void) { + vtd_disable_all(); + amdvi_disable_all(); +} + +#endif diff --git a/limine/common/sys/iommu.h b/limine/common/sys/iommu.h new file mode 100644 index 0000000..6826eef --- /dev/null +++ b/limine/common/sys/iommu.h @@ -0,0 +1,6 @@ +#ifndef SYS__IOMMU_H__ +#define SYS__IOMMU_H__ + +void iommu_disable_all(void); + +#endif diff --git a/limine/common/sys/lapic.c b/limine/common/sys/lapic.c new file mode 100644 index 0000000..bc8c94d --- /dev/null +++ b/limine/common/sys/lapic.c @@ -0,0 +1,507 @@ +#if defined (__x86_64__) || defined (__i386__) + +#include +#include +#include +#include +#include +#include +#include +#include + +#define LAPIC_REG_LVT_CMCI 0x2f0 +#define LAPIC_REG_LVT_TIMER 0x320 +#define LAPIC_REG_LVT_THERMAL 0x330 +#define LAPIC_REG_LVT_PMC 0x340 +#define LAPIC_REG_LVT_LINT0 0x350 +#define LAPIC_REG_LVT_LINT1 0x360 +#define LAPIC_REG_LVT_ERROR 0x370 +#define LAPIC_REG_SVR 0x0f0 +#define LAPIC_REG_TPR 0x080 +#define LAPIC_REG_VERSION 0x030 + +static uint32_t pending_lint0 = UINT32_MAX; // no override +static uint32_t pending_lint1 = UINT32_MAX; // no override + +static uint32_t lapic_madt_nmi_flags_to_lvt(uint16_t flags) { + uint32_t lvt = 0x10400; // masked + NMI delivery mode + + // Polarity: bits 1:0 of flags + uint8_t polarity = flags & 0x3; + if (polarity == 0x3) { + lvt |= (1 << 13); // active low + } + // 0b00 (conforms) and 0b01 (active high) leave bit 13 clear + + // Trigger mode: bits 3:2 of flags + uint8_t trigger = (flags >> 2) & 0x3; + if (trigger == 0x3) { + lvt |= (1 << 15); // level triggered + } + // 0b00 (conforms) and 0b01 (edge) leave bit 15 clear + + return lvt; +} + +void lapic_prep_lint(struct madt *madt, uint32_t acpi_uid, bool x2apic) { + pending_lint0 = UINT32_MAX; // no override + pending_lint1 = UINT32_MAX; // no override + + // Walk MADT entries looking for NMI entries + for (uint8_t *madt_ptr = (uint8_t *)madt->madt_entries_begin; + (uintptr_t)madt_ptr + 1 < (uintptr_t)madt + madt->header.length; + madt_ptr += *(madt_ptr + 1)) { + if (*(madt_ptr + 1) == 0) { + break; + } + switch (*madt_ptr) { + case 4: { + // Local APIC NMI + if (*(madt_ptr + 1) < sizeof(struct madt_lapic_nmi)) { + continue; + } + + struct madt_lapic_nmi *nmi = (void *)madt_ptr; + + // Match all processors (0xff) or specific UID + if (nmi->acpi_processor_uid != 0xff && nmi->acpi_processor_uid != (uint8_t)acpi_uid) { + continue; + } + + uint32_t lvt = lapic_madt_nmi_flags_to_lvt(nmi->flags); + if (nmi->lint == 0) { + pending_lint0 = lvt; + } else if (nmi->lint == 1) { + pending_lint1 = lvt; + } + continue; + } + case 0x0a: { + // Local x2APIC NMI + if (!x2apic) { + continue; + } + if (*(madt_ptr + 1) < sizeof(struct madt_x2apic_nmi)) { + continue; + } + + struct madt_x2apic_nmi *nmi = (void *)madt_ptr; + + // Match all processors (0xffffffff) or specific UID + if (nmi->acpi_processor_uid != 0xffffffff && nmi->acpi_processor_uid != acpi_uid) { + continue; + } + + uint32_t lvt = lapic_madt_nmi_flags_to_lvt(nmi->flags); + if (nmi->lint == 0) { + pending_lint0 = lvt; + } else if (nmi->lint == 1) { + pending_lint1 = lvt; + } + continue; + } + } + } +} + +static bool lvt_should_mask(uint32_t lvt) { + switch ((lvt >> 8) & 7) { + case 0b000: // Fixed + case 0b001: // Lowest Priority + case 0b100: // NMI + case 0b111: // ExtINT + return true; + default: // SMI, INIT, Reserved + return false; + } +} + +void lapic_configure_handoff_state(void) { + bool is_x2 = !!(rdmsr(0x1b) & (1 << 10)); + + uint32_t max_lvt; + if (is_x2) { + max_lvt = (x2apic_read(LAPIC_REG_VERSION) >> 16) & 0xff; + } else { + max_lvt = (lapic_read(LAPIC_REG_VERSION) >> 16) & 0xff; + } + + uint32_t lvt; + + if (is_x2) { + x2apic_write(LAPIC_REG_SVR, 0x1ff); + x2apic_write(LAPIC_REG_TPR, 0); + if (max_lvt >= 6) { + lvt = x2apic_read(LAPIC_REG_LVT_CMCI); + if (lvt_should_mask(lvt)) { + x2apic_write(LAPIC_REG_LVT_CMCI, lvt | (1 << 16)); + } + } + lvt = x2apic_read(LAPIC_REG_LVT_TIMER); + if (lvt_should_mask(lvt)) { + x2apic_write(LAPIC_REG_LVT_TIMER, lvt | (1 << 16)); + } + if (max_lvt >= 5) { + lvt = x2apic_read(LAPIC_REG_LVT_THERMAL); + if (lvt_should_mask(lvt)) { + x2apic_write(LAPIC_REG_LVT_THERMAL, lvt | (1 << 16)); + } + } + if (max_lvt >= 4) { + lvt = x2apic_read(LAPIC_REG_LVT_PMC); + if (lvt_should_mask(lvt)) { + x2apic_write(LAPIC_REG_LVT_PMC, lvt | (1 << 16)); + } + } + lvt = x2apic_read(LAPIC_REG_LVT_ERROR); + if (lvt_should_mask(lvt)) { + x2apic_write(LAPIC_REG_LVT_ERROR, lvt | (1 << 16)); + } + lvt = x2apic_read(LAPIC_REG_LVT_LINT0); + if (lvt_should_mask(lvt)) { + x2apic_write(LAPIC_REG_LVT_LINT0, pending_lint0 != UINT32_MAX ? pending_lint0 : lvt | (1 << 16)); + } + lvt = x2apic_read(LAPIC_REG_LVT_LINT1); + if (lvt_should_mask(lvt)) { + x2apic_write(LAPIC_REG_LVT_LINT1, pending_lint1 != UINT32_MAX ? pending_lint1 : lvt | (1 << 16)); + } + } else { + lapic_write(LAPIC_REG_SVR, 0x1ff); + lapic_write(LAPIC_REG_TPR, 0); + if (max_lvt >= 6) { + lvt = lapic_read(LAPIC_REG_LVT_CMCI); + if (lvt_should_mask(lvt)) { + lapic_write(LAPIC_REG_LVT_CMCI, lvt | (1 << 16)); + } + } + lvt = lapic_read(LAPIC_REG_LVT_TIMER); + if (lvt_should_mask(lvt)) { + lapic_write(LAPIC_REG_LVT_TIMER, lvt | (1 << 16)); + } + if (max_lvt >= 5) { + lvt = lapic_read(LAPIC_REG_LVT_THERMAL); + if (lvt_should_mask(lvt)) { + lapic_write(LAPIC_REG_LVT_THERMAL, lvt | (1 << 16)); + } + } + if (max_lvt >= 4) { + lvt = lapic_read(LAPIC_REG_LVT_PMC); + if (lvt_should_mask(lvt)) { + lapic_write(LAPIC_REG_LVT_PMC, lvt | (1 << 16)); + } + } + lvt = lapic_read(LAPIC_REG_LVT_ERROR); + if (lvt_should_mask(lvt)) { + lapic_write(LAPIC_REG_LVT_ERROR, lvt | (1 << 16)); + } + lvt = lapic_read(LAPIC_REG_LVT_LINT0); + if (lvt_should_mask(lvt)) { + lapic_write(LAPIC_REG_LVT_LINT0, pending_lint0 != UINT32_MAX ? pending_lint0 : lvt | (1 << 16)); + } + lvt = lapic_read(LAPIC_REG_LVT_LINT1); + if (lvt_should_mask(lvt)) { + lapic_write(LAPIC_REG_LVT_LINT1, pending_lint1 != UINT32_MAX ? pending_lint1 : lvt | (1 << 16)); + } + } +} + +void lapic_configure_bsp(void) { + struct madt *madt = acpi_get_table("APIC", 0); + if (madt == NULL) { + return; + } + + // Detect x2APIC from MSR + bool is_x2 = !!(rdmsr(0x1b) & (1 << 10)); + + // Find the BSP entry by matching LAPIC ID + uint32_t bsp_lapic_id; + if (is_x2) { + bsp_lapic_id = x2apic_read(LAPIC_REG_ID); + } else { + bsp_lapic_id = lapic_read(LAPIC_REG_ID) >> 24; + } + + uint32_t bsp_acpi_uid = 0; + + for (uint8_t *madt_ptr = (uint8_t *)madt->madt_entries_begin; + (uintptr_t)madt_ptr + 1 < (uintptr_t)madt + madt->header.length; + madt_ptr += *(madt_ptr + 1)) { + if (*(madt_ptr + 1) == 0) { + break; + } + switch (*madt_ptr) { + case 0: { + if (*(madt_ptr + 1) < sizeof(struct madt_lapic)) { + continue; + } + struct madt_lapic *lapic = (void *)madt_ptr; + if (lapic->lapic_id == bsp_lapic_id) { + bsp_acpi_uid = lapic->acpi_processor_uid; + goto found; + } + continue; + } + case 9: { + if (!is_x2) { + continue; + } + if (*(madt_ptr + 1) < sizeof(struct madt_x2apic)) { + continue; + } + struct madt_x2apic *x2lapic = (void *)madt_ptr; + if (x2lapic->x2apic_id == bsp_lapic_id) { + bsp_acpi_uid = x2lapic->acpi_processor_uid; + goto found; + } + continue; + } + } + } + +found: + lapic_prep_lint(madt, bsp_acpi_uid, is_x2); + lapic_configure_handoff_state(); +} + +struct dmar { + struct sdt header; + uint8_t host_address_width; + uint8_t flags; + uint8_t reserved[10]; + symbol remapping_structures; +} __attribute__((packed)); + +bool lapic_check(void) { + uint32_t eax, ebx, ecx, edx; + if (!cpuid(1, 0, &eax, &ebx, &ecx, &edx)) + return false; + + if (!(edx & (1 << 9))) + return false; + + return true; +} + +uint32_t lapic_read(uint32_t reg) { + size_t lapic_mmio_base = (size_t)(rdmsr(0x1b) & 0xfffff000); + return mmind(lapic_mmio_base + reg); +} + +void lapic_write(uint32_t reg, uint32_t data) { + size_t lapic_mmio_base = (size_t)(rdmsr(0x1b) & 0xfffff000); + mmoutd(lapic_mmio_base + reg, data); +} + +void lapic_icr_wait(void) { + for (int i = 0; i < 1000000; i++) { + if (!(lapic_read(LAPIC_REG_ICR0) & (1 << 12))) { + return; + } + asm volatile ("pause"); + } +} + +bool x2apic_check(void) { + uint32_t eax, ebx, ecx, edx; + if (!cpuid(1, 0, &eax, &ebx, &ecx, &edx)) + return false; + + if (!(ecx & (1 << 21))) + return false; + + // According to the Intel VT-d spec, we're required + // to check if bit 0 and 1 of the flags field of the + // DMAR ACPI table are set, and if they are, we should + // not report x2APIC capabilities. + struct dmar *dmar = acpi_get_table("DMAR", 0); + if (!dmar) + return true; + + if ((dmar->flags & (1 << 0)) && (dmar->flags & (1 << 1))) + return false; + + return true; +} + +static bool x2apic_mode = false; + +bool x2apic_enable(void) { + if (!x2apic_check()) + return false; + + uint64_t ia32_apic_base = rdmsr(0x1b); + ia32_apic_base |= (1 << 10); + wrmsr(0x1b, ia32_apic_base); + + x2apic_mode = true; + + return true; +} + +bool x2apic_disable(void) { + uint64_t msr = rdmsr(0x1b); + if (!(msr & (1 << 10))) + return true; + + // Check for LEGACY_XAPIC_DISABLED (Intel Meteor Lake+). + // CPUID.07H.0:EDX[29] enumerates IA32_ARCH_CAPABILITIES MSR (0x10A). + // IA32_ARCH_CAPABILITIES bit 21 = XAPIC_DISABLE feature supported. + // IA32_XAPIC_DISABLE_STATUS MSR (0xBD) bit 0 = xAPIC permanently disabled. + uint32_t eax, ebx, ecx, edx; + if (cpuid(7, 0, &eax, &ebx, &ecx, &edx) && (edx & (1 << 29))) { + uint64_t arch_caps = rdmsr(0x10a); + if (arch_caps & (1 << 21)) { + if (rdmsr(0xbd) & 1) { + return false; + } + } + } + + // Save LAPIC state; clearing EN resets all registers except APIC ID. + uint32_t max_lvt = (x2apic_read(LAPIC_REG_VERSION) >> 16) & 0xff; + uint32_t saved_svr = x2apic_read(LAPIC_REG_SVR); + uint32_t saved_tpr = x2apic_read(LAPIC_REG_TPR); + uint32_t saved_timer = x2apic_read(LAPIC_REG_LVT_TIMER); + uint32_t saved_lint0 = x2apic_read(LAPIC_REG_LVT_LINT0); + uint32_t saved_lint1 = x2apic_read(LAPIC_REG_LVT_LINT1); + uint32_t saved_error = x2apic_read(LAPIC_REG_LVT_ERROR); + uint32_t saved_pmc = max_lvt >= 4 ? x2apic_read(LAPIC_REG_LVT_PMC) : 0; + uint32_t saved_thermal = max_lvt >= 5 ? x2apic_read(LAPIC_REG_LVT_THERMAL) : 0; + uint32_t saved_cmci = max_lvt >= 6 ? x2apic_read(LAPIC_REG_LVT_CMCI) : 0; + + // Transition x2APIC -> disabled -> xAPIC. + // Direct x2APIC -> xAPIC is an invalid transition (#GP). + msr &= ~((1ULL << 11) | (1ULL << 10)); + wrmsr(0x1b, msr); + + msr |= (1ULL << 11); + wrmsr(0x1b, msr); + + x2apic_mode = false; + + // Restore LAPIC state. SVR is restored last to re-enable the APIC. + lapic_write(LAPIC_REG_TPR, saved_tpr); + lapic_write(LAPIC_REG_LVT_TIMER, saved_timer); + lapic_write(LAPIC_REG_LVT_LINT0, saved_lint0); + lapic_write(LAPIC_REG_LVT_LINT1, saved_lint1); + lapic_write(LAPIC_REG_LVT_ERROR, saved_error); + if (max_lvt >= 4) lapic_write(LAPIC_REG_LVT_PMC, saved_pmc); + if (max_lvt >= 5) lapic_write(LAPIC_REG_LVT_THERMAL, saved_thermal); + if (max_lvt >= 6) lapic_write(LAPIC_REG_LVT_CMCI, saved_cmci); + lapic_write(LAPIC_REG_SVR, saved_svr); + + return true; +} + +void lapic_eoi(void) { + if (!x2apic_mode) { + lapic_write(0xb0, 0); + } else { + x2apic_write(0xb0, 0); + } +} + +uint64_t x2apic_read(uint32_t reg) { + return rdmsr(0x800 + (reg >> 4)); +} + +void x2apic_write(uint32_t reg, uint64_t data) { + wrmsr(0x800 + (reg >> 4), data); +} + +static struct madt_io_apic **io_apics = NULL; +static size_t max_io_apics = 0; + +void init_io_apics(void) { + static bool already_inited = false; + if (already_inited) { + return; + } + + struct madt *madt = acpi_get_table("APIC", 0); + + if (madt == NULL) { + goto out; + } + + for (uint8_t *madt_ptr = (uint8_t *)madt->madt_entries_begin; + (uintptr_t)madt_ptr + 1 < (uintptr_t)madt + madt->header.length; + madt_ptr += *(madt_ptr + 1)) { + if (*(madt_ptr + 1) == 0) { + break; + } + switch (*madt_ptr) { + case 1: { + if (*(madt_ptr + 1) < sizeof(struct madt_io_apic)) + continue; + max_io_apics++; + continue; + } + } + } + + io_apics = ext_mem_alloc(max_io_apics * sizeof(struct madt_io_apic *)); + max_io_apics = 0; + + for (uint8_t *madt_ptr = (uint8_t *)madt->madt_entries_begin; + (uintptr_t)madt_ptr + 1 < (uintptr_t)madt + madt->header.length; + madt_ptr += *(madt_ptr + 1)) { + if (*(madt_ptr + 1) == 0) { + break; + } + switch (*madt_ptr) { + case 1: { + if (*(madt_ptr + 1) < sizeof(struct madt_io_apic)) + continue; + io_apics[max_io_apics++] = (void *)madt_ptr; + continue; + } + } + } + +out: + already_inited = true; +} + +uint32_t io_apic_read(size_t io_apic, uint32_t reg) { + uintptr_t base = (uintptr_t)io_apics[io_apic]->address; + mmoutd(base, reg); + return mmind(base + 16); +} + +void io_apic_write(size_t io_apic, uint32_t reg, uint32_t value) { + uintptr_t base = (uintptr_t)io_apics[io_apic]->address; + mmoutd(base, reg); + mmoutd(base + 16, value); +} + +uint32_t io_apic_gsi_count(size_t io_apic) { + return ((io_apic_read(io_apic, 1) & 0xff0000) >> 16) + 1; +} + +void io_apic_mask_all(bool mask_nmi_and_extint) { + for (size_t i = 0; i < max_io_apics; i++) { + uint32_t gsi_count = io_apic_gsi_count(i); + for (uint32_t j = 0; j < gsi_count; j++) { + uintptr_t ioredtbl = j * 2 + 16; + switch ((io_apic_read(i, ioredtbl) >> 8) & 0b111) { + case 0b000: // Fixed + case 0b001: // Lowest Priority + break; + case 0b100: // NMI + case 0b111: // ExtINT + if (!mask_nmi_and_extint) { + continue; + } + break; + default: + continue; + } + + io_apic_write(i, ioredtbl, io_apic_read(i, ioredtbl) | (1 << 16)); + } + } +} + +#endif diff --git a/limine/common/sys/lapic.h b/limine/common/sys/lapic.h new file mode 100644 index 0000000..876cc05 --- /dev/null +++ b/limine/common/sys/lapic.h @@ -0,0 +1,38 @@ +#ifndef SYS__APIC_H__ +#define SYS__APIC_H__ + +#include +#include +#include + +struct madt; + +#define LAPIC_REG_ICR0 0x300 +#define LAPIC_REG_ICR1 0x310 +#define LAPIC_REG_SPURIOUS 0x0f0 +#define LAPIC_REG_EOI 0x0b0 +#define LAPIC_REG_ID 0x020 + +bool lapic_check(void); +void lapic_eoi(void); +uint32_t lapic_read(uint32_t reg); +void lapic_write(uint32_t reg, uint32_t data); +void lapic_icr_wait(void); + +bool x2apic_check(void); +bool x2apic_enable(void); +bool x2apic_disable(void); +uint64_t x2apic_read(uint32_t reg); +void x2apic_write(uint32_t reg, uint64_t data); + +void lapic_configure_bsp(void); +void lapic_prep_lint(struct madt *madt, uint32_t acpi_uid, bool x2apic); +void lapic_configure_handoff_state(void); + +void init_io_apics(void); +uint32_t io_apic_read(size_t io_apic, uint32_t reg); +void io_apic_write(size_t io_apic, uint32_t reg, uint32_t value); +uint32_t io_apic_gsi_count(size_t io_apic); +void io_apic_mask_all(bool mask_nmi_and_extint); + +#endif diff --git a/limine/common/sys/pic.c b/limine/common/sys/pic.c new file mode 100644 index 0000000..0c9dcee --- /dev/null +++ b/limine/common/sys/pic.c @@ -0,0 +1,48 @@ +#if defined (__x86_64__) || defined (__i386__) + +#include +#include +#include +#include +#include +#include + +void pic_eoi(int irq) { + if (irq >= 8) { + outb(0xa0, 0x20); + } + + outb(0x20, 0x20); +} + +// Flush all potentially pending IRQs +void pic_flush(void) { + for (int i = 0; i < 16; i++) + pic_eoi(i); +} + +void pic_set_mask(int line, bool status) { + uint16_t port; + uint8_t value; + + if (line < 8) { + port = 0x21; + } else { + port = 0xa1; + line -= 8; + } + + if (!status) + value = inb(port) & ~((uint8_t)1 << line); + else + value = inb(port) | ((uint8_t)1 << line); + + outb(port, value); +} + +void pic_mask_all(void) { + outb(0xa1, 0xff); + outb(0x21, 0xff); +} + +#endif diff --git a/limine/common/sys/pic.h b/limine/common/sys/pic.h new file mode 100644 index 0000000..0340724 --- /dev/null +++ b/limine/common/sys/pic.h @@ -0,0 +1,11 @@ +#ifndef SYS__PIC_H__ +#define SYS__PIC_H__ + +#include + +void pic_eoi(int irq); +void pic_flush(void); +void pic_set_mask(int line, bool status); +void pic_mask_all(void); + +#endif diff --git a/limine/common/sys/sbi.asm_riscv64 b/limine/common/sys/sbi.asm_riscv64 new file mode 100644 index 0000000..a601656 --- /dev/null +++ b/limine/common/sys/sbi.asm_riscv64 @@ -0,0 +1,19 @@ +.section .text + +.global sbicall +sbicall: +.option norelax + mv t0, a0 + mv t1, a1 + mv a0, a2 + mv a1, a3 + mv a2, a4 + mv a3, a5 + mv a4, a6 + mv a5, a7 + mv a7, t0 + mv a6, t1 + ecall + ret + +.section .note.GNU-stack,"",%progbits diff --git a/limine/common/sys/sbi.h b/limine/common/sys/sbi.h new file mode 100644 index 0000000..ef1fea2 --- /dev/null +++ b/limine/common/sys/sbi.h @@ -0,0 +1,32 @@ +#ifndef SYS__SBI_H__ +#define SYS__SBI_H__ + +#include +#include + +struct sbiret { + long error; + long value; +}; + +#define SBI_SUCCESS ((long)0) +#define SBI_ERR_FAILED ((long)-1) +#define SBI_ERR_NOT_SUPPORTED ((long)-2) +#define SBI_ERR_INVALID_PARAM ((long)-3) +#define SBI_ERR_DENIED ((long)-4) +#define SBI_ERR_INVALID_ADDRESS ((long)-5) +#define SBI_ERR_ALREADY_AVAILABLE ((long)-6) +#define SBI_ERR_ALREADY_STARTED ((long)-7) +#define SBI_ERR_ALREADY_STOPPED ((long)-8) + +extern struct sbiret sbicall(int eid, int fid, ...); + +#define SBI_EID_HSM 0x48534d + +static inline struct sbiret sbi_hart_start(unsigned long hartid, + unsigned long start_addr, + unsigned long opaque) { + return sbicall(SBI_EID_HSM, 0, hartid, start_addr, opaque); +} + +#endif diff --git a/limine/common/sys/smp.c b/limine/common/sys/smp.c new file mode 100644 index 0000000..b72a7e6 --- /dev/null +++ b/limine/common/sys/smp.c @@ -0,0 +1,881 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define LIMINE_NO_POINTERS +#include +#if defined (__riscv) +#include +#endif +#if defined (__aarch64__) +#include +#endif + +extern symbol smp_trampoline_start; +extern size_t smp_trampoline_size; + +#if defined (__x86_64__) || defined (__i386__) + +struct trampoline_passed_info { + uint8_t smp_tpl_booted_flag; + uint8_t smp_tpl_target_mode; + uint32_t smp_tpl_pagemap; + uint32_t smp_tpl_info_struct; + struct gdtr smp_tpl_gdt; + uint64_t smp_tpl_hhdm; + uint64_t smp_tpl_bsp_apic_addr_msr; + uint64_t smp_tpl_mtrr_restore; + uint64_t smp_tpl_temp_stack; + uint64_t smp_tpl_lapic_setup; +} __attribute__((packed)); + +bool smp_configure_apic = false; + +static bool smp_start_ap(uint32_t lapic_id, struct gdtr *gdtr, + struct limine_mp_info *info_struct, + int paging_mode, uint32_t pagemap, + bool x2apic, bool nx, uint64_t hhdm, bool wp) { + // Prepare the trampoline + static void *trampoline = NULL; + if (trampoline == NULL) { + trampoline = conv_mem_alloc(smp_trampoline_size); + + memcpy(trampoline, smp_trampoline_start, smp_trampoline_size); + } + + static void *temp_stack = NULL; + if (temp_stack == NULL) { + temp_stack = ext_mem_alloc(8192); + } + + static struct trampoline_passed_info *passed_info = NULL; + if (passed_info == NULL) { + passed_info = (void *)(((uintptr_t)trampoline + smp_trampoline_size) + - sizeof(struct trampoline_passed_info)); + } + + passed_info->smp_tpl_info_struct = (uint32_t)(uintptr_t)info_struct; + passed_info->smp_tpl_booted_flag = 0; + passed_info->smp_tpl_pagemap = pagemap; + passed_info->smp_tpl_target_mode = ((uint32_t)(paging_mode == PAGING_MODE_X86_64_5LVL) << 1) + | ((uint32_t)nx << 3) + | ((uint32_t)wp << 4); + passed_info->smp_tpl_gdt = *gdtr; + passed_info->smp_tpl_hhdm = hhdm; + passed_info->smp_tpl_bsp_apic_addr_msr = rdmsr(0x1b); + passed_info->smp_tpl_mtrr_restore = (uint64_t)(uintptr_t)mtrr_restore; + passed_info->smp_tpl_temp_stack = (uint64_t)(uintptr_t)temp_stack + 8192; + passed_info->smp_tpl_lapic_setup = smp_configure_apic + ? (uint64_t)(uintptr_t)lapic_configure_handoff_state : 0; + + asm volatile ("" ::: "memory"); + + // Send the INIT IPI + if (x2apic) { + x2apic_write(LAPIC_REG_ICR0, ((uint64_t)lapic_id << 32) | 0x4500); + } else { + lapic_icr_wait(); + lapic_write(LAPIC_REG_ICR1, lapic_id << 24); + lapic_write(LAPIC_REG_ICR0, 0x4500); + } + stall(10000); + + // Send two Startup IPIs per Intel SDM recommendation (Vol 3, 8.4.4.1) + for (int j = 0; j < 2; j++) { + if (x2apic) { + x2apic_write(LAPIC_REG_ICR0, ((uint64_t)lapic_id << 32) | + ((size_t)trampoline / 4096) | 0x4600); + } else { + lapic_icr_wait(); + lapic_write(LAPIC_REG_ICR1, lapic_id << 24); + lapic_write(LAPIC_REG_ICR0, ((size_t)trampoline / 4096) | 0x4600); + } + if (j == 0) { + stall(200); + } + } + + if (!x2apic) { + lapic_icr_wait(); + } + + for (int i = 0; i < 100; i++) { + if (locked_read(&passed_info->smp_tpl_booted_flag) == 1) { + return true; + } + stall(10000); + } + + return false; +} + +struct limine_mp_info *init_smp(size_t *cpu_count, + uint32_t *_bsp_lapic_id, + int paging_mode, + pagemap_t pagemap, + bool x2apic, + bool nx, + uint64_t hhdm, + bool wp) { + if (!lapic_check()) + return NULL; + + // Search for MADT table + struct madt *madt = acpi_get_table("APIC", 0); + + if (madt == NULL) + return NULL; + + struct gdtr gdtr = gdt; + + uint32_t bsp_lapic_id; + + // If x2APIC already enabled by firmware, try to revert to xAPIC + if (rdmsr(0x1b) & (1 << 10)) { + if (!x2apic) { + if (!x2apic_disable()) { + panic(false, "smp: Kernel does not support x2APIC and x2APIC cannot be disabled"); + } + printv("smp: Firmware had x2APIC enabled, reverted to xAPIC mode\n"); + } + } + + x2apic = x2apic && x2apic_enable(); + + if (x2apic) { + bsp_lapic_id = x2apic_read(LAPIC_REG_ID); + } else { + bsp_lapic_id = lapic_read(LAPIC_REG_ID) >> 24; + } + + *_bsp_lapic_id = bsp_lapic_id; + + *cpu_count = 0; + + // Count the MAX of startable APs and allocate accordingly + size_t max_cpus = 0; + + for (uint8_t *madt_ptr = (uint8_t *)madt->madt_entries_begin; + (uintptr_t)madt_ptr + 1 < (uintptr_t)madt + madt->header.length; + madt_ptr += *(madt_ptr + 1)) { + // Prevent infinite loop on zero-length MADT entry + if (*(madt_ptr + 1) == 0) { + break; + } + switch (*madt_ptr) { + case 0: { + // Processor local xAPIC + if (*(madt_ptr + 1) < sizeof(struct madt_lapic)) + continue; + + struct madt_lapic *lapic = (void *)madt_ptr; + + // Check if we can actually try to start the AP + if ((lapic->flags & 1) ^ ((lapic->flags >> 1) & 1)) + max_cpus++; + + continue; + } + case 9: { + // Processor local x2APIC + if (!x2apic) + continue; + + if (*(madt_ptr + 1) < sizeof(struct madt_x2apic)) + continue; + + struct madt_x2apic *x2lapic = (void *)madt_ptr; + + // Check if we can actually try to start the AP + if ((x2lapic->flags & 1) ^ ((x2lapic->flags >> 1) & 1)) + max_cpus++; + + continue; + } + } + } + + if (max_cpus == 0) { + return NULL; + } + + struct limine_mp_info *ret = ext_mem_alloc(max_cpus * sizeof(struct limine_mp_info)); + *cpu_count = 0; + + // Try to start all APs + mtrr_save(); + + for (uint8_t *madt_ptr = (uint8_t *)madt->madt_entries_begin; + (uintptr_t)madt_ptr + 1 < (uintptr_t)madt + madt->header.length; + madt_ptr += *(madt_ptr + 1)) { + // Prevent infinite loop on zero-length MADT entry + if (*(madt_ptr + 1) == 0) { + break; + } + switch (*madt_ptr) { + case 0: { + // Processor local xAPIC + if (*(madt_ptr + 1) < sizeof(struct madt_lapic)) + continue; + + struct madt_lapic *lapic = (void *)madt_ptr; + + // Check if we can actually try to start the AP + if (!((lapic->flags & 1) ^ ((lapic->flags >> 1) & 1))) + continue; + + struct limine_mp_info *info_struct = &ret[*cpu_count]; + + info_struct->processor_id = lapic->acpi_processor_uid; + info_struct->lapic_id = lapic->lapic_id; + + // Do not try to restart the BSP + if (lapic->lapic_id == bsp_lapic_id) { + (*cpu_count)++; + continue; + } + + printv("smp: [xAPIC] Found candidate AP for bring-up. LAPIC ID: %u\n", lapic->lapic_id); + + // Set up per-AP LINT values before starting + if (smp_configure_apic) { + lapic_prep_lint(madt, lapic->acpi_processor_uid, x2apic); + } + + // Try to start the AP + if (!smp_start_ap(lapic->lapic_id, &gdtr, info_struct, + paging_mode, (uintptr_t)pagemap.top_level, + x2apic, nx, hhdm, wp)) { + print("smp: FAILED to bring-up AP\n"); + continue; + } + + printv("smp: Successfully brought up AP\n"); + + (*cpu_count)++; + continue; + } + case 9: { + // Processor local x2APIC + if (!x2apic) + continue; + + if (*(madt_ptr + 1) < sizeof(struct madt_x2apic)) + continue; + + struct madt_x2apic *x2lapic = (void *)madt_ptr; + + // Check if we can actually try to start the AP + if (!((x2lapic->flags & 1) ^ ((x2lapic->flags >> 1) & 1))) + continue; + + struct limine_mp_info *info_struct = &ret[*cpu_count]; + + info_struct->processor_id = x2lapic->acpi_processor_uid; + info_struct->lapic_id = x2lapic->x2apic_id; + + // Do not try to restart the BSP + if (x2lapic->x2apic_id == bsp_lapic_id) { + (*cpu_count)++; + continue; + } + + printv("smp: [x2APIC] Found candidate AP for bring-up. LAPIC ID: %u\n", x2lapic->x2apic_id); + + // Set up per-AP LINT values before starting + if (smp_configure_apic) { + lapic_prep_lint(madt, x2lapic->acpi_processor_uid, true); + } + + // Try to start the AP + if (!smp_start_ap(x2lapic->x2apic_id, &gdtr, info_struct, + paging_mode, (uintptr_t)pagemap.top_level, + true, nx, hhdm, wp)) { + print("smp: FAILED to bring-up AP\n"); + continue; + } + + printv("smp: Successfully brought up AP\n"); + + (*cpu_count)++; + continue; + } + } + } + + if (*cpu_count == 0) { + pmm_free(ret, max_cpus * sizeof(struct limine_mp_info)); + return NULL; + } + + return ret; +} + +#elif defined (__aarch64__) + +struct trampoline_passed_info { + uint64_t smp_tpl_booted_flag; + + uint64_t smp_tpl_hhdm_offset; + + uint64_t smp_tpl_ttbr0; + uint64_t smp_tpl_ttbr1; + + uint64_t smp_tpl_mair; + uint64_t smp_tpl_tcr; + uint64_t smp_tpl_sctlr; + + uint64_t smp_tpl_info_struct; +}; + +enum { + BOOT_WITH_SPIN_TBL, + BOOT_WITH_PSCI_SMC, + BOOT_WITH_PSCI_HVC, + BOOT_WITH_ACPI_PARK +}; + +static uint32_t psci_cpu_on = 0xC4000003; + +static bool try_start_ap(int boot_method, uint64_t method_ptr, + struct limine_mp_info *info_struct, + uint64_t ttbr0, uint64_t ttbr1, uint64_t mair, + uint64_t tcr, uint64_t sctlr, + uint64_t hhdm_offset) { + // Prepare the trampoline + static void *trampoline = NULL; + if (trampoline == NULL) { + trampoline = ext_mem_alloc(smp_trampoline_size); + + memcpy(trampoline, smp_trampoline_start, smp_trampoline_size); + } + + static struct trampoline_passed_info *passed_info = NULL; + if (passed_info == NULL) { + passed_info = (void *)(((uintptr_t)trampoline + 0x1000) + - sizeof(struct trampoline_passed_info)); + } + + passed_info->smp_tpl_info_struct = (uint64_t)(uintptr_t)info_struct; + passed_info->smp_tpl_booted_flag = 0; + passed_info->smp_tpl_ttbr0 = ttbr0; + passed_info->smp_tpl_ttbr1 = ttbr1; + passed_info->smp_tpl_mair = mair; + passed_info->smp_tpl_tcr = tcr; + passed_info->smp_tpl_sctlr = sctlr; + passed_info->smp_tpl_hhdm_offset = hhdm_offset; + + // Cache coherency between the I-Cache and D-Cache is not guaranteed by the + // architecture and as such we must perform I-Cache invalidation. + // Additionally, the newly-booted AP may have caches disabled which implies + // it possibly does not see our cache contents either. + + clean_dcache_poc((uintptr_t)trampoline, (uintptr_t)trampoline + smp_trampoline_size); + inval_icache_pou((uintptr_t)trampoline, (uintptr_t)trampoline + smp_trampoline_size); + + asm volatile ("" ::: "memory"); + + switch (boot_method) { + case BOOT_WITH_SPIN_TBL: + *(volatile uint64_t *)method_ptr = (uint64_t)(uintptr_t)trampoline; + clean_dcache_poc(method_ptr, method_ptr + 8); + asm ("sev"); + break; + + case BOOT_WITH_PSCI_SMC: + case BOOT_WITH_PSCI_HVC: { + register int32_t result asm("w0"); + register uint32_t cmd asm("w0") = psci_cpu_on; + register uint64_t cpu asm("x1") = info_struct->mpidr; + register uint64_t addr asm("x2") = (uint64_t)(uintptr_t)trampoline; + register uint64_t ctx asm("x3") = 0; + + if (boot_method == BOOT_WITH_PSCI_SMC) + asm volatile ("smc #0" : "=r"(result) : "r"(cmd), "r"(cpu), "r"(addr), "r"(ctx)); + else + asm volatile ("hvc #0" : "=r"(result) : "r"(cmd), "r"(cpu), "r"(addr), "r"(ctx)); + + switch (result) { + case 0: // Success + break; + case -2: + printv("smp: PSCI says CPU_ON was given invalid arguments\n"); + return false; + case -4: + printv("smp: PSCI says AP is already on\n"); + return false; + case -5: + printv("smp: PSCI says CPU_ON is already pending for this AP\n"); + return false; + case -6: + printv("smp: PSCI reports internal failure\n"); + return false; + case -9: + printv("smp: PSCI says CPU_ON was given an invalid address\n"); + return false; + default: + printv("smp: PSCI reports an unexpected error (%d)\n", result); + return false; + } + + break; + } + + case BOOT_WITH_ACPI_PARK: + panic(false, "ACPI parking protocol is unsupported, please report this!"); + break; + + default: + panic(false, "Invalid boot method specified"); + } + + for (int i = 0; i < 1000000; i++) { + // We do not need cache invalidation here as by the time the AP gets to + // set this flag, it has enabled its caches + + if (locked_read(&passed_info->smp_tpl_booted_flag) == 1) { + return true; + } + stall(100); + } + + return false; +} + +static struct limine_mp_info *try_acpi_smp(size_t *cpu_count, + uint64_t *_bsp_mpidr, + pagemap_t pagemap, + uint64_t mair, + uint64_t tcr, + uint64_t sctlr, + uint64_t hhdm_offset) { + int boot_method = BOOT_WITH_ACPI_PARK; + + // Search for FADT table + uint8_t *fadt = acpi_get_table("FACP", 0); + + if (fadt == NULL) + return NULL; + + // Check FADT length before accessing ARM boot flags at offset 129 + uint32_t fadt_length; + memcpy(&fadt_length, fadt + 4, 4); + if (fadt_length >= 131) { + // Read the single field from the FADT without defining a struct for the whole table + uint16_t arm_boot_args; + memcpy(&arm_boot_args, fadt + 129, 2); + + if (arm_boot_args & 1) // PSCI compliant? + boot_method = arm_boot_args & 2 ? BOOT_WITH_PSCI_HVC : BOOT_WITH_PSCI_SMC; + } + + // Search for MADT table + struct madt *madt = acpi_get_table("APIC", 0); + + if (madt == NULL) + return NULL; + + uint64_t bsp_mpidr; + asm volatile ("mrs %0, mpidr_el1" : "=r"(bsp_mpidr)); + + // This bit is Res1 in the system reg, but not included in the MPIDR from MADT + bsp_mpidr &= ~((uint64_t)1 << 31); + + *_bsp_mpidr = bsp_mpidr; + + printv("smp: BSP MPIDR is %X\n", bsp_mpidr); + + *cpu_count = 0; + + // Count the MAX of startable APs and allocate accordingly + size_t max_cpus = 0; + + for (uint8_t *madt_ptr = (uint8_t *)madt->madt_entries_begin; + (uintptr_t)madt_ptr + 1 < (uintptr_t)madt + madt->header.length; + madt_ptr += *(madt_ptr + 1)) { + if (*(madt_ptr + 1) == 0) { + break; + } + switch (*madt_ptr) { + case 11: { + // GIC CPU Interface + if (*(madt_ptr + 1) < sizeof(struct madt_gicc)) + continue; + + struct madt_gicc *gicc = (void *)madt_ptr; + + // Check if we can actually try to start the AP + if (gicc->flags & 1) + max_cpus++; + + continue; + } + } + } + + struct limine_mp_info *ret = ext_mem_alloc(max_cpus * sizeof(struct limine_mp_info)); + *cpu_count = 0; + + // Try to start all APs + for (uint8_t *madt_ptr = (uint8_t *)madt->madt_entries_begin; + (uintptr_t)madt_ptr + 1 < (uintptr_t)madt + madt->header.length; + madt_ptr += *(madt_ptr + 1)) { + if (*(madt_ptr + 1) == 0) { + break; + } + switch (*madt_ptr) { + case 11: { + // GIC CPU Interface + if (*(madt_ptr + 1) < sizeof(struct madt_gicc)) + continue; + + struct madt_gicc *gicc = (void *)madt_ptr; + + // Check if we can actually try to start the AP + if (!(gicc->flags & 1)) + continue; + + struct limine_mp_info *info_struct = &ret[*cpu_count]; + + info_struct->processor_id = gicc->acpi_uid; + info_struct->mpidr = gicc->mpidr; + + // Do not try to restart the BSP + if (gicc->mpidr == bsp_mpidr) { + (*cpu_count)++; + continue; + } + + printv("smp: Found candidate AP for bring-up. Interface no.: %x, MPIDR: %X\n", gicc->iface_no, gicc->mpidr); + + // Try to start the AP + if (!try_start_ap(boot_method, gicc->parking_addr, info_struct, + (uint64_t)(uintptr_t)pagemap.top_level[0], + (uint64_t)(uintptr_t)pagemap.top_level[1], + mair, tcr, sctlr, hhdm_offset)) { + print("smp: FAILED to bring-up AP\n"); + continue; + } + + printv("smp: Successfully brought up AP\n"); + + (*cpu_count)++; + continue; + } + } + } + + if (*cpu_count == 0) { + pmm_free(ret, max_cpus * sizeof(struct limine_mp_info)); + return NULL; + } + + return ret; +} + +static struct limine_mp_info *try_dtb_smp( void *dtb, + size_t *cpu_count, + uint64_t *_bsp_mpidr, + pagemap_t pagemap, + uint64_t mair, + uint64_t tcr, + uint64_t sctlr, + uint64_t hhdm_offset) { + uint64_t bsp_mpidr; + asm volatile ("mrs %0, mpidr_el1" : "=r"(bsp_mpidr)); + + // This bit is Res1 in the system reg, but not included in the MPIDR from DT + bsp_mpidr &= ~((uint64_t)1 << 31); + + *_bsp_mpidr = bsp_mpidr; + + printv("smp: BSP MPIDR is %X\n", bsp_mpidr); + + *cpu_count = 0; + + int cpus = fdt_path_offset(dtb, "/cpus"); + if (cpus < 0) { + printv("smp: failed to find /cpus node: %s\n", fdt_strerror(cpus)); + return NULL; + } + + int psci = fdt_path_offset(dtb, "/psci"); + + if (psci > 0 && !fdt_node_check_compatible(dtb, psci, "arm,psci")) { + int prop_len; + const void *prop; + if (!(prop = fdt_getprop(dtb, psci, "cpu_on", &prop_len)) || prop_len < 4) { + printv("smp: failed to find PSCI cpu_on prop\n"); + return NULL; + } + + const uint8_t *bytes = prop; + + psci_cpu_on = ((uint64_t)bytes[0] << 24) + | ((uint64_t)bytes[1] << 16) + | ((uint64_t)bytes[2] << 8) + | ((uint64_t)bytes[3]); + } + + int address_cells = fdt_address_cells(dtb, cpus); + if (address_cells < 1) { + printv("smp: fdt_address_cells failed: %s\n", fdt_strerror(address_cells)); + return NULL; + } + if (address_cells > 2) { + printv("smp: illegal #address-cells value: %d\n", address_cells); + return NULL; + } + + uint64_t max_cpus = 0; + int node; + fdt_for_each_subnode(node, dtb, cpus) { + const void *prop; + + if (!(prop = fdt_getprop(dtb, node, "device_type", NULL)) || strcmp(prop, "cpu")) { + continue; + } + + if (!(prop = fdt_getprop(dtb, node, "reg", NULL))) { + continue; + } + + max_cpus++; + } + + struct limine_mp_info *ret = ext_mem_alloc(max_cpus * sizeof(struct limine_mp_info)); + + fdt_for_each_subnode(node, dtb, cpus) { + const void *prop; + int prop_len; + + if (!(prop = fdt_getprop(dtb, node, "device_type", NULL)) || strcmp(prop, "cpu")) { + continue; + } + + if (!(prop = fdt_getprop(dtb, node, "reg", &prop_len)) || prop_len < address_cells * 4) { + continue; + } + + uint64_t mpidr = 0; + + if (address_cells == 1) { + const uint8_t *bytes = prop; + + mpidr = ((uint64_t)bytes[0] << 24) + | ((uint64_t)bytes[1] << 16) + | ((uint64_t)bytes[2] << 8) + | ((uint64_t)bytes[3]); + } else if (address_cells == 2) { + const uint8_t *bytes = prop; + + mpidr = ((uint64_t)bytes[3] << 32) + | ((uint64_t)bytes[4] << 24) + | ((uint64_t)bytes[5] << 16) + | ((uint64_t)bytes[6] << 8) + | ((uint64_t)bytes[7]); + } + + + struct limine_mp_info *info_struct = &ret[*cpu_count]; + + info_struct->processor_id = 0; + info_struct->mpidr = mpidr; + + // Do not try to restart the BSP + if (mpidr == bsp_mpidr) { + (*cpu_count)++; + continue; + } + + if (!(prop = fdt_getprop(dtb, node, "enable-method", NULL))) { + printv("smp: missing enable-method\n"); + continue; + } + + int boot_method = -1; + uint64_t method_ptr = 0; + + if (!strcmp(prop, "psci")) { + if (psci < 0) { + printv("smp: failed to find /psci: %s\n", fdt_strerror(psci)); + continue; + } + + const void *psci_method = fdt_getprop(dtb, psci, "method", NULL); + + if (psci_method == NULL) { + printv("smp: PSCI method property not found\n"); + continue; + } else if (!strcmp(psci_method, "smc")) { + boot_method = BOOT_WITH_PSCI_SMC; + } else if (!strcmp(psci_method, "hvc")) { + boot_method = BOOT_WITH_PSCI_HVC; + } else { + printv("smp: illegal PSCI method: '%s'\n", psci_method); + continue; + } + + } else if (!strcmp(prop, "spin-table")) { + boot_method = BOOT_WITH_SPIN_TBL; + + if (!(prop = fdt_getprop(dtb, node, "cpu-release-addr", &prop_len)) || prop_len < 8) { + printv("smp: missing cpu-release-addr\n"); + continue; + } + + const uint8_t *bytes = prop; + + method_ptr = ((uint64_t)bytes[0] << 56) + | ((uint64_t)bytes[1] << 48) + | ((uint64_t)bytes[2] << 40) + | ((uint64_t)bytes[3] << 32) + | ((uint64_t)bytes[4] << 24) + | ((uint64_t)bytes[5] << 16) + | ((uint64_t)bytes[6] << 8) + | ((uint64_t)bytes[7]); + } else { + printv("smp: illegal enable-method: '%s'\n", prop); + continue; + } + + printv("smp: Found candidate AP for bring-up. MPIDR: %X\n", mpidr); + + // Try to start the AP + if (!try_start_ap(boot_method, method_ptr, info_struct, + (uint64_t)(uintptr_t)pagemap.top_level[0], + (uint64_t)(uintptr_t)pagemap.top_level[1], + mair, tcr, sctlr, hhdm_offset)) { + print("smp: FAILED to bring-up AP\n"); + continue; + } + + printv("smp: Successfully brought up AP\n"); + + (*cpu_count)++; + } + + return ret; +} + + +struct limine_mp_info *init_smp(const char *config, + size_t *cpu_count, + uint64_t *bsp_mpidr, + pagemap_t pagemap, + uint64_t mair, + uint64_t tcr, + uint64_t sctlr, + uint64_t hhdm_offset) { + struct limine_mp_info *info = NULL; + + if (acpi_get_rsdp() && (info = try_acpi_smp( + cpu_count, bsp_mpidr, pagemap, + mair, tcr, sctlr, hhdm_offset))) + return info; + + // No RSDP means no ACPI, try device trees in that case. + void *dtb = get_device_tree_blob(config, 0); + if (dtb) { + info = try_dtb_smp(dtb, + cpu_count, bsp_mpidr, pagemap, + mair, tcr, sctlr, hhdm_offset); + pmm_free(dtb, fdt_totalsize(dtb)); + return info; + } + + printv("Failed to figure out how to start APs."); + + return NULL; +} + +#elif defined (__riscv) + +struct trampoline_passed_info { + uint64_t smp_tpl_booted_flag; + uint64_t smp_tpl_satp; + uint64_t smp_tpl_info_struct; + uint64_t smp_tpl_hhdm_offset; +}; + +static bool smp_start_ap(size_t hartid, size_t satp, struct limine_mp_info *info_struct, + uint64_t hhdm_offset) { + static struct trampoline_passed_info passed_info; + + passed_info.smp_tpl_booted_flag = 0; + passed_info.smp_tpl_satp = satp; + passed_info.smp_tpl_info_struct = (uint64_t)info_struct; + passed_info.smp_tpl_hhdm_offset = hhdm_offset; + + asm volatile ("" ::: "memory"); + + struct sbiret ret = sbi_hart_start(hartid, (size_t)smp_trampoline_start, (size_t)&passed_info); + if (ret.error != SBI_SUCCESS) + return false; + + for (int i = 0; i < 1000000; i++) { + if (locked_read(&passed_info.smp_tpl_booted_flag) == 1) + return true; + + stall(100); + } + + return false; +} + +struct limine_mp_info *init_smp(size_t *cpu_count, pagemap_t pagemap, uint64_t hhdm_offset) { + size_t num_cpus = 0; + for (struct riscv_hart *hart = hart_list; hart != NULL; hart = hart->next) { + if (!(hart->flags & RISCV_HART_COPROC)) { + num_cpus += 1; + } + } + + struct limine_mp_info *ret = ext_mem_alloc(num_cpus * sizeof(struct limine_mp_info)); + + *cpu_count = 0; + for (struct riscv_hart *hart = hart_list; hart != NULL; hart = hart->next) { + if (hart->flags & RISCV_HART_COPROC) { + continue; + } + struct limine_mp_info *info_struct = &ret[*cpu_count]; + + info_struct->hartid = hart->hartid; + info_struct->processor_id = hart->acpi_uid; + + // Don't try to start the BSP. + if (hart->hartid == bsp_hartid) { + *cpu_count += 1; + continue; + } + + printv("smp: Found candidate AP for bring-up. Hart ID: %U\n", (uint64_t)hart->hartid); + + // Try to start the AP. + size_t satp = make_satp(pagemap.paging_mode, pagemap.top_level); + if (!smp_start_ap(hart->hartid, satp, info_struct, hhdm_offset)) { + print("smp: FAILED to bring-up AP\n"); + continue; + } + + (*cpu_count)++; + continue; + } + + return ret; +} + +#elif defined (__loongarch64) +#else +#error Unknown architecture +#endif diff --git a/limine/common/sys/smp.h b/limine/common/sys/smp.h new file mode 100644 index 0000000..7cac37c --- /dev/null +++ b/limine/common/sys/smp.h @@ -0,0 +1,46 @@ +#ifndef SYS__SMP_H__ +#define SYS__SMP_H__ + +#include +#include +#include +#include +#define LIMINE_NO_POINTERS +#include + +#if defined (__x86_64__) || defined (__i386__) + +extern bool smp_configure_apic; + +struct limine_mp_info *init_smp(size_t *cpu_count, + uint32_t *_bsp_lapic_id, + int paging_mode, + pagemap_t pagemap, + bool x2apic, + bool nx, + uint64_t hhdm, + bool wp); + +#elif defined (__aarch64__) + +struct limine_mp_info *init_smp(const char *config, + size_t *cpu_count, + uint64_t *bsp_mpidr, + pagemap_t pagemap, + uint64_t mair, + uint64_t tcr, + uint64_t sctlr, + uint64_t hhdm_offset); + +#elif defined (__riscv) + +struct limine_mp_info *init_smp(size_t *cpu_count, + pagemap_t pagemap, + uint64_t hhdm_offset); + +#elif defined (__loongarch64) +#else +#error Unknown architecture +#endif + +#endif diff --git a/limine/common/sys/smp_trampoline.asm_aarch64 b/limine/common/sys/smp_trampoline.asm_aarch64 new file mode 100644 index 0000000..a6a4a24 --- /dev/null +++ b/limine/common/sys/smp_trampoline.asm_aarch64 @@ -0,0 +1,149 @@ +#include + +.set tpl_booted_flag, -64 +.set tpl_hhdm_offset, -56 +.set tpl_ttbr0, -48 +.set tpl_ttbr1, -40 +.set tpl_mair, -32 +.set tpl_tcr, -24 +.set tpl_sctlr, -16 +.set tpl_info_struct, -8 + +.section .text + +.global smp_trampoline_start +smp_trampoline_start: + bl .L_entry +.L_entry: + // Mask IRQs + msr daifset, #0b1111 + + // Address to next page (since our offsets into the boot data are negative) + add x1, x30, #0xFFC + + ldr x0, [x1, tpl_info_struct] + ldr x2, [x1, tpl_sctlr] + ldr x3, [x1, tpl_mair] + ldr x4, [x1, tpl_tcr] + ldr x5, [x1, tpl_ttbr0] + ldr x6, [x1, tpl_ttbr1] + ldr x7, [x1, tpl_hhdm_offset] + + PICK_EL x8, 1f, 0f +0: + // Configure EL2-specific state for EL1 + + // Configure EL1 page tables + msr mair_el1, x3 + msr tcr_el1, x4 + msr ttbr0_el1, x5 + msr ttbr1_el1, x6 + msr sctlr_el1, x2 + isb + dsb sy + isb + + // Don't trap counters to EL2 + mrs x8, cnthctl_el2 + orr x8, x8, #3 + msr cnthctl_el2, x8 + msr cntvoff_el2, xzr + + // Enable AArch64 in EL1 + mov x8, xzr + orr x8, x8, #(1 << 31) + orr x8, x8, #(1 << 1) + msr hcr_el2, x8 + + // Don't trap FP/SIMD to EL2 + mov x8, #0x33FF + msr cptr_el2, x8 + msr hstr_el2, xzr + + // Run rest of trampoline in EL1 + mov x8, #0x3c4 + msr spsr_el2, x8 + adrp x8, 3f + add x8, x8, :lo12:3f + add x8, x8, x7 // Add HHDM offset + msr elr_el2, x8 + + eret + +1: + msr spsel, #0 + + // Switch to the new page tables + + // Point the EL1t handler to the continuation, such that after we page fault, + // execution continues as expected. + adrp x8, 2f + add x8, x8, #:lo12:2f + add x8, x8, x7 + msr vbar_el1, x8 + isb + dsb sy + isb + + // Switch the page table registers + msr mair_el1, x3 + msr tcr_el1, x4 + msr ttbr0_el1, x5 + msr ttbr1_el1, x6 + msr sctlr_el1, x2 + isb + dsb sy + isb + + // Jump to the higher half mapping in case we didn't immediately crash + br x8 + +// Alignment required by VBAR register +.align 12 +2: + // Zero out VBAR to avoid confusion + msr vbar_el1, xzr + +3: + // Add HHDM offset to data pointer + add x1, x1, x7 + + // Notify BSP we are alive + mov x8, #1 + add x9, x1, tpl_booted_flag + stlr x8, [x9] + + // Wait for BSP to tell us where to go + // Add HHDM offset to our info struct pointer + add x0, x0, x7 + add x9, x0, #24 +4: + ldar x8, [x9] + cbnz x8, 5f + yield + b 4b + +5: + msr elr_el1, x8 + + msr spsel, #0 + ldr x8, [x0, #16] + mov sp, x8 + + // Enter kernel + mov x8, #0x3c4 + msr spsr_el1, x8 + + ZERO_REGS_EXCEPT_X0 + + eret + +smp_trampoline_end: + +.section .rodata + +.global smp_trampoline_size +smp_trampoline_size: + .quad smp_trampoline_end - smp_trampoline_start + +.section .note.GNU-stack,"",%progbits diff --git a/limine/common/sys/smp_trampoline.asm_riscv64 b/limine/common/sys/smp_trampoline.asm_riscv64 new file mode 100644 index 0000000..c2749c3 --- /dev/null +++ b/limine/common/sys/smp_trampoline.asm_riscv64 @@ -0,0 +1,84 @@ +.section .text + +.global smp_trampoline_start +smp_trampoline_start: +.option norelax + // The AP begins executing here with the following state: + // satp = 0 + // sstatus.SIE = 0 + // a0 = hartid + // a1 = struct trampoline_passed_info * + // + // All other registers are undefined. + +#define smp_tpl_booted_flag 0 +#define smp_tpl_satp 8 +#define smp_tpl_info_struct 16 +#define smp_tpl_hhdm_offset 24 + + ld a0, smp_tpl_info_struct(a1) + ld t1, smp_tpl_hhdm_offset(a1) + + // Set `stvec` so we page fault into the higher half after loading `satp`. + lla t0, 0f + add t0, t1, t0 + csrw stvec, t0 + ld t0, smp_tpl_satp(a1) + csrw satp, t0 + sfence.vma + unimp +0: + // Relocate the smp_info and passed_info pointers to the higher half. + add a0, t1, a0 + add a1, t1, a1 + + // Tell the BSP we've started. + li t0, 1 + fence rw, w + sd t0, (a1) + + // Zero all the things. + // Preserve a0 + mv a1, zero + mv a2, zero + mv a3, zero + mv a4, zero + mv a5, zero + mv a6, zero + mv a7, zero + mv s0, zero + mv s1, zero + mv s2, zero + mv s3, zero + mv s4, zero + mv s5, zero + mv s6, zero + mv s7, zero + mv s8, zero + mv s9, zero + mv s10, zero + mv s11, zero + mv t1, zero + mv t2, zero + mv t3, zero + mv t4, zero + mv t5, zero + mv t6, zero + mv tp, zero + mv ra, zero + + csrw sie, zero + csrw stvec, zero + + // Wait for kernel to tell us where to go. +0: .4byte 0x0100000f // pause + ld t0, 24(a0) + fence r, rw + beqz t0, 0b + + // Load sp from reserved field of info struct + ld sp, 16(a0) + + jr t0 + +.section .note.GNU-stack,"",%progbits diff --git a/limine/common/sys/smp_trampoline.asm_x86 b/limine/common/sys/smp_trampoline.asm_x86 new file mode 100644 index 0000000..9729634 --- /dev/null +++ b/limine/common/sys/smp_trampoline.asm_x86 @@ -0,0 +1,271 @@ +bits 16 + +section .rodata + +global smp_trampoline_start +smp_trampoline_start: + cli + cld + + mov ebx, cs + shl ebx, 4 + + o32 lidt [cs:(invalid_idt - smp_trampoline_start)] + o32 lgdt [cs:(passed_info.gdtr - smp_trampoline_start)] + + lea eax, [ebx + (.mode32 - smp_trampoline_start)] + mov [cs:(.farjmp_off - smp_trampoline_start)], eax + + mov eax, 0x00000011 + mov cr0, eax + o32 jmp far [cs:(.farjmp - smp_trampoline_start)] + + .farjmp: + .farjmp_off: dd 0 + .farjmp_seg: dd 0x18 + + bits 32 + .mode32: + mov ax, 0x20 + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + mov ss, ax + + xor eax, eax + lldt ax + + xor eax, eax + mov cr4, eax + + mov esi, ebx + mov eax, 1 + xor ecx, ecx + cpuid + test edx, 1 << 16 + jz .no_pat + mov ecx, 0x277 + mov eax, 0x00070406 + mov edx, 0x00000105 + wrmsr + .no_pat: + mov ebx, esi + + mov ecx, 0x1b + rdmsr + test eax, (1 << 10) + jz .write_apic_msr + + ; Check if target also has x2APIC + test dword [ebx + (passed_info.bsp_apic_addr_msr_lo - smp_trampoline_start)], (1 << 10) + jnz .write_apic_msr + + ; AP is x2APIC but target is xAPIC: go through disabled state + btr eax, 11 + btr eax, 10 + wrmsr + + .write_apic_msr: + mov eax, [ebx + (passed_info.bsp_apic_addr_msr_lo - smp_trampoline_start)] + mov edx, [ebx + (passed_info.bsp_apic_addr_msr_hi - smp_trampoline_start)] + bts eax, 11 + btr eax, 8 + wrmsr + mov esp, [ebx + (passed_info.temp_stack - smp_trampoline_start)] + + mov eax, cr4 + bts eax, 5 + mov cr4, eax + + ; Set EFER (LME + NX if available), all other bits cleared + mov ecx, 0xc0000080 + xor edx, edx + mov eax, 1 << 8 + test dword [ebx + (passed_info.target_mode - smp_trampoline_start)], (1 << 3) + jz .no_nx + or eax, 1 << 11 + .no_nx: + wrmsr + + test dword [ebx + (passed_info.target_mode - smp_trampoline_start)], (1 << 1) + jz .no5lv + + mov eax, cr4 + bts eax, 12 + mov cr4, eax + + .no5lv: + mov eax, dword [ebx + (passed_info.pagemap - smp_trampoline_start)] + mov cr3, eax + + ; Enable CR0.PG (and WP if requested) + mov eax, cr0 + test dword [ebx + (passed_info.target_mode - smp_trampoline_start)], (1 << 4) + jz .no_wp + bts eax, 16 + .no_wp: + bts eax, 31 + mov cr0, eax + +%ifdef IA32_TARGET + ; Synchronise MTRRs with BSP + call [ebx + (passed_info.mtrr_restore - smp_trampoline_start)] + + ; Configure local APIC handoff state (if pointer is set) + mov eax, dword [ebx + (passed_info.lapic_setup - smp_trampoline_start)] + test eax, eax + jz .skip_lapic_setup32 + call eax + .skip_lapic_setup32: +%endif + + lea eax, [ebx + (.mode64 - smp_trampoline_start)] + push 0x28 + push eax + retf + + bits 64 + .mode64: + mov ax, 0x30 + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + mov ss, ax + + mov ebx, ebx + mov rax, qword [rbx + (passed_info.hhdm - smp_trampoline_start)] + add qword [rbx + (passed_info.gdtr - smp_trampoline_start) + 2], rax + lgdt [rbx + (passed_info.gdtr - smp_trampoline_start)] + + ; Clear TSS busy bit and load TR + mov rcx, [rbx + (passed_info.gdtr - smp_trampoline_start) + 2] + mov byte [rcx + 0x3d], 0x89 + mov ecx, 0x38 + ltr cx + + lea rax, [rax + rbx + (parking64 - smp_trampoline_start)] + + jmp rax + +bits 64 +parking64: + mov ebx, ebx + +%ifdef X86_64_TARGET + ; Synchronise MTRRs with BSP + call [rbx + (passed_info.mtrr_restore - smp_trampoline_start)] + + ; Configure local APIC handoff state (if pointer is set) + mov rax, [rbx + (passed_info.lapic_setup - smp_trampoline_start)] + test rax, rax + jz .skip_lapic_setup64 + call rax + .skip_lapic_setup64: +%endif + + mov edi, dword [rbx + (passed_info.smp_info_struct - smp_trampoline_start)] + add rdi, qword [rbx + (passed_info.hhdm - smp_trampoline_start)] + + mov eax, 1 + xchg dword [rbx + (passed_info.booted_flag - smp_trampoline_start)], eax + + ; Check for MONITOR/MWAIT support + mov eax, 1 + xor ecx, ecx + cpuid + test ecx, (1 << 3) + jnz .monitor_spin + + .loop: + mov rax, qword [rdi + 16] + test rax, rax + jnz .out + pause + jmp .loop + + .monitor_spin: + mov rax, qword [rdi + 16] + test rax, rax + jnz .out + lea rax, [rdi + 16] + xor ecx, ecx + xor edx, edx + monitor + mov rax, qword [rdi + 16] + test rax, rax + jnz .out + xor eax, eax + xor ecx, ecx + mwait + jmp .monitor_spin + + .out: + ; Clear TLB + mov rbx, cr3 + mov cr3, rbx + + ; Switch to new stack (HHDM address, safe after lower half unmap) + mov rsp, qword [rdi + 8] + + ; Push fake return address + push 0 + mov rsi, rsp + + ; Prepare iretq frame + push 0x30 + push rsi + push 0x2 + push 0x28 + push rax + + ; Zero out all GPRs (except rdi = mp_info pointer) + xor eax, eax + xor ebx, ebx + xor ecx, ecx + xor edx, edx + xor esi, esi + xor ebp, ebp + xor r8d, r8d + xor r9d, r9d + xor r10d, r10d + xor r11d, r11d + xor r12d, r12d + xor r13d, r13d + xor r14d, r14d + xor r15d, r15d + + iretq + +invalid_idt: + times 2 dq 0 + +align 16 +passed_info: + .booted_flag db 0 + .target_mode db 0 + .pagemap dd 0 + .smp_info_struct dd 0 + .gdtr: + dw 0 + dq 0 + .hhdm: + dq 0 + .bsp_apic_addr_msr_lo: + dd 0 + .bsp_apic_addr_msr_hi: + dd 0 + .mtrr_restore: + dq 0 + .temp_stack: + dq 0 + .lapic_setup: + dq 0 + +smp_trampoline_end: + +global smp_trampoline_size +smp_trampoline_size dq smp_trampoline_end - smp_trampoline_start + +section .note.GNU-stack noalloc noexec nowrite progbits diff --git a/limine/config.h.in b/limine/config.h.in new file mode 100644 index 0000000..c93e511 --- /dev/null +++ b/limine/config.h.in @@ -0,0 +1,7 @@ +#ifndef __CONFIG_H__ +#define __CONFIG_H__ + +#define LIMINE_VERSION "@PACKAGE_VERSION@" +#define LIMINE_COPYRIGHT "@LIMINE_COPYRIGHT@" + +#endif diff --git a/limine/configure.ac b/limine/configure.ac new file mode 100644 index 0000000..312a0d1 --- /dev/null +++ b/limine/configure.ac @@ -0,0 +1,348 @@ +AC_INIT([Limine], [m4_esyscmd([./version.sh])], [https://codeberg.org/Limine/Limine/issues], [limine], [https://limine-bootloader.org/]) + +AC_PREREQ([2.69]) + +AC_CONFIG_AUX_DIR([build-aux]) + +SRCDIR="$(cd "$srcdir" && pwd -P)" +BUILDDIR="$(pwd -P)" + +AC_SUBST([SRCDIR]) +AC_SUBST([BUILDDIR]) + +. "$SRCDIR"/timestamps + +AC_SUBST([REGEN_DATE]) +AC_SUBST([SOURCE_DATE_EPOCH]) +AC_SUBST([SOURCE_DATE_EPOCH_TOUCH]) + +AC_CANONICAL_HOST + +# Portably convert relative paths into absolute paths. +rel2abs() { + rel2abs_first=true + for i in $1; do + if test $rel2abs_first = true; then + case "$i" in + /*) + printf "%s" "$i" + ;; + */*) + if test -d "$(dirname "$i")"; then + printf "%s" "$(cd "$(dirname "$i")" && pwd -P)/$(basename "$i")" + else + printf "false" + fi + ;; + *) + printf "%s" "$i" + ;; + esac + rel2abs_first=false + else + printf " %s" "$i" + fi + done + printf "\n" +} + +test "x${CFLAGS+set}" = "x" && CFLAGS='-g -O2 -pipe' + +AC_LANG([C]) +AC_PROG_CC +CC="$(rel2abs "$CC")" + +werror_state="no" +AC_ARG_ENABLE([werror], + [AS_HELP_STRING([--enable-werror], [treat warnings as errors])], + [werror_state="$enableval"]) +if test "$werror_state" = "yes"; then + AC_SUBST([WERROR_FLAG], [-Werror]) +else + AC_SUBST([WERROR_FLAG], [-Wno-error]) +fi + +AC_PROG_MKDIR_P +MKDIR_P="$(rel2abs "$MKDIR_P")" +AC_PROG_INSTALL +INSTALL="$(rel2abs "$INSTALL")" +AC_PROG_SED +SED="$(rel2abs "$SED")" +AC_PROG_GREP +GREP="$(rel2abs "$GREP")" +AC_PROG_AWK +AWK="$(rel2abs "$AWK")" + +AC_CHECK_PROG([FIND_FOUND], [find], [yes]) +if ! test "x$FIND_FOUND" = "xyes"; then + AC_MSG_ERROR([find not found, please install find before configuring]) +fi + +# $1 - UPPERCASEVAR, $2 - default command, $3 - 'tool' if toolchain, $4 - 'no-err' to ignore errors +AC_DEFUN([GET_PROG], [ + if test "x${$1+set}" = "xset"; then + first_elem="$(rel2abs $(echo "$$1" | cut -f 1 -d " "))" + case "$first_elem" in + /*) + AC_MSG_CHECKING([for $first_elem]) + if test -f "$first_elem" && test -x "$first_elem"; then + $1="$(rel2abs "$$1")" + $1_FOUND=yes + else + $1_FOUND=no + fi + AC_MSG_RESULT([$$1_FOUND]) + ;; + *) + AC_CHECK_PROG([$1_FOUND], [$first_elem], [yes]) + ;; + esac + else + if ! test -z "$2"; then + if test "x$3" = "xtool"; then + AC_CHECK_TOOL([$1_FOUND], [$2], [no]) + if ! test "x$$1_FOUND" = "xno"; then + $1="$(rel2abs "$$1_FOUND")" + $1_FOUND=yes + fi + else + first_elem="$(rel2abs $(echo "$2" | cut -f 1 -d " "))" + AC_CHECK_PROG([$1_FOUND], [$first_elem], [yes]) + if test "x$$1_FOUND" = "xyes"; then + $1="$(rel2abs "$2")" + fi + fi + else + $1_FOUND=no + fi + fi + + if ! test "x$$1_FOUND" = "xyes"; then + if test "x$4" = "xno-err"; then + AC_MSG_WARN([$first_elem invalid, set $1 to a valid program]) + else + AC_MSG_ERROR([$first_elem invalid, set $1 to a valid program]) + fi + fi +]) + +AC_ARG_VAR([STRIP], [strip command]) +GET_PROG([STRIP], [strip], [tool]) + +AC_CHECK_HEADERS([stdio.h stdlib.h stdint.h stddef.h stdbool.h stdarg.h string.h errno.h inttypes.h limits.h time.h], + [], [AC_MSG_ERROR([required header not found])]) + +AC_ARG_VAR([TOOLCHAIN_FOR_TARGET], [alternative toolchain prefix for Limine]) + +AC_ARG_VAR([CC_FOR_TARGET], [C compiler command for Limine]) +if test "x${CC_FOR_TARGET+set}" = "x"; then + if test "x${TOOLCHAIN_FOR_TARGET+set}" = "x"; then + CC_FOR_TARGET="clang" + else + CC_FOR_TARGET="${TOOLCHAIN_FOR_TARGET}gcc" + fi +fi +AC_ARG_VAR([LD_FOR_TARGET], [linker command for Limine]) +if test "x${LD_FOR_TARGET+set}" = "x"; then + if test "x${TOOLCHAIN_FOR_TARGET+set}" = "x"; then + LD_FOR_TARGET="ld.lld" + else + LD_FOR_TARGET="${TOOLCHAIN_FOR_TARGET}ld" + fi +fi + +test "x${TOOLCHAIN_FOR_TARGET+set}" = "x" && TOOLCHAIN_FOR_TARGET=llvm- + +AC_ARG_VAR([OBJCOPY_FOR_TARGET], [objcopy command for Limine]) +test "x${OBJCOPY_FOR_TARGET+set}" = "x" && OBJCOPY_FOR_TARGET="${TOOLCHAIN_FOR_TARGET}objcopy" +AC_ARG_VAR([OBJDUMP_FOR_TARGET], [objdump command for Limine]) +test "x${OBJDUMP_FOR_TARGET+set}" = "x" && OBJDUMP_FOR_TARGET="${TOOLCHAIN_FOR_TARGET}objdump" +AC_ARG_VAR([READELF_FOR_TARGET], [readelf command for Limine]) +test "x${READELF_FOR_TARGET+set}" = "x" && READELF_FOR_TARGET="${TOOLCHAIN_FOR_TARGET}readelf" + +GET_PROG([CC_FOR_TARGET]) +GET_PROG([LD_FOR_TARGET]) +GET_PROG([OBJCOPY_FOR_TARGET]) +GET_PROG([OBJDUMP_FOR_TARGET]) +GET_PROG([READELF_FOR_TARGET]) + +BUILD_ALL="no" + +AC_ARG_ENABLE([all], + [AS_HELP_STRING([--enable-all], [enable ALL ports and targets])], + [BUILD_ALL="$enableval"]) + +BUILD_BIOS_CD="$BUILD_ALL" + +AC_ARG_ENABLE([bios-cd], + [AS_HELP_STRING([--enable-bios-cd], [enable building the x86 BIOS CD image])], + [BUILD_BIOS_CD="$enableval"]) + +AC_SUBST([BUILD_BIOS_CD]) + +BUILD_BIOS_PXE="$BUILD_ALL" + +AC_ARG_ENABLE([bios-pxe], + [AS_HELP_STRING([--enable-bios-pxe], [enable building the x86 BIOS PXE image])], + [BUILD_BIOS_PXE="$enableval"]) + +AC_SUBST([BUILD_BIOS_PXE]) + +BUILD_BIOS="$BUILD_ALL" + +AC_ARG_ENABLE([bios], + [AS_HELP_STRING([--enable-bios], [enable building the x86 BIOS port])], + [BUILD_BIOS="$enableval"]) + +if test "x$BUILD_BIOS" = "xno"; then + if test "x$BUILD_BIOS_CD" = "xyes"; then + BUILD_BIOS="yes" + fi + if test "x$BUILD_BIOS_PXE" = "xyes"; then + BUILD_BIOS="yes" + fi +fi + +if test "x$BUILD_BIOS" = "xno"; then + BUILD_BIOS="" +else + BUILD_BIOS="limine-bios" + NEED_NASM=yes + NEED_GZIP=yes +fi + +AC_SUBST([BUILD_BIOS]) + +BUILD_UEFI_IA32="$BUILD_ALL" + +AC_ARG_ENABLE([uefi-ia32], + [AS_HELP_STRING([--enable-uefi-ia32], [enable building the IA-32 UEFI port])], + [BUILD_UEFI_IA32="$enableval"]) + +if test "x$BUILD_UEFI_IA32" = "xno"; then + BUILD_UEFI_IA32="" +else + BUILD_UEFI_IA32="limine-uefi-ia32" + NEED_NASM=yes +fi + +AC_SUBST([BUILD_UEFI_IA32]) + +BUILD_UEFI_X86_64="$BUILD_ALL" + +AC_ARG_ENABLE([uefi-x86-64], + [AS_HELP_STRING([--enable-uefi-x86-64], [enable building the x86-64 UEFI port])], + [BUILD_UEFI_X86_64="$enableval"]) + +if test "x$BUILD_UEFI_X86_64" = "xno"; then + BUILD_UEFI_X86_64="" +else + BUILD_UEFI_X86_64="limine-uefi-x86-64" + NEED_NASM=yes +fi + +AC_SUBST([BUILD_UEFI_X86_64]) + +BUILD_UEFI_AARCH64="$BUILD_ALL" + +AC_ARG_ENABLE([uefi-aarch64], + [AS_HELP_STRING([--enable-uefi-aarch64], [enable building the aarch64 UEFI port])], + [BUILD_UEFI_AARCH64="$enableval"]) + +if test "x$BUILD_UEFI_AARCH64" = "xno"; then + BUILD_UEFI_AARCH64="" +else + BUILD_UEFI_AARCH64="limine-uefi-aarch64" +fi + +AC_SUBST([BUILD_UEFI_AARCH64]) + +BUILD_UEFI_RISCV64="$BUILD_ALL" + +AC_ARG_ENABLE([uefi-riscv64], + [AS_HELP_STRING([--enable-uefi-riscv64], [enable building the riscv64 UEFI port])], + [BUILD_UEFI_RISCV64="$enableval"]) + +if test "x$BUILD_UEFI_RISCV64" = "xno"; then + BUILD_UEFI_RISCV64="" +else + BUILD_UEFI_RISCV64="limine-uefi-riscv64" +fi + +AC_SUBST([BUILD_UEFI_RISCV64]) + +BUILD_UEFI_LOONGARCH64="$BUILD_ALL" + +AC_ARG_ENABLE([uefi-loongarch64], + [AS_HELP_STRING([--enable-uefi-loongarch64], [enable building the loongarch64 UEFI port])], + [BUILD_UEFI_LOONGARCH64="$enableval"]) + +if test "x$BUILD_UEFI_LOONGARCH64" = "xno"; then + BUILD_UEFI_LOONGARCH64="" +else + BUILD_UEFI_LOONGARCH64="limine-uefi-loongarch64" +fi + +AC_SUBST([BUILD_UEFI_LOONGARCH64]) + +BUILD_UEFI_CD="$BUILD_ALL" + +AC_ARG_ENABLE([uefi-cd], + [AS_HELP_STRING([--enable-uefi-cd], [enable building limine-uefi-cd.bin])], + [BUILD_UEFI_CD="$enableval"]) + +if ! test "x$BUILD_UEFI_CD" = "xno"; then + AC_CHECK_PROG([MTOOLS_FOUND], [mcopy], [yes]) + if ! test "x$MTOOLS_FOUND" = "xyes"; then + if test "x$BUILD_UEFI_CD" = "xyes"; then + AC_MSG_ERROR([mtools not found, install mtools to build limine-uefi-cd.bin]) + fi + AC_MSG_WARN([mtools not found, install mtools to build limine-uefi-cd.bin]) + BUILD_UEFI_CD="no" + fi +fi + +AC_SUBST([BUILD_UEFI_CD]) + +if test "x$NEED_NASM" = "xyes"; then + AC_CHECK_PROG([NASM_FOUND], [nasm], [yes]) + if ! test "x$NASM_FOUND" = "xyes"; then + AC_MSG_ERROR([nasm not found, please install nasm before configuring]) + fi +fi + +if test "x$NEED_GZIP" = "xyes"; then + AC_CHECK_PROG([GZIP_FOUND], [gzip], [yes]) + if ! test "x$GZIP_FOUND" = "xyes"; then + AC_MSG_ERROR([gzip not found, please install gzip before configuring]) + fi +fi + +BORROWED_CFLAGS="" +for cflag in $CFLAGS; do + case $cflag in + -O*|-pipe|-g|-f*-prefix-map*) + BORROWED_CFLAGS="$BORROWED_CFLAGS $cflag" + ;; + esac +done + +AC_ARG_VAR([CFLAGS_FOR_TARGET], [C flags for Limine]) +test "x${CFLAGS_FOR_TARGET+set}" = "x" && CFLAGS_FOR_TARGET="$BORROWED_CFLAGS" + +AC_ARG_VAR([CPPFLAGS_FOR_TARGET], [C preprocessor flags for Limine]) +test "x${CPPFLAGS_FOR_TARGET+set}" = "x" && CPPFLAGS_FOR_TARGET="" + +AC_ARG_VAR([LDFLAGS_FOR_TARGET], [linker flags for Limine]) +test "x${LDFLAGS_FOR_TARGET+set}" = "x" && LDFLAGS_FOR_TARGET="" + +AC_ARG_VAR([NASMFLAGS_FOR_TARGET], [nasm flags for Limine]) +test "x${NASMFLAGS_FOR_TARGET+set}" = "x" && NASMFLAGS_FOR_TARGET="-g" + +LIMINE_COPYRIGHT=$($GREP Copyright "$SRCDIR/COPYING") +AC_SUBST([LIMINE_COPYRIGHT]) + +AC_PREFIX_DEFAULT([/usr/local]) + +AC_CONFIG_FILES([man/man1/limine.1 GNUmakefile config.h]) +AC_OUTPUT diff --git a/limine/decompressor/decompressor.mk b/limine/decompressor/decompressor.mk new file mode 100644 index 0000000..718d07f --- /dev/null +++ b/limine/decompressor/decompressor.mk @@ -0,0 +1,84 @@ +.SUFFIXES: + +override SPACE := $(subst ,, ) + +override MKESCAPE = $(subst $(SPACE),\ ,$(1)) +override SHESCAPE = $(subst ','\'',$(1)) +override OBJESCAPE = $(subst .a ,.a' ',$(subst .o ,.o' ',$(call SHESCAPE,$(1)))) + +override CC_FOR_TARGET_IS_CLANG := $(shell ! $(CC_FOR_TARGET) --version 2>/dev/null | $(GREP) -q '^Target: '; echo $$?) + +ifeq ($(CC_FOR_TARGET_IS_CLANG),1) + override CC_FOR_TARGET += \ + -target i686-unknown-none-elf +endif + +override CFLAGS_FOR_TARGET += \ + -Os \ + -Wall \ + -Wextra \ + -Wshadow \ + -Wvla \ + $(WERROR_FLAG) \ + -std=gnu11 \ + -nostdinc \ + -ffreestanding \ + -ffunction-sections \ + -fdata-sections \ + -fno-stack-protector \ + -fno-stack-check \ + -fomit-frame-pointer \ + -fno-strict-aliasing \ + -fno-lto \ + -fno-PIC \ + -m32 \ + -march=i686 \ + -mabi=sysv \ + -mno-80387 \ + -mno-mmx + +override CPPFLAGS_FOR_TARGET := \ + -I . \ + -I tinf \ + -isystem ../freestnd-c-hdrs/include \ + $(CPPFLAGS_FOR_TARGET) \ + -MMD \ + -MP + +override LDFLAGS_FOR_TARGET += \ + -m elf_i386 \ + -nostdlib \ + -z max-page-size=0x1000 \ + --gc-sections \ + -static \ + -T linker.ld + +override NASMFLAGS_FOR_TARGET := \ + -f elf32 \ + $(patsubst -g,-g -F dwarf,$(NASMFLAGS_FOR_TARGET)) \ + -Wall \ + -w-unknown-warning \ + -w-reloc \ + $(WERROR_FLAG) + +override C_FILES := $(shell find . -type f -name '*.c' | LC_ALL=C sort) +override ASM_FILES := $(shell find . -type f -name '*.asm' | LC_ALL=C sort) +override OBJ := $(addprefix $(call MKESCAPE,$(BUILDDIR))/, $(ASM_FILES:.asm=.o) $(C_FILES:.c=.o)) +override HEADER_DEPS := $(addprefix $(call MKESCAPE,$(BUILDDIR))/, $(C_FILES:.c=.d)) + +.PHONY: all +all: $(call MKESCAPE,$(BUILDDIR))/decompressor.bin + +$(call MKESCAPE,$(BUILDDIR))/decompressor.bin: $(OBJ) + $(LD_FOR_TARGET) $(LDFLAGS_FOR_TARGET) '$(call OBJESCAPE,$^)' -o '$(call SHESCAPE,$(BUILDDIR))/decompressor.elf' + $(OBJCOPY_FOR_TARGET) -O binary '$(call SHESCAPE,$(BUILDDIR))/decompressor.elf' '$(call SHESCAPE,$@)' + +-include $(HEADER_DEPS) + +$(call MKESCAPE,$(BUILDDIR))/%.o: %.c + $(MKDIR_P) "$$(dirname '$(call SHESCAPE,$@)')" + $(CC_FOR_TARGET) $(CFLAGS_FOR_TARGET) $(CPPFLAGS_FOR_TARGET) -c '$(call SHESCAPE,$<)' -o '$(call SHESCAPE,$@)' + +$(call MKESCAPE,$(BUILDDIR))/%.o: %.asm + $(MKDIR_P) "$$(dirname '$(call SHESCAPE,$@)')" + nasm '$(call SHESCAPE,$<)' $(NASMFLAGS_FOR_TARGET) -o '$(call SHESCAPE,$@)' diff --git a/limine/decompressor/entry.asm b/limine/decompressor/entry.asm new file mode 100644 index 0000000..70aa0cf --- /dev/null +++ b/limine/decompressor/entry.asm @@ -0,0 +1,20 @@ +extern bss_begin +extern bss_end +extern entry + +section .entry progbits alloc exec nowrite align=16 + +global _start +_start: + cld + + ; Zero out .bss + xor al, al + mov edi, bss_begin + mov ecx, bss_end + sub ecx, bss_begin + rep stosb + + jmp entry + +section .note.GNU-stack noalloc noexec nowrite progbits diff --git a/limine/decompressor/linker.ld b/limine/decompressor/linker.ld new file mode 100644 index 0000000..c6adcfa --- /dev/null +++ b/limine/decompressor/linker.ld @@ -0,0 +1,39 @@ +OUTPUT_FORMAT(elf32-i386) +ENTRY(_start) + +PHDRS +{ + text PT_LOAD FLAGS(0x05); + rodata PT_LOAD FLAGS(0x04); + data PT_LOAD FLAGS(0x06); +} + +SECTIONS +{ + . = 0x70000; + + .text : { + *(.entry) + *(.text .text.*) + } :text + + .rodata : { + *(.rodata .rodata.*) + } :rodata + + .data : { + *(.data .data.*) + } :data + + .bss : { + bss_begin = .; + *(.bss .bss.*) + *(COMMON) + bss_end = .; + } :data + + /DISCARD/ : { + *(.eh_frame*) + *(.note .note.*) + } +} diff --git a/limine/decompressor/main.c b/limine/decompressor/main.c new file mode 100644 index 0000000..94e8214 --- /dev/null +++ b/limine/decompressor/main.c @@ -0,0 +1,26 @@ +#include +#include +#include +#include + +noreturn void entry(uint8_t *compressed_stage2, size_t stage2_size, uint8_t boot_drive, int pxe) { + // The decompressor should decompress compressed_stage2 to address 0xf000. + uint8_t *dest = (uint8_t *)0xf000; + + tinf_gzip_uncompress(dest, compressed_stage2, stage2_size); + + asm volatile ( + "movl $0xf000, %%esp\n\t" + "xorl %%ebp, %%ebp\n\t" + "pushl %1\n\t" + "pushl %0\n\t" + "pushl $0\n\t" + "pushl $0xf000\n\t" + "ret\n\t" + : + : "r" ((uint32_t)boot_drive), "r" (pxe) + : "memory" + ); + + __builtin_unreachable(); +} diff --git a/limine/decompressor/memory.c b/limine/decompressor/memory.c new file mode 100644 index 0000000..a5353b2 --- /dev/null +++ b/limine/decompressor/memory.c @@ -0,0 +1,53 @@ +#include +#include + +void *memcpy(void *restrict dest, const void *restrict src, size_t n) { + uint8_t *restrict pdest = (uint8_t *restrict)dest; + const uint8_t *restrict psrc = (const uint8_t *restrict)src; + + for (size_t i = 0; i < n; i++) { + pdest[i] = psrc[i]; + } + + return dest; +} + +void *memset(void *s, int c, size_t n) { + uint8_t *p = (uint8_t *)s; + + for (size_t i = 0; i < n; i++) { + p[i] = (uint8_t)c; + } + + return s; +} + +void *memmove(void *dest, const void *src, size_t n) { + uint8_t *pdest = (uint8_t *)dest; + const uint8_t *psrc = (const uint8_t *)src; + + if ((uintptr_t)src > (uintptr_t)dest) { + for (size_t i = 0; i < n; i++) { + pdest[i] = psrc[i]; + } + } else if ((uintptr_t)src < (uintptr_t)dest) { + for (size_t i = n; i > 0; i--) { + pdest[i-1] = psrc[i-1]; + } + } + + return dest; +} + +int memcmp(const void *s1, const void *s2, size_t n) { + const uint8_t *p1 = (const uint8_t *)s1; + const uint8_t *p2 = (const uint8_t *)s2; + + for (size_t i = 0; i < n; i++) { + if (p1[i] != p2[i]) { + return p1[i] < p2[i] ? -1 : 1; + } + } + + return 0; +} diff --git a/limine/decompressor/tinf.patch b/limine/decompressor/tinf.patch new file mode 100644 index 0000000..3f87e8e --- /dev/null +++ b/limine/decompressor/tinf.patch @@ -0,0 +1,264 @@ +diff '--color=auto' -urN tinf-clean/tinf.h decompressor/tinf/tinf.h +--- tinf-clean/tinf.h 2023-06-06 00:57:04.683481827 +0200 ++++ decompressor/tinf/tinf.h 2023-06-06 00:56:14.485629430 +0200 +@@ -59,7 +59,9 @@ + * + * @deprecated No longer required, may be removed in a future version. + */ ++/* + void TINFCC tinf_init(void); ++*/ + + /** + * Decompress `sourceLen` bytes of deflate data from `source` to `dest`. +@@ -76,7 +78,7 @@ + * @param sourceLen size of compressed data + * @return `TINF_OK` on success, error code on error + */ +-int TINFCC tinf_uncompress(void *dest, unsigned int *destLen, ++int TINFCC tinf_uncompress(void *dest, + const void *source, unsigned int sourceLen); + + /** +@@ -94,7 +96,7 @@ + * @param sourceLen size of compressed data + * @return `TINF_OK` on success, error code on error + */ +-int TINFCC tinf_gzip_uncompress(void *dest, unsigned int *destLen, ++int TINFCC tinf_gzip_uncompress(void *dest, + const void *source, unsigned int sourceLen); + + /** +@@ -112,8 +114,10 @@ + * @param sourceLen size of compressed data + * @return `TINF_OK` on success, error code on error + */ ++/* + int TINFCC tinf_zlib_uncompress(void *dest, unsigned int *destLen, + const void *source, unsigned int sourceLen); ++*/ + + /** + * Compute Adler-32 checksum of `length` bytes starting at `data`. +@@ -122,7 +126,9 @@ + * @param length size of data + * @return Adler-32 checksum + */ ++/* + unsigned int TINFCC tinf_adler32(const void *data, unsigned int length); ++*/ + + /** + * Compute CRC32 checksum of `length` bytes starting at `data`. +@@ -131,7 +137,9 @@ + * @param length size of data + * @return CRC32 checksum + */ ++/* + unsigned int TINFCC tinf_crc32(const void *data, unsigned int length); ++*/ + + #ifdef __cplusplus + } /* extern "C" */ +diff '--color=auto' -urN tinf-clean/tinfgzip.c decompressor/tinf/tinfgzip.c +--- tinf-clean/tinfgzip.c 2023-06-06 00:57:16.983772372 +0200 ++++ decompressor/tinf/tinfgzip.c 2023-06-06 01:09:39.119942087 +0200 +@@ -39,6 +39,8 @@ + | ((unsigned int) p[1] << 8); + } + ++/* ++ + static unsigned int read_le32(const unsigned char *p) + { + return ((unsigned int) p[0]) +@@ -47,13 +49,17 @@ + | ((unsigned int) p[3] << 24); + } + +-int tinf_gzip_uncompress(void *dest, unsigned int *destLen, ++*/ ++ ++int tinf_gzip_uncompress(void *dest, + const void *source, unsigned int sourceLen) + { + const unsigned char *src = (const unsigned char *) source; + unsigned char *dst = (unsigned char *) dest; + const unsigned char *start; ++/* + unsigned int dlen, crc32; ++*/ + int res; + unsigned char flg; + +@@ -101,7 +107,7 @@ + /* Skip file name if present */ + if (flg & FNAME) { + do { +- if (start - src >= sourceLen) { ++ if (((unsigned int)(start - src)) >= sourceLen) { + return TINF_DATA_ERROR; + } + } while (*start++); +@@ -110,7 +116,7 @@ + /* Skip file comment if present */ + if (flg & FCOMMENT) { + do { +- if (start - src >= sourceLen) { ++ if (((unsigned int)(start - src)) >= sourceLen) { + return TINF_DATA_ERROR; + } + } while (*start++); +@@ -118,6 +124,7 @@ + + /* Check header crc if present */ + if (flg & FHCRC) { ++/* + unsigned int hcrc; + + if (start - src > sourceLen - 2) { +@@ -129,10 +136,12 @@ + if (hcrc != (tinf_crc32(src, start - src) & 0x0000FFFF)) { + return TINF_DATA_ERROR; + } ++*/ + + start += 2; + } + ++#if 0 + /* -- Get decompressed length -- */ + + dlen = read_le32(&src[sourceLen - 4]); +@@ -144,6 +153,7 @@ + /* -- Get CRC32 checksum of original data -- */ + + crc32 = read_le32(&src[sourceLen - 8]); ++#endif + + /* -- Decompress data -- */ + +@@ -151,13 +161,14 @@ + return TINF_DATA_ERROR; + } + +- res = tinf_uncompress(dst, destLen, start, ++ res = tinf_uncompress(dst, start, + (src + sourceLen) - start - 8); + + if (res != TINF_OK) { + return TINF_DATA_ERROR; + } + ++#if 0 + if (*destLen != dlen) { + return TINF_DATA_ERROR; + } +@@ -167,6 +178,7 @@ + if (crc32 != tinf_crc32(dst, dlen)) { + return TINF_DATA_ERROR; + } ++#endif + + return TINF_OK; + } +diff '--color=auto' -urN tinf-clean/tinflate.c decompressor/tinf/tinflate.c +--- tinf-clean/tinflate.c 2023-06-06 00:57:10.746958386 +0200 ++++ decompressor/tinf/tinflate.c 2023-06-06 01:12:19.629674294 +0200 +@@ -25,7 +25,7 @@ + + #include "tinf.h" + +-#include ++#define assert(...) + #include + + #if defined(UINT_MAX) && (UINT_MAX) < 0xFFFFFFFFUL +@@ -49,7 +49,9 @@ + + unsigned char *dest_start; + unsigned char *dest; ++/* + unsigned char *dest_end; ++*/ + + struct tinf_tree ltree; /* Literal/length tree */ + struct tinf_tree dtree; /* Distance tree */ +@@ -425,9 +427,12 @@ + } + + if (sym < 256) { ++/* + if (d->dest == d->dest_end) { + return TINF_BUF_ERROR; + } ++*/ ++ + *d->dest++ = sym; + } + else { +@@ -465,9 +470,11 @@ + return TINF_DATA_ERROR; + } + ++/* + if (d->dest_end - d->dest < length) { + return TINF_BUF_ERROR; + } ++*/ + + /* Copy match */ + for (i = 0; i < length; ++i) { +@@ -501,6 +508,7 @@ + + d->source += 4; + ++/* + if (d->source_end - d->source < length) { + return TINF_DATA_ERROR; + } +@@ -508,6 +516,7 @@ + if (d->dest_end - d->dest < length) { + return TINF_BUF_ERROR; + } ++*/ + + /* Copy block */ + while (length--) { +@@ -548,13 +557,15 @@ + /* -- Public functions -- */ + + /* Initialize global (static) data */ ++/* + void tinf_init(void) + { + return; + } ++*/ + + /* Inflate stream from source to dest */ +-int tinf_uncompress(void *dest, unsigned int *destLen, ++int tinf_uncompress(void *dest, + const void *source, unsigned int sourceLen) + { + struct tinf_data d; +@@ -569,7 +580,9 @@ + + d.dest = (unsigned char *) dest; + d.dest_start = d.dest; ++/* + d.dest_end = d.dest + *destLen; ++*/ + + do { + unsigned int btype; +@@ -610,7 +623,9 @@ + return TINF_DATA_ERROR; + } + ++/* + *destLen = d.dest - d.dest_start; ++*/ + + return TINF_OK; + } diff --git a/limine/host/.gitignore b/limine/host/.gitignore new file mode 100644 index 0000000..a1492fd --- /dev/null +++ b/limine/host/.gitignore @@ -0,0 +1,2 @@ +limine +limine.exe diff --git a/limine/host/hgen.sh b/limine/host/hgen.sh new file mode 100755 index 0000000..934a5a5 --- /dev/null +++ b/limine/host/hgen.sh @@ -0,0 +1,16 @@ +#! /bin/sh + +set -e + +LC_ALL=C +export LC_ALL + +cat < +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef LIMINE_NO_BIOS +#include "limine-bios-hdd.h" +#endif + +static char *program_name = NULL; + +static void perror_wrap(const char *fmt, ...) { + int old_errno = errno; + + fprintf(stderr, "%s: ", program_name); + + va_list args; + va_start(args, fmt); + + vfprintf(stderr, fmt, args); + + va_end(args); + + fprintf(stderr, ": %s\n", strerror(old_errno)); +} + +static void remove_arg(int *argc, char *argv[], int index) { + for (int i = index; i < *argc - 1; i++) { + argv[i] = argv[i + 1]; + } + + (*argc)--; + + argv[*argc] = NULL; +} + +static inline bool mul_u64_overflow(uint64_t a, uint64_t b, uint64_t *res) { + *res = a * b; + return a != 0 && b > UINT64_MAX / a; +} + +static inline bool add_u64_overflow(uint64_t a, uint64_t b, uint64_t *res) { + *res = a + b; + return a > UINT64_MAX - b; +} + +#ifndef LIMINE_NO_BIOS + +static bool quiet = false; + +static int set_pos(FILE *stream, uint64_t pos) { + if (sizeof(long) >= 8) { + return fseek(stream, (long)pos, SEEK_SET); + } + + long jump_size = (LONG_MAX / 2) + 1; + long last_jump = pos % jump_size; + uint64_t jumps = pos / jump_size; + + rewind(stream); + + for (uint64_t i = 0; i < jumps; i++) { + if (fseek(stream, jump_size, SEEK_CUR) != 0) { + return -1; + } + } + if (fseek(stream, last_jump, SEEK_CUR) != 0) { + return -1; + } + + return 0; +} + +#define SIZEOF_ARRAY(array) (sizeof(array) / sizeof(array[0])) +#define DIV_ROUNDUP(a, b) (((a) + ((b) - 1)) / (b)) + +struct gpt_table_header { + // the head + char signature[8]; + uint32_t revision; + uint32_t header_size; + uint32_t crc32; + uint32_t _reserved0; + + // the partitioning info + uint64_t my_lba; + uint64_t alternate_lba; + uint64_t first_usable_lba; + uint64_t last_usable_lba; + + // the guid + uint64_t disk_guid[2]; + + // entries related + uint64_t partition_entry_lba; + uint32_t number_of_partition_entries; + uint32_t size_of_partition_entry; + uint32_t partition_entry_array_crc32; +}; + +struct gpt_entry { + uint64_t partition_type_guid[2]; + + uint64_t unique_partition_guid[2]; + + uint64_t starting_lba; + uint64_t ending_lba; + + uint64_t attributes; + + uint16_t partition_name[36]; +}; + +struct gpt2mbr_type_conv { + uint64_t gpt_type1; + uint64_t gpt_type2; + uint8_t mbr_type; +}; + +// This table is very incomplete, but it should be enough for covering +// all that matters for ISOHYBRIDs. +// Of course, though, expansion is welcome. +static struct gpt2mbr_type_conv gpt2mbr_type_conv_table[] = { + { 0x11d2f81fc12a7328, 0x3bc93ec9a0004bba, 0xef }, // EFI system partition + { 0x4433b9e5ebd0a0a2, 0xc79926b7b668c087, 0x07 }, // Microsoft basic data + { 0x11aa000048465300, 0xacec4365300011aa, 0xaf }, // HFS/HFS+ +}; + +static int gpt2mbr_type(uint64_t gpt_type1, uint64_t gpt_type2) { + for (size_t i = 0; i < SIZEOF_ARRAY(gpt2mbr_type_conv_table); i++) { + if (gpt2mbr_type_conv_table[i].gpt_type1 == gpt_type1 + && gpt2mbr_type_conv_table[i].gpt_type2 == gpt_type2) { + return gpt2mbr_type_conv_table[i].mbr_type; + } + } + return -1; +} + +static void lba2chs(uint8_t *chs, uint64_t lba) { + // If LBA is too big to express, use a standard value for CHS. + if (lba > 63 * 255 * 1024) { + goto lba_too_big; + } + + uint64_t cylinder = lba / (255 * 63); + if (cylinder >= 1024) { +lba_too_big: + chs[0] = 0xfe; + chs[1] = 0xff; + chs[2] = 0xff; + return; + } + uint64_t head = (lba / 63) % 255; + uint64_t sector = (lba % 63) + 1; + + chs[0] = head; + chs[1] = (cylinder >> 2) & 0xc0; // high 2 bits + chs[1] |= sector & 0x3f; + chs[2] = cylinder; // low 8 bits +} + +static uint16_t endswap16(uint16_t value) { + uint16_t ret = 0; + ret |= (value >> 8) & 0x00ff; + ret |= (value << 8) & 0xff00; + return ret; +} + +static uint32_t endswap32(uint32_t value) { + uint32_t ret = 0; + ret |= (value >> 24) & 0x000000ff; + ret |= (value >> 8) & 0x0000ff00; + ret |= (value << 8) & 0x00ff0000; + ret |= (value << 24) & 0xff000000; + return ret; +} + +static uint64_t endswap64(uint64_t value) { + uint64_t ret = 0; + ret |= (value >> 56) & 0x00000000000000ff; + ret |= (value >> 40) & 0x000000000000ff00; + ret |= (value >> 24) & 0x0000000000ff0000; + ret |= (value >> 8) & 0x00000000ff000000; + ret |= (value << 8) & 0x000000ff00000000; + ret |= (value << 24) & 0x0000ff0000000000; + ret |= (value << 40) & 0x00ff000000000000; + ret |= (value << 56) & 0xff00000000000000; + return ret; +} + +#ifdef __BYTE_ORDER__ + +#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +#define bigendian true +#else +#define bigendian false +#endif + +#else /* !__BYTE_ORDER__ */ + +static bool bigendian = false; + +#endif /* !__BYTE_ORDER__ */ + +#define ENDSWAP(VALUE) (bigendian ? ( \ + sizeof(VALUE) == 1 ? (VALUE) : \ + sizeof(VALUE) == 2 ? endswap16(VALUE) : \ + sizeof(VALUE) == 4 ? endswap32(VALUE) : \ + sizeof(VALUE) == 8 ? endswap64(VALUE) : (abort(), 1) \ +) : (VALUE)) + +static enum { + CACHE_CLEAN, + CACHE_DIRTY +} cache_state; +static uint64_t cached_block; +static uint8_t *cache = NULL; +static FILE *device = NULL; +static size_t block_size; + +static bool device_init(void) { + size_t guesses[] = { 512, 2048, 4096 }; + + for (size_t i = 0; i < SIZEOF_ARRAY(guesses); i++) { + void *tmp = realloc(cache, guesses[i]); + if (tmp == NULL) { + perror_wrap("error: device_init(): realloc()"); + return false; + } + cache = tmp; + + rewind(device); + + size_t ret = fread(cache, guesses[i], 1, device); + if (ret != 1) { + continue; + } + + block_size = guesses[i]; + + if (!quiet) { + fprintf(stderr, "Physical block size of %zu bytes.\n", block_size); + } + + cache_state = CACHE_CLEAN; + cached_block = 0; + return true; + } + + fprintf(stderr, "error: device_init(): Couldn't determine block size of device.\n"); + return false; +} + +static bool device_flush_cache(void) { + if (cache_state == CACHE_CLEAN) + return true; + + if (set_pos(device, cached_block * block_size) != 0) { + perror_wrap("error: device_flush_cache(): set_pos()"); + return false; + } + + size_t ret = fwrite(cache, block_size, 1, device); + if (ret != 1) { + if (ferror(device)) { + perror_wrap("error: device_flush_cache(): fwrite()"); + } + return false; + } + + cache_state = CACHE_CLEAN; + return true; +} + +static bool device_cache_block(uint64_t block) { + if (cached_block == block) + return true; + + if (cache_state == CACHE_DIRTY) { + if (!device_flush_cache()) + return false; + } + + if (set_pos(device, block * block_size) != 0) { + perror_wrap("error: device_cache_block(): set_pos()"); + return false; + } + + size_t ret = fread(cache, block_size, 1, device); + if (ret != 1) { + if (ferror(device)) { + perror_wrap("error: device_cache_block(): fread()"); + } + return false; + } + + cached_block = block; + + return true; +} + +struct uninstall_data { + void *data; + uint64_t loc; + uint64_t count; +}; + +#define UNINSTALL_DATA_MAX 256 + +static bool uninstalling = false; +static struct uninstall_data uninstall_data[UNINSTALL_DATA_MAX]; +static struct uninstall_data uninstall_data_rev[UNINSTALL_DATA_MAX]; +static uint64_t uninstall_data_i = 0; +static const char *uninstall_file = NULL; + +static void reverse_uninstall_data(void) { + for (size_t i = 0, j = uninstall_data_i - 1; i < uninstall_data_i; i++, j--) { + uninstall_data_rev[j] = uninstall_data[i]; + } + + memcpy(uninstall_data, uninstall_data_rev, uninstall_data_i * sizeof(struct uninstall_data)); +} + +static void free_uninstall_data(void) { + for (size_t i = 0; i < uninstall_data_i; i++) { + free(uninstall_data[i].data); + } +} + +static bool store_uninstall_data(const char *filename) { + if (!quiet) { + fprintf(stderr, "Storing uninstall data to file: `%s`...\n", filename); + } + + FILE *udfile = fopen(filename, "wb"); + if (udfile == NULL) { + perror_wrap("error: `%s`", filename); + goto error; + } + + if (fwrite(&uninstall_data_i, sizeof(uint64_t), 1, udfile) != 1) { + goto fwrite_error; + } + + for (size_t i = 0; i < uninstall_data_i; i++) { + if (fwrite(&uninstall_data[i].loc, sizeof(uint64_t), 1, udfile) != 1) { + goto fwrite_error; + } + if (fwrite(&uninstall_data[i].count, sizeof(uint64_t), 1, udfile) != 1) { + goto fwrite_error; + } + if (fwrite(uninstall_data[i].data, uninstall_data[i].count, 1, udfile) != 1) { + goto fwrite_error; + } + } + + fclose(udfile); + return true; + +fwrite_error: + perror_wrap("error: store_uninstall_data(): fwrite()"); + +error: + if (udfile != NULL) { + fclose(udfile); + } + return false; +} + +static bool load_uninstall_data(const char *filename) { + size_t loaded_count = 0; + + if (!quiet) { + fprintf(stderr, "Loading uninstall data from file: `%s`...\n", filename); + } + + FILE *udfile = fopen(filename, "rb"); + if (udfile == NULL) { + perror_wrap("error: `%s`", filename); + goto error; + } + + if (fread(&uninstall_data_i, sizeof(uint64_t), 1, udfile) != 1) { + goto fread_error; + } + + if (uninstall_data_i > UNINSTALL_DATA_MAX) { + fprintf(stderr, "error: load_uninstall_data(): too many entries (%zu > %d)\n", + (size_t)uninstall_data_i, UNINSTALL_DATA_MAX); + goto error; + } + + for (size_t i = 0; i < uninstall_data_i; i++) { + if (fread(&uninstall_data[i].loc, sizeof(uint64_t), 1, udfile) != 1) { + goto fread_error; + } + if (fread(&uninstall_data[i].count, sizeof(uint64_t), 1, udfile) != 1) { + goto fread_error; + } + uninstall_data[i].data = malloc(uninstall_data[i].count); + if (uninstall_data[i].data == NULL) { + perror_wrap("error: load_uninstall_data(): malloc()"); + goto error; + } + if (fread(uninstall_data[i].data, uninstall_data[i].count, 1, udfile) != 1) { + free(uninstall_data[i].data); + goto fread_error; + } + loaded_count++; + } + + fclose(udfile); + return true; + +fread_error: + perror_wrap("error: load_uninstall_data(): fread()"); + +error: + // Free any previously allocated uninstall data + for (size_t j = 0; j < loaded_count; j++) { + free(uninstall_data[j].data); + } + if (udfile != NULL) { + fclose(udfile); + } + return false; +} + +static bool _device_read(void *_buffer, uint64_t loc, size_t count) { + uint8_t *buffer = _buffer; + uint64_t progress = 0; + while (progress < count) { + uint64_t block = (loc + progress) / block_size; + + if (!device_cache_block(block)) { + return false; + } + + uint64_t chunk = count - progress; + uint64_t offset = (loc + progress) % block_size; + if (chunk > block_size - offset) + chunk = block_size - offset; + + memcpy(buffer + progress, &cache[offset], chunk); + progress += chunk; + } + + return true; +} + +static bool _device_write(const void *_buffer, uint64_t loc, size_t count) { + struct uninstall_data *ud = NULL; + + if (uninstalling) { + goto skip_save; + } + + if (uninstall_data_i >= UNINSTALL_DATA_MAX) { + fprintf(stderr, "error: Too many uninstall data entries! Please report this bug upstream.\n"); + return false; + } + + ud = &uninstall_data[uninstall_data_i]; + + ud->data = malloc(count); + if (ud->data == NULL) { + perror_wrap("error: _device_write(): malloc()"); + return false; + } + + if (!_device_read(ud->data, loc, count)) { + free(ud->data); + return false; + } + + ud->loc = loc; + ud->count = count; + +skip_save:; + const uint8_t *buffer = _buffer; + uint64_t progress = 0; + while (progress < count) { + uint64_t block = (loc + progress) / block_size; + + if (!device_cache_block(block)) { + if (!uninstalling) { + free(ud->data); + } + return false; + } + + uint64_t chunk = count - progress; + uint64_t offset = (loc + progress) % block_size; + if (chunk > block_size - offset) + chunk = block_size - offset; + + memcpy(&cache[offset], buffer + progress, chunk); + cache_state = CACHE_DIRTY; + progress += chunk; + } + + if (!uninstalling) { + uninstall_data_i++; + } + return true; +} + +static bool uninstall(bool quiet_arg) { + bool print_cache_flush_fail = false; + bool print_write_fail = false; + bool ret = true; + + uninstalling = true; + + cache_state = CACHE_CLEAN; + cached_block = (uint64_t)-1; + + for (size_t i = 0; i < uninstall_data_i; i++) { + struct uninstall_data *ud = &uninstall_data[i]; + bool retry = false; + while (!_device_write(ud->data, ud->loc, ud->count)) { + if (retry) { + fprintf(stderr, "warning: Retry failed.\n"); + print_write_fail = true; + break; + } + if (!quiet) { + fprintf(stderr, "warning: Uninstall data index %zu failed to write, retrying...\n", i); + } + if (!device_flush_cache()) { + print_cache_flush_fail = true; + } + cache_state = CACHE_CLEAN; + cached_block = (uint64_t)-1; + retry = true; + } + } + + if (!device_flush_cache()) { + print_cache_flush_fail = true; + } + + if (print_write_fail) { + fprintf(stderr, "error: Some data failed to be uninstalled correctly.\n"); + ret = false; + } + + if (print_cache_flush_fail) { + fprintf(stderr, "error: Device cache flush failure. Uninstall may be incomplete.\n"); + ret = false; + } + + if (ret == true && !quiet && !quiet_arg) { + fprintf(stderr, "Uninstall data restored successfully.\n"); + } + + return ret; +} + +#define device_read(BUFFER, LOC, COUNT) \ + do { \ + if (!_device_read(BUFFER, LOC, COUNT)) \ + goto cleanup; \ + } while (0) + +#define device_write(BUFFER, LOC, COUNT) \ + do { \ + if (!_device_write(BUFFER, LOC, COUNT)) \ + goto cleanup; \ + } while (0) + +static void bios_install_usage(void) { + printf("usage: %s bios-install [GPT partition index]\n", program_name); + printf("\n"); + printf(" --force Force installation even if the safety checks fail\n"); + printf(" (DANGEROUS!)\n"); + printf("\n"); + printf(" --uninstall Reverse the entire install procedure\n"); + printf("\n"); + printf(" --uninstall-data-file=\n"); + printf(" Set the input (for --uninstall) or output file\n"); + printf(" name of the file which contains uninstall data\n"); + printf("\n"); + printf(" --no-gpt-to-mbr-isohybrid-conversion\n"); + printf(" Do not automatically convert a GUID partition table (GPT)\n"); + printf(" found on an ISOHYBRID image into an MBR partition table\n"); + printf(" (which is done for better hardware compatibility)\n"); + printf("\n"); + printf(" --quiet Do not print verbose diagnostic messages\n"); + printf("\n"); + printf(" --help | -h Display this help message\n"); + printf("\n"); +} + +static bool validate_or_force(uint64_t offset, bool force, bool *err) { + *err = false; + + char hintc[64]; + device_read(hintc, offset + 3, 4); + if (memcmp(hintc, "NTFS", 4) == 0) { + if (!force) { + return false; + } else { + memset(hintc, 0, 4); + device_write(hintc, offset + 3, 4); + } + } + device_read(hintc, offset + 54, 3); + if (memcmp(hintc, "FAT", 3) == 0) { + if (!force) { + return false; + } else { + memset(hintc, 0, 5); + device_write(hintc, offset + 54, 5); + } + } + device_read(hintc, offset + 82, 3); + if (memcmp(hintc, "FAT", 3) == 0) { + if (!force) { + return false; + } else { + memset(hintc, 0, 5); + device_write(hintc, offset + 82, 5); + } + } + device_read(hintc, offset + 3, 5); + if (memcmp(hintc, "FAT32", 5) == 0) { + if (!force) { + return false; + } else { + memset(hintc, 0, 5); + device_write(hintc, offset + 3, 5); + } + } + uint16_t hint16 = 0; + device_read(&hint16, offset + 1080, sizeof(uint16_t)); + hint16 = ENDSWAP(hint16); + if (hint16 == 0xef53) { + if (!force) { + return false; + } else { + hint16 = 0; + hint16 = ENDSWAP(hint16); + device_write(&hint16, offset + 1080, sizeof(uint16_t)); + } + } + + return true; + +cleanup: + *err = true; + return false; +} + +static int bios_install(int argc, char *argv[]) { + int ok = EXIT_FAILURE; + bool force = false; + bool gpt2mbr_allowed = true; + bool uninstall_mode = false; + const uint8_t *bootloader_img = binary_limine_hdd_bin_data; + size_t bootloader_file_size = sizeof(binary_limine_hdd_bin_data); + uint8_t orig_mbr[70], timestamp[6]; + const char *part_ndx = NULL; + +#ifndef __BYTE_ORDER__ + uint32_t endcheck = 0x12345678; + uint8_t endbyte = *((uint8_t *)&endcheck); + bigendian = endbyte == 0x12; +#endif + + if (argc < 2) { + bios_install_usage(); +#ifdef IS_WINDOWS + system("pause"); +#endif + return EXIT_FAILURE; + } + + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) { + bios_install_usage(); + return EXIT_SUCCESS; + } else if (strcmp(argv[i], "--quiet") == 0) { + quiet = true; + } else if (strcmp(argv[i], "--force") == 0) { + if (force && !quiet) { + fprintf(stderr, "warning: --force already set.\n"); + } + force = true; + } else if (strcmp(argv[i], "--no-gpt-to-mbr-isohybrid-conversion") == 0) { + gpt2mbr_allowed = false; + } else if (strcmp(argv[i], "--uninstall") == 0) { + if (uninstall_mode && !quiet) { + fprintf(stderr, "warning: --uninstall already set.\n"); + } + uninstall_mode = true; + } else if (memcmp(argv[i], "--uninstall-data-file=", 22) == 0) { + if (uninstall_file != NULL && !quiet) { + fprintf(stderr, "warning: --uninstall-data-file already set. Overriding...\n"); + } + uninstall_file = argv[i] + 22; + if (strlen(uninstall_file) == 0) { + fprintf(stderr, "error: Uninstall data file has a zero-length name!\n"); + return EXIT_FAILURE; + } + } else { + if (device != NULL) { // [GPT partition index] + part_ndx = argv[i]; // TODO: Make this non-positional? + } else if ((device = fopen(argv[i], "r+b")) == NULL) { // + perror_wrap("error: `%s`", argv[i]); + return EXIT_FAILURE; + } + } + } + + if (device == NULL) { + fprintf(stderr, "error: No device specified\n"); + bios_install_usage(); + return EXIT_FAILURE; + } + + if (!device_init()) { + goto uninstall_mode_cleanup; + } + + if (uninstall_mode) { + if (uninstall_file == NULL) { + fprintf(stderr, "error: Uninstall mode set but no --uninstall-data-file=... passed.\n"); + goto uninstall_mode_cleanup; + } + + if (!load_uninstall_data(uninstall_file)) { + goto uninstall_mode_cleanup; + } + + if (uninstall(false) == false) { + ok = EXIT_FAILURE; + } else { + ok = EXIT_SUCCESS; + } + goto uninstall_mode_cleanup; + } + + // Probe for GPT and logical block size + int gpt = 0; + struct gpt_table_header gpt_header; + uint64_t lb_guesses[] = { 512, 4096 }; + uint64_t lb_size = 0; + for (size_t i = 0; i < SIZEOF_ARRAY(lb_guesses); i++) { + device_read(&gpt_header, lb_guesses[i], sizeof(struct gpt_table_header)); + if (!strncmp(gpt_header.signature, "EFI PART", 8)) { + lb_size = lb_guesses[i]; + gpt = 1; + if (!quiet) { + fprintf(stderr, "Installing to GPT. Logical block size of %" PRIu64 " bytes.\n", + lb_guesses[i]); + } + break; + } + } + + struct gpt_table_header secondary_gpt_header; + if (gpt) { + if (!quiet) { + fprintf(stderr, "Secondary header at LBA 0x%" PRIx64 ".\n", + ENDSWAP(gpt_header.alternate_lba)); + } + device_read(&secondary_gpt_header, lb_size * ENDSWAP(gpt_header.alternate_lba), + sizeof(struct gpt_table_header)); + if (!strncmp(secondary_gpt_header.signature, "EFI PART", 8)) { + if (!quiet) { + fprintf(stderr, "Secondary header valid.\n"); + } + } else { + fprintf(stderr, "error: Secondary header not valid, aborting.\n"); + goto cleanup; + } + } + + // Check if this is an ISO w/ a GPT, in which case try converting it + // to MBR for improved compatibility with a whole range of hardware that + // does not like booting off of GPT in BIOS or CSM mode, and other + // broken hardware. + if (gpt && gpt2mbr_allowed == true) { + char iso_signature[5]; + device_read(iso_signature, 32769, 5); + + if (strncmp(iso_signature, "CD001", 5) != 0) { + goto no_mbr_conv; + } + + if (!quiet) { + fprintf(stderr, "Detected ISOHYBRID with a GUID partition table (GPT).\n"); + fprintf(stderr, "Converting to MBR for improved compatibility...\n"); + } + + // Gather the (up to 4) GPT partition to convert. + struct { + uint64_t lba_start; + uint64_t lba_end; + uint8_t chs_start[3]; + uint8_t chs_end[3]; + uint8_t type; + } part_to_conv[4]; + size_t part_to_conv_i = 0; + + uint64_t part_entry_base; + if (mul_u64_overflow(ENDSWAP(gpt_header.partition_entry_lba), lb_size, &part_entry_base)) { + goto no_mbr_conv; + } + + for (int64_t i = 0; i < (int64_t)ENDSWAP(gpt_header.number_of_partition_entries); i++) { + struct gpt_entry gpt_entry; + uint64_t entry_offset = (uint64_t)i * ENDSWAP(gpt_header.size_of_partition_entry); + if (add_u64_overflow(part_entry_base, entry_offset, &entry_offset)) { + goto no_mbr_conv; + } + device_read(&gpt_entry, entry_offset, sizeof(struct gpt_entry)); + + if (gpt_entry.unique_partition_guid[0] == 0 && + gpt_entry.unique_partition_guid[1] == 0) { + continue; + } + + if (part_to_conv_i == 4) { + if (!quiet) { + fprintf(stderr, "GPT contains more than 4 partitions, will not convert.\n"); + } + goto no_mbr_conv; + } + + if (ENDSWAP(gpt_entry.starting_lba) > UINT32_MAX) { + if (!quiet) { + fprintf(stderr, "Starting LBA of partition %" PRIi64 " is greater than UINT32_MAX, will not convert GPT.\n", i + 1); + } + goto no_mbr_conv; + } + part_to_conv[part_to_conv_i].lba_start = ENDSWAP(gpt_entry.starting_lba); + lba2chs(part_to_conv[part_to_conv_i].chs_start, part_to_conv[part_to_conv_i].lba_start); + + if (ENDSWAP(gpt_entry.ending_lba) > UINT32_MAX) { + if (!quiet) { + fprintf(stderr, "Ending LBA of partition %" PRIi64 " is greater than UINT32_MAX, will not convert GPT.\n", i + 1); + } + goto no_mbr_conv; + } + part_to_conv[part_to_conv_i].lba_end = ENDSWAP(gpt_entry.ending_lba); + lba2chs(part_to_conv[part_to_conv_i].chs_end, part_to_conv[part_to_conv_i].lba_end); + + if (part_to_conv[part_to_conv_i].lba_end - part_to_conv[part_to_conv_i].lba_start + 1 > UINT32_MAX) { + if (!quiet) { + fprintf(stderr, "Sector count of partition %" PRIi64 " is greater than UINT32_MAX, will not convert GPT.\n", i + 1); + } + goto no_mbr_conv; + } + + int type = gpt2mbr_type(ENDSWAP(gpt_entry.partition_type_guid[0]), + ENDSWAP(gpt_entry.partition_type_guid[1])); + if (type == -1) { + if (!quiet) { + fprintf(stderr, "Cannot convert partition type for partition %" PRIi64 ", will not convert GPT.\n", i + 1); + } + goto no_mbr_conv; + } + + part_to_conv[part_to_conv_i].type = type; + + part_to_conv_i++; + } + + // Nuke the GPTs. + void *empty_lba = calloc(1, lb_size); + if (empty_lba == NULL) { + perror_wrap("error: bios_install(): malloc()"); + goto cleanup; + } + + // ... nuke primary GPT + protective MBR. + for (size_t i = 0; i < 34; i++) { + device_write(empty_lba, i * lb_size, lb_size); + } + + // ... nuke secondary GPT. + uint64_t alt_lba = ENDSWAP(gpt_header.alternate_lba); + if (alt_lba >= 32) { + for (size_t i = 0; i < 33; i++) { + device_write(empty_lba, (alt_lba - 32 + i) * lb_size, lb_size); + } + } + + free(empty_lba); + + // We're no longer GPT. + gpt = 0; + + // Generate pseudorandom MBR disk ID. + srand(time(NULL)); + for (size_t i = 0; i < 4; i++) { + uint8_t r = rand(); + device_write(&r, 0x1b8 + i, 1); + } + + // Write out the partition entries. + for (size_t i = 0; i < part_to_conv_i; i++) { + device_write(&part_to_conv[i].type, 0x1be + i * 16 + 0x04, 1); + uint32_t lba_start = ENDSWAP((uint32_t)part_to_conv[i].lba_start); + device_write(&lba_start, 0x1be + i * 16 + 0x08, 4); + uint32_t sect_count = ENDSWAP((uint32_t)((part_to_conv[i].lba_end - part_to_conv[i].lba_start) + 1)); + device_write(§_count, 0x1be + i * 16 + 0x0c, 4); + + device_write(part_to_conv[i].chs_start, 0x1be + i * 16 + 1, 3); + device_write(part_to_conv[i].chs_end, 0x1be + i * 16 + 5, 3); + } + + if (!quiet) { + fprintf(stderr, "Conversion successful.\n"); + } + } + +no_mbr_conv:; + + int mbr = 0; + if (gpt == 0) { + // Do all sanity checks on MBR + mbr = 1; + + uint8_t hint8 = 0; + uint32_t hint32 = 0; + + bool any_active = false; + + device_read(&hint8, 446, sizeof(uint8_t)); + if (hint8 != 0x00 && hint8 != 0x80) { + if (!force) { + mbr = 0; + } else { + hint8 &= 0x80; + device_write(&hint8, 446, sizeof(uint8_t)); + } + } + any_active = any_active || (hint8 & 0x80) != 0; + device_read(&hint8, 446 + 4, sizeof(uint8_t)); + if (hint8 != 0x00) { + device_read(&hint32, 446 + 8, sizeof(uint32_t)); + hint32 = ENDSWAP(hint32); + if (hint32 < 63) { + goto part_too_low; + } + } + device_read(&hint8, 462, sizeof(uint8_t)); + if (hint8 != 0x00 && hint8 != 0x80) { + if (!force) { + mbr = 0; + } else { + hint8 &= 0x80; + device_write(&hint8, 462, sizeof(uint8_t)); + } + } + any_active = any_active || (hint8 & 0x80) != 0; + device_read(&hint8, 462 + 4, sizeof(uint8_t)); + if (hint8 != 0x00) { + device_read(&hint32, 462 + 8, sizeof(uint32_t)); + hint32 = ENDSWAP(hint32); + if (hint32 < 63) { + goto part_too_low; + } + } + device_read(&hint8, 478, sizeof(uint8_t)); + if (hint8 != 0x00 && hint8 != 0x80) { + if (!force) { + mbr = 0; + } else { + hint8 &= 0x80; + device_write(&hint8, 478, sizeof(uint8_t)); + } + } + any_active = any_active || (hint8 & 0x80) != 0; + device_read(&hint8, 478 + 4, sizeof(uint8_t)); + if (hint8 != 0x00) { + device_read(&hint32, 478 + 8, sizeof(uint32_t)); + hint32 = ENDSWAP(hint32); + if (hint32 < 63) { + goto part_too_low; + } + } + device_read(&hint8, 494, sizeof(uint8_t)); + if (hint8 != 0x00 && hint8 != 0x80) { + if (!force) { + mbr = 0; + } else { + hint8 &= 0x80; + device_write(&hint8, 494, sizeof(uint8_t)); + } + } + any_active = any_active || (hint8 & 0x80) != 0; + device_read(&hint8, 494 + 4, sizeof(uint8_t)); + if (hint8 != 0x00) { + device_read(&hint32, 494 + 8, sizeof(uint32_t)); + hint32 = ENDSWAP(hint32); + if (hint32 < 63) { + goto part_too_low; + } + } + + if (0) { +part_too_low: + fprintf(stderr, "error: A partition's start sector is less than 63, aborting.\n"); + goto cleanup; + } + + if (mbr) { + bool err; + mbr = validate_or_force(0, force, &err); + if (err) { + goto cleanup; + } + } + + if (mbr && !any_active) { + if (!quiet) { + fprintf(stderr, "No active partition found, some systems may not boot.\n"); + fprintf(stderr, "Setting partition 1 as active to work around the issue...\n"); + } + hint8 = 0x80; + device_write(&hint8, 446, sizeof(uint8_t)); + } + } + + if (gpt == 0 && mbr == 0) { + fprintf(stderr, "error: Could not determine if the device has a valid partition table.\n"); + fprintf(stderr, " Please ensure the device has a valid MBR or GPT.\n"); + fprintf(stderr, " Alternatively, pass `--force` to override these checks.\n"); + fprintf(stderr, " **ONLY DO THIS AT YOUR OWN RISK, DATA LOSS MAY OCCUR!**\n"); + goto cleanup; + } + + // Default location of stage2 for MBR (in post MBR gap) + uint64_t stage2_loc = 512; + + if (gpt) { + struct gpt_entry gpt_entry; + uint32_t partition_num; + + uint64_t gpt_part_entry_base; + if (mul_u64_overflow(ENDSWAP(gpt_header.partition_entry_lba), lb_size, &gpt_part_entry_base)) { + fprintf(stderr, "error: GPT partition entry LBA overflows.\n"); + goto cleanup; + } + + if (part_ndx != NULL) { + if (sscanf(part_ndx, "%" SCNu32, &partition_num) != 1) { + fprintf(stderr, "error: Invalid partition number format.\n"); + goto cleanup; + } + partition_num--; + if (partition_num >= ENDSWAP(gpt_header.number_of_partition_entries)) { + fprintf(stderr, "error: Partition number is too large.\n"); + goto cleanup; + } + + uint64_t entry_off = (uint64_t)partition_num * ENDSWAP(gpt_header.size_of_partition_entry); + if (add_u64_overflow(gpt_part_entry_base, entry_off, &entry_off)) { + fprintf(stderr, "error: GPT partition entry offset overflows.\n"); + goto cleanup; + } + device_read(&gpt_entry, entry_off, sizeof(struct gpt_entry)); + + if (gpt_entry.unique_partition_guid[0] == 0 && + gpt_entry.unique_partition_guid[1] == 0) { + fprintf(stderr, "error: No such partition: %" PRIu32 ".\n", partition_num + 1); + goto cleanup; + } + + if (!force && memcmp("Hah!IdontNeedEFI", &gpt_entry.partition_type_guid, 16) != 0) { + fprintf(stderr, "error: Chosen partition for BIOS boot code is not of BIOS boot partition type.\n"); + fprintf(stderr, " Pass `--force` to override this check.\n"); + fprintf(stderr, " **ONLY DO THIS AT YOUR OWN RISK, DATA LOSS MAY OCCUR!**\n"); + goto cleanup; + } + } else { + // Try to autodetect the BIOS boot partition + for (partition_num = 0; partition_num < ENDSWAP(gpt_header.number_of_partition_entries); partition_num++) { + uint64_t entry_off = (uint64_t)partition_num * ENDSWAP(gpt_header.size_of_partition_entry); + if (add_u64_overflow(gpt_part_entry_base, entry_off, &entry_off)) { + fprintf(stderr, "error: GPT partition entry offset overflows.\n"); + goto cleanup; + } + device_read(&gpt_entry, entry_off, sizeof(struct gpt_entry)); + + if (memcmp("Hah!IdontNeedEFI", &gpt_entry.partition_type_guid, 16) == 0) { + if (!quiet) { + fprintf(stderr, "Autodetected partition %" PRIu32 " as BIOS boot partition.\n", partition_num + 1); + } + goto bios_boot_autodetected; + } + } + + fprintf(stderr, "error: Installing to a GPT device, but no BIOS boot partition specified or\n"); + fprintf(stderr, " detected.\n"); + goto cleanup; + } + +bios_boot_autodetected:; + uint64_t starting_lba = ENDSWAP(gpt_entry.starting_lba); + uint64_t ending_lba = ENDSWAP(gpt_entry.ending_lba); + + if (ending_lba < starting_lba) { + fprintf(stderr, "error: Partition %" PRIu32 " has ending LBA less than starting LBA.\n", partition_num + 1); + goto cleanup; + } + + uint64_t part_size; + if (mul_u64_overflow(ending_lba - starting_lba + 1, lb_size, &part_size)) { + fprintf(stderr, "error: Partition %" PRIu32 " size overflows.\n", partition_num + 1); + goto cleanup; + } + + if (part_size < 32768) { + fprintf(stderr, "error: Partition %" PRIu32 " is smaller than 32KiB.\n", partition_num + 1); + goto cleanup; + } + + if (mul_u64_overflow(starting_lba, lb_size, &stage2_loc)) { + fprintf(stderr, "error: Partition %" PRIu32 " starting LBA overflows.\n", partition_num + 1); + goto cleanup; + } + + bool err; + bool valid = validate_or_force(stage2_loc, force, &err); + if (err) { + goto cleanup; + } + + if (!valid) { + fprintf(stderr, "error: The partition selected to install the BIOS boot code to contains\n"); + fprintf(stderr, " a recognised filesystem.\n"); + fprintf(stderr, " Pass `--force` to override these checks.\n"); + fprintf(stderr, " **ONLY DO THIS AT YOUR OWN RISK, DATA LOSS MAY OCCUR!**\n"); + goto cleanup; + } + + if (!quiet) { + fprintf(stderr, "Installing BIOS boot code to partition %" PRIu32 ".\n", partition_num + 1); + } + } else { + if (!quiet) { + fprintf(stderr, "Installing to MBR.\n"); + } + } + + if (!quiet) { + fprintf(stderr, "Stage 2 to be located at byte offset 0x%" PRIx64 ".\n", stage2_loc); + } + + // Save original timestamp + device_read(timestamp, 218, 6); + + // Save the original partition table of the device + device_read(orig_mbr, 440, 70); + + // Write the bootsector from the bootloader to the device + device_write(&bootloader_img[0], 0, 512); + + // Write the rest of stage 2 to the device + device_write(&bootloader_img[512], stage2_loc, bootloader_file_size - 512); + + // Hardcode in the bootsector the location of stage 2 + stage2_loc = ENDSWAP(stage2_loc); + device_write(&stage2_loc, 0x1a4, sizeof(uint64_t)); + + // Write back timestamp + device_write(timestamp, 218, 6); + + // Write back the saved partition table to the device + device_write(orig_mbr, 440, 70); + + if (!device_flush_cache()) + goto cleanup; + + if (!quiet) { + fprintf(stderr, "Reminder: Remember to copy the limine-bios.sys file in either\n" + " the root, /boot, /limine, or /boot/limine directories of\n" + " one of the partitions on the device, or boot will fail!\n"); + + fprintf(stderr, "Limine BIOS stages installed successfully.\n"); + } + + ok = EXIT_SUCCESS; + +cleanup: + reverse_uninstall_data(); + if (ok != EXIT_SUCCESS) { + // If we failed, attempt to reverse install process + fprintf(stderr, "Install failed, undoing work...\n"); + uninstall(true); + } else if (uninstall_file != NULL) { + store_uninstall_data(uninstall_file); + } +uninstall_mode_cleanup: + free_uninstall_data(); + if (cache) + free(cache); + if (device != NULL) + fclose(device); + + return ok; +} +#endif + +#define CONFIG_B2SUM_SIGNATURE "++CONFIG_B2SUM_SIGNATURE++" + +static void enroll_config_usage(void) { + printf("usage: %s enroll-config \n", program_name); + printf("\n"); + printf(" --reset Remove enrolled BLAKE2B, will not check config integrity\n"); + printf("\n"); + printf(" --quiet Do not print verbose diagnostic messages\n"); + printf("\n"); + printf(" --help | -h Display this help message\n"); + printf("\n"); +} + +static int enroll_config(int argc, char *argv[]) { + int ret = EXIT_FAILURE; + + char *bootloader = NULL; + FILE *bootloader_file = NULL; + bool quiet = false; + bool reset = false; + + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) { + enroll_config_usage(); + return EXIT_SUCCESS; + } else if (strcmp(argv[i], "--quiet") == 0) { + remove_arg(&argc, argv, i--); + quiet = true; + } else if (strcmp(argv[i], "--reset") == 0) { + remove_arg(&argc, argv, i--); + reset = true; + } + } + + if (argc <= (reset ? 1 : 2)) { + enroll_config_usage(); +#ifdef IS_WINDOWS + system("pause"); +#endif + return EXIT_FAILURE; + } + + if (!reset && strlen(argv[2]) != 128) { + fprintf(stderr, "error: BLAKE2B specified is not 128 characters long.\n"); + goto cleanup; + } + + bootloader_file = fopen(argv[1], "r+b"); + if (bootloader_file == NULL) { + perror_wrap("error: `%s`", argv[1]); + goto cleanup; + } + + if (fseek(bootloader_file, 0, SEEK_END) != 0) { + perror_wrap("error: enroll_config(): fseek()"); + goto cleanup; + } + long ftell_result = ftell(bootloader_file); + if (ftell_result < 0) { + perror_wrap("error: enroll_config(): ftell()"); + goto cleanup; + } + size_t bootloader_size = (size_t)ftell_result; + rewind(bootloader_file); + + size_t min_size = (sizeof(CONFIG_B2SUM_SIGNATURE) - 1) + 128; + if (bootloader_size < min_size) { + fprintf(stderr, "error: Bootloader file too small (need at least %zu bytes)\n", min_size); + goto cleanup; + } + + bootloader = malloc(bootloader_size); + if (bootloader == NULL) { + perror_wrap("error: enroll_config(): malloc()"); + goto cleanup; + } + + if (fread(bootloader, bootloader_size, 1, bootloader_file) != 1) { + perror_wrap("error: enroll_config(): fread()"); + goto cleanup; + } + + char *checksum_loc = NULL; + size_t checked_count = 0; + const char *config_b2sum_sign = CONFIG_B2SUM_SIGNATURE; + for (size_t i = 0; i < bootloader_size - min_size + 1; i++) { + if (bootloader[i] != config_b2sum_sign[checked_count]) { + if (checked_count > 0) { + i -= checked_count; // restart after first byte of failed match + checked_count = 0; + } + continue; + } + + checked_count++; + + if (checked_count == sizeof(CONFIG_B2SUM_SIGNATURE) - 1) { + checksum_loc = &bootloader[i + 1]; + break; + } + } + + if (checksum_loc == NULL) { + fprintf(stderr, "error: Checksum location not found in provided executable.\n"); + goto cleanup; + } + + if (!reset) { + memcpy(checksum_loc, argv[2], 128); + } else { + memset(checksum_loc, '0', 128); + } + + if (fseek(bootloader_file, 0, SEEK_SET) != 0) { + perror_wrap("error: enroll_config(): fseek()"); + goto cleanup; + } + if (fwrite(bootloader, bootloader_size, 1, bootloader_file) != 1) { + perror_wrap("error: enroll_config(): fwrite()"); + goto cleanup; + } + + if (!quiet) { + fprintf(stderr, "Config file BLAKE2B successfully %s.\n", reset ? "reset" : "enrolled"); + } + ret = EXIT_SUCCESS; + +cleanup: + if (bootloader != NULL) { + free(bootloader); + } + if (bootloader_file != NULL) { + fclose(bootloader_file); + } + return ret; +} + +#define LIMINE_VERSION "%VERSION%" +#define LIMINE_COPYRIGHT "%COPYRIGHT%" + +static void version_usage(void) { + printf("usage: %s version [options...]\n", program_name); + printf("\n"); + printf(" --version-only Only print the version number without licensing info\n"); + printf(" and other distractions\n"); + printf("\n"); + printf(" --help | -h Display this help message\n"); + printf("\n"); +} + +static int version(int argc, char *argv[]) { + if (argc >= 2) { + if (strcmp(argv[1], "--help") == 0) { + version_usage(); + return EXIT_SUCCESS; + } else if (strcmp(argv[1], "--version-only") == 0) { + puts(LIMINE_VERSION); + return EXIT_SUCCESS; + } + } + + puts("Limine " LIMINE_VERSION); + puts(LIMINE_COPYRIGHT); + puts("Limine is distributed under the terms of the BSD-2-Clause license."); + puts("There is ABSOLUTELY NO WARRANTY, to the extent permitted by law."); + return EXIT_SUCCESS; +} + +static void general_usage(void) { + printf("usage: %s \n", program_name); + printf("\n"); + printf(" --print-datadir Print the directory containing the bootloader files\n"); + printf("\n"); + printf(" --version Print the Limine version (like the `version` command)\n"); + printf("\n"); + printf(" --help | -h Display this help message\n"); + printf("\n"); + printf("Commands: `help`, `version`, `bios-install`, `enroll-config`\n"); + printf("Use `--help` after specifying the command for command-specific help.\n"); +} + +static int print_datadir(void) { +#ifdef LIMINE_DATADIR + puts(LIMINE_DATADIR); + return EXIT_SUCCESS; +#else + fprintf(stderr, "error: Cannot print datadir for `limine` built standalone.\n"); + return EXIT_FAILURE; +#endif +} + +int main(int argc, char *argv[]) { + program_name = argv[0]; + + if (argc <= 1) { + general_usage(); + return EXIT_FAILURE; + } + + if (strcmp(argv[1], "help") == 0 + || strcmp(argv[1], "--help") == 0 + || strcmp(argv[1], "-h") == 0) { + general_usage(); + return EXIT_SUCCESS; + } else if (strcmp(argv[1], "bios-install") == 0) { +#ifndef LIMINE_NO_BIOS + return bios_install(argc - 1, &argv[1]); +#else + fprintf(stderr, "error: Limine has been compiled without BIOS support.\n"); + return EXIT_FAILURE; +#endif + } else if (strcmp(argv[1], "enroll-config") == 0) { + return enroll_config(argc - 1, &argv[1]); + } else if (strcmp(argv[1], "--print-datadir") == 0) { + return print_datadir(); + } else if (strcmp(argv[1], "version") == 0 + || strcmp(argv[1], "--version") == 0) { + return version(argc - 1, &argv[1]); + } + + general_usage(); + return EXIT_FAILURE; +} diff --git a/limine/logo.png b/limine/logo.png new file mode 100644 index 0000000..9083c1b Binary files /dev/null and b/limine/logo.png differ diff --git a/limine/man/man1/limine.1.in b/limine/man/man1/limine.1.in new file mode 100644 index 0000000..ba308af --- /dev/null +++ b/limine/man/man1/limine.1.in @@ -0,0 +1,13 @@ +.TH LIMINE 1 "version @PACKAGE_VERSION@" "@REGEN_DATE@" +.SH NAME +limine \- Multiplexer to several Limine-related utilities. +.SH SYNOPSIS +.B limine +.RI " " +.SH DESCRIPTION +\fBlimine\fR provides a series of Limine-related utilities condensed in a single executable. +.SH BUGS +Please report bugs via +.IR @PACKAGE_BUGREPORT@ . +.SH HOMEPAGE +.I @PACKAGE_URL@ diff --git a/limine/screenshot.png b/limine/screenshot.png new file mode 100644 index 0000000..2b7644e Binary files /dev/null and b/limine/screenshot.png differ diff --git a/limine/stage1/cd/bootsect.asm b/limine/stage1/cd/bootsect.asm new file mode 100644 index 0000000..6170f39 --- /dev/null +++ b/limine/stage1/cd/bootsect.asm @@ -0,0 +1,108 @@ +org 0x7c00 +bits 16 + +jmp skip_bpb +nop + +; El Torito Boot Information Table +; ↓ Set by mkisofs +times 8-($-$$) db 0 +boot_info: + bi_PVD dd 0 + bi_boot_LBA dd 0 + bi_boot_len dd 0 + bi_checksum dd 0 + bi_reserved times 40 db 0 + +times 90-($-$$) db 0 + +skip_bpb: + cli + cld + jmp 0x0000:.initialise_cs + .initialise_cs: + xor si, si + mov ds, si + mov es, si + mov ss, si + mov sp, 0x7c00 + sti + + ; int 13h? + mov ah, 0x41 + mov bx, 0x55aa + int 0x13 + jc err.0 + cmp bx, 0xaa55 + jne err.1 + + ; --- Load the decompressor --- + mov eax, dword [bi_boot_LBA] + add eax, 1 + mov ecx, stage2.fullsize / 2048 + ; DECOMPRESSOR_LOCATION = 0x70000 = 0x7000:0x0000 + push 0x7000 + pop es + xor bx, bx + call read_2k_sectors + jc err.2 + + ; Enable GDT + lgdt [gdt] + cli + mov eax, cr0 + or al, 1 + mov cr0, eax + + jmp 0x08:pmode + +err: + .2: + inc si + .1: + inc si + .0: + add si, '0' | (0x4f << 8) + + push 0xb800 + pop es + mov word [es:0], si + + sti + .h: hlt + jmp .h + +%include 'read_2k_sectors.asm' +%include '../gdt.asm' + +bits 32 +pmode: + mov eax, 0x10 + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + mov ss, ax + + ; Time to handle control over to the decompressor + push 2 + and edx, 0xff + push edx ; Boot drive + push stage2.size + push (stage2 - decompressor) + 0x70000 + call 0x70000 + +; Align stage2 to 2K ON DISK +times 2048-($-$$) db 0 +decompressor: +%strcat DECOMPRESSOR_PATH BUILDDIR, '/decompressor-build/decompressor.bin' +incbin DECOMPRESSOR_PATH + +align 16 +stage2: +%strcat STAGE2_PATH BUILDDIR, '/common-bios/stage2.bin.gz' +incbin STAGE2_PATH +.size: equ $ - stage2 + +times ((($-$$)+2047) & ~2047)-($-$$) db 0 +.fullsize: equ $ - decompressor diff --git a/limine/stage1/cd/read_2k_sectors.asm b/limine/stage1/cd/read_2k_sectors.asm new file mode 100644 index 0000000..14090fe --- /dev/null +++ b/limine/stage1/cd/read_2k_sectors.asm @@ -0,0 +1,35 @@ +bits 16 + +; --- Read sectors from disk --- +; IN: +; eax <- start LBA (2k sectors) +; cx <- number of 2k sectors +; dl <- drive number +; ds <- ZERO +; bx <- buffer offset +; es <- buffer segment + +; OUT: +; Carry if error + +align 8 +dapack: + dapack_size: db 0x10 + dapack_null: db 0x00 + dapack_nblocks: dw 0 + dapack_offset: dw 0 + dapack_segment: dw 0 + dapack_LBA: dq 0 + +read_2k_sectors: + pusha + mov dword [dapack_LBA], eax + mov word [dapack_nblocks], cx + mov word [dapack_offset], bx + mov word [dapack_segment], es + + mov ah, 0x42 + mov si, dapack + int 0x13 + popa + ret diff --git a/limine/stage1/gdt.asm b/limine/stage1/gdt.asm new file mode 100644 index 0000000..42c3f68 --- /dev/null +++ b/limine/stage1/gdt.asm @@ -0,0 +1,24 @@ +gdt: + dw .size - 1 + 8 ; GDT size + dd .start - 8 ; GDT start address + + .start: + ; 32-bit code + dw 0xffff ; Limit + dw 0x0000 ; Base (low 16 bits) + db 0x00 ; Base (mid 8 bits) + db 10011011b ; Access + db 11001111b ; Granularity + db 0x00 ; Base (high 8 bits) + + ; 32-bit data + dw 0xffff ; Limit + dw 0x0000 ; Base (low 16 bits) + db 0x00 ; Base (mid 8 bits) + db 10010011b ; Access + db 11001111b ; Granularity + db 0x00 ; Base (high 8 bits) + + .end: + + .size: equ .end - .start diff --git a/limine/stage1/hdd/bootsect.asm b/limine/stage1/hdd/bootsect.asm new file mode 100644 index 0000000..0d2b47e --- /dev/null +++ b/limine/stage1/hdd/bootsect.asm @@ -0,0 +1,153 @@ +org 0x7c00 +bits 16 + +start: + jmp .skip_bpb ; Workaround for some BIOSes that require this stub + nop + + ; Some BIOSes will do a funny and decide to overwrite bytes of code in + ; the section where a FAT BPB would be, potentially overwriting + ; bootsector code. + ; Avoid that by filling the BPB area with dummy values. + ; Some of the values have to be set to certain values in order + ; to boot on even quirkier machines. + ; Source: https://github.com/freebsd/freebsd-src/blob/82a21151cf1d7a3e9e95b9edbbf74ac10f386d6a/stand/i386/boot2/boot1.S + .bpb: + times 3-($-$$) db 0 + .bpb_oem_id: db "LIMINE " + .bpb_sector_size: dw 512 + .bpb_sects_per_cluster: db 0 + .bpb_reserved_sects: dw 0 + .bpb_fat_count: db 0 + .bpb_root_dir_entries: dw 0 + .bpb_sector_count: dw 0 + .bpb_media_type: db 0 + .bpb_sects_per_fat: dw 0 + .bpb_sects_per_track: dw 18 + .bpb_heads_count: dw 2 + .bpb_hidden_sects: dd 0 + .bpb_sector_count_big: dd 0 + .bpb_drive_num: db 0 + .bpb_reserved: db 0 + .bpb_signature: db 0 + .bpb_volume_id: dd 0 + .bpb_volume_label: db "LIMINE " + .bpb_filesystem_type: times 8 db 0 + + .skip_bpb: + cli + cld + jmp 0x0000:.initialise_cs + .initialise_cs: + xor si, si + mov ds, si + mov es, si + mov ss, si + mov sp, 0x7c00 + sti + + ; Limine isn't made for floppy disks, these are dead anyways. + ; So if the value the BIOS passed is <0x80, just assume it has passed + ; an incorrect value. + cmp dl, 0x80 + jb err.0 + ; Values above 0x8f are dubious so we assume we weren't booted properly + ; for those either + cmp dl, 0x8f + ja err.1 + + .continue: + ; Make sure int 13h extensions are supported + mov ah, 0x41 + mov bx, 0x55aa + int 0x13 + jc err.2 + cmp bx, 0xaa55 + jne err.3 + + push 0x7000 + pop es + mov di, stage2_loc + mov eax, dword [di] + mov ebp, dword [di+4] + xor bx, bx + mov ecx, 32256 ; 32KiB minus boot sector size + call read_sectors + jc err.4 + + lgdt [gdt] + + cli + + push dword 0 + mov ebp, 0x10 + + mov eax, cr0 + bts ax, 0 + mov cr0, eax + + jmp 0x08:vector + +times 0xda-($-$$) db 0 +times 6 db 0 + +; Includes + +%include 'disk.asm' +%include '../gdt.asm' + +err: + .4: + inc si + .3: + inc si + .2: + inc si + .1: + inc si + .0: + add si, '0' | (0x4f << 8) + + push 0xb800 + pop es + mov word [es:0], si + + sti + .h: hlt + jmp .h + +bits 32 +vector: + mov ds, ebp + mov es, ebp + mov fs, ebp + mov gs, ebp + mov ss, ebp + + and edx, 0xff + + push edx + + push stage2.size + push (stage2 - decompressor) + 0x70000 + + call 0x70000 + +times 0x1a4-($-$$) db 0 +stage2_loc: dq 0 + +times 0x1b8-($-$$) db 0 +times 510-($-$$) db 0 +dw 0xaa55 + +; ********************* Stage 2 ********************* + +decompressor: +%strcat DECOMPRESSOR_PATH BUILDDIR, '/decompressor-build/decompressor.bin' +incbin DECOMPRESSOR_PATH + +align 16 +stage2: +%strcat STAGE2_PATH BUILDDIR, '/common-bios/stage2.bin.gz' +incbin STAGE2_PATH +.size: equ $ - stage2 diff --git a/limine/stage1/hdd/disk.asm b/limine/stage1/hdd/disk.asm new file mode 100644 index 0000000..05a7245 --- /dev/null +++ b/limine/stage1/hdd/disk.asm @@ -0,0 +1,90 @@ +; ***************************** +; Reads bytes from disk +; ***************************** + +; IN: +; EAX = Start address to load low 32 +; EBP = Start address to load high 32 +; DL = Drive number +; ES = Buffer segment +; BX = Buffer offset +; ECX = Byte count + +; OUT: +; Carry if error + +read_sectors: + pusha + + mov si, .da_struct + + mov dword [si], 0x00010010 + mov word [si+4], bx + mov word [si+6], es + + push dx + push si + + push eax + push ebp + + ; Get bytes per sector + mov ah, 0x48 + mov si, .drive_params + mov word [si], 30 ; buf_size + int 0x13 + jc .fail + movzx ebp, word [si+24] ; bytes_per_sect + + ; ECX byte count to CX sector count + xchg ax, cx + shr ecx, 16 + mov dx, cx + xor cx, cx + div bp + test dx, dx + setnz cl + add cx, ax + + pop edx + pop eax + + pop si + + ; EDX:EAX byte address to 64-bit LBA sector + push eax + xchg eax, edx + xor edx, edx + div ebp + xchg ebx, eax + pop eax + div ebp + mov dword [si+8], eax + mov dword [si+12], ebx + + pop dx + + .loop: + mov ah, 0x42 + + clc + int 0x13 + jc .done + + add word [si+4], bp + add dword [si+8], 1 + adc dword [si+12], 0 + + loop .loop + + jmp short .done + + .fail: + add sp, 12 + stc + .done: + popa + ret + + .da_struct: equ 0x8000 + .drive_params: equ 0x8010 diff --git a/limine/stage1/pxe/bootsect.asm b/limine/stage1/pxe/bootsect.asm new file mode 100644 index 0000000..c1b96fb --- /dev/null +++ b/limine/stage1/pxe/bootsect.asm @@ -0,0 +1,60 @@ +org 0x7c00 +bits 16 + +start: + cli + cld + jmp 0x0000:.initialise_cs + .initialise_cs: + xor ax, ax + mov ds, ax + mov es, ax + mov ss, ax + mov sp, 0x7c00 + + lgdt [gdt] + + mov eax, cr0 + bts ax, 0 + mov cr0, eax + + jmp 0x08:.mode32 + bits 32 + .mode32: + mov eax, 0x10 + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + mov ss, ax + + push 0x1 + and edx, 0xff + push edx + + push stage2.size + push (stage2 - decompressor) + 0x70000 + + mov esi, decompressor + mov edi, 0x70000 + mov ecx, stage2.fullsize + rep movsb + + call 0x70000 + +; Includes + +%include '../gdt.asm' + +; ********************* Stage 2 ********************* + +decompressor: +%strcat DECOMPRESSOR_PATH BUILDDIR, '/decompressor-build/decompressor.bin' +incbin DECOMPRESSOR_PATH + +align 16 +stage2: +%strcat STAGE2_PATH BUILDDIR, '/common-bios/stage2.bin.gz' +incbin STAGE2_PATH +.size: equ $ - stage2 +.fullsize: equ $ - decompressor diff --git a/limine/test.mk b/limine/test.mk new file mode 100644 index 0000000..fb20c4f --- /dev/null +++ b/limine/test.mk @@ -0,0 +1,278 @@ +.PHONY: test-clean +test-clean: + $(MAKE) -C test -f test.mk clean + rm -rf test_image test.hdd test.iso + +edk2-ovmf: + curl -L https://github.com/osdev0/edk2-ovmf-nightly/releases/latest/download/edk2-ovmf.tar.gz | gunzip | tar -xf - + +.PHONY: test.hdd +test.hdd: + rm -f test.hdd + dd if=/dev/zero bs=1M count=0 seek=64 of=test.hdd + PATH=$$PATH:/usr/sbin:/sbin parted -s test.hdd mklabel msdos + PATH=$$PATH:/usr/sbin:/sbin parted -s test.hdd mkpart primary 2048s 100% + +.PHONY: mbrtest.hdd +mbrtest.hdd: + rm -f mbrtest.hdd + dd if=/dev/zero bs=1M count=0 seek=64 of=mbrtest.hdd + echo -e "o\nn\np\n1\n2048\n\nt\n6\na\nw\n" | fdisk mbrtest.hdd -H 16 -S 63 + +.PHONY: fat12-test +fat12-test: + $(MAKE) test-clean + $(MAKE) test.hdd + $(MAKE) limine-bios + $(MAKE) limine + $(MAKE) -C test -f test.mk ARCH=x86 + rm -rf test_image/ + mkdir test_image + sudo losetup -Pf --show test.hdd > loopback_dev + sudo partprobe `cat loopback_dev` + sudo mkfs.fat -F 12 `cat loopback_dev`p1 + sudo mount `cat loopback_dev`p1 test_image + sudo mkdir test_image/boot + sudo cp -rv $(BINDIR)/* test_image/boot/ + sudo cp -rv test/* test_image/boot/ + sync + sudo umount test_image/ + sudo losetup -d `cat loopback_dev` + rm -rf test_image loopback_dev + $(BINDIR)/limine bios-install test.hdd + qemu-system-x86_64 -net none -smp 4 -hda test.hdd -debugcon stdio + +.PHONY: fat16-test +fat16-test: + $(MAKE) test-clean + $(MAKE) test.hdd + $(MAKE) limine-bios + $(MAKE) limine + $(MAKE) -C test -f test.mk ARCH=x86 + rm -rf test_image/ + mkdir test_image + sudo losetup -Pf --show test.hdd > loopback_dev + sudo partprobe `cat loopback_dev` + sudo mkfs.fat -F 16 `cat loopback_dev`p1 + sudo mount `cat loopback_dev`p1 test_image + sudo mkdir test_image/boot + sudo cp -rv $(BINDIR)/* test_image/boot/ + sudo cp -rv test/* test_image/boot/ + sync + sudo umount test_image/ + sudo losetup -d `cat loopback_dev` + rm -rf test_image loopback_dev + $(BINDIR)/limine bios-install test.hdd + qemu-system-x86_64 -net none -smp 4 -hda test.hdd -debugcon stdio + +.PHONY: legacy-fat16-test +legacy-fat16-test: + $(MAKE) test-clean + $(MAKE) mbrtest.hdd + fdisk -l mbrtest.hdd + $(MAKE) limine-bios + $(MAKE) limine + $(MAKE) -C test -f test.mk ARCH=x86 + rm -rf test_image/ + mkdir test_image + sudo losetup -Pf --show mbrtest.hdd > loopback_dev + sudo partprobe `cat loopback_dev` + sudo mkfs.fat -F 16 `cat loopback_dev`p1 + sudo mount `cat loopback_dev`p1 test_image + sudo mkdir test_image/boot + sudo cp -rv $(BINDIR)/* test_image/boot/ + sudo cp -rv test/* test_image/boot/ + sync + sudo umount test_image/ + sudo losetup -d `cat loopback_dev` + rm -rf test_image loopback_dev + $(BINDIR)/limine bios-install mbrtest.hdd + qemu-system-i386 -cpu pentium2 -m 16M -M isapc -net none -hda mbrtest.hdd -debugcon stdio + +.PHONY: fat32-test +fat32-test: + $(MAKE) test-clean + $(MAKE) test.hdd + $(MAKE) limine-bios + $(MAKE) limine + $(MAKE) -C test -f test.mk ARCH=x86 + rm -rf test_image/ + mkdir test_image + sudo losetup -Pf --show test.hdd > loopback_dev + sudo partprobe `cat loopback_dev` + sudo mkfs.fat -F 32 `cat loopback_dev`p1 + sudo mount `cat loopback_dev`p1 test_image + sudo mkdir test_image/boot + sudo cp -rv $(BINDIR)/* test_image/boot/ + sudo cp -rv test/* test_image/boot/ + sync + sudo umount test_image/ + sudo losetup -d `cat loopback_dev` + rm -rf test_image loopback_dev + $(BINDIR)/limine bios-install test.hdd + qemu-system-x86_64 -net none -smp 4 -hda test.hdd -debugcon stdio + +.PHONY: iso9660-test +iso9660-test: + $(MAKE) test-clean + $(MAKE) test.hdd + $(MAKE) limine-bios + $(MAKE) -C test -f test.mk ARCH=x86 + rm -rf test_image/ + $(MKDIR_P) test_image/boot + cp -rv $(BINDIR)/* test_image/boot/ + cp -rv test/* test_image/boot/ + xorriso -as mkisofs -b boot/limine-bios-cd.bin -no-emul-boot -boot-load-size 4 -boot-info-table test_image/ -o test.iso + qemu-system-x86_64 -net none -smp 4 -cdrom test.iso -debugcon stdio + +.PHONY: full-hybrid-test +full-hybrid-test: + $(MAKE) edk2-ovmf + $(MAKE) test-clean + $(MAKE) all + $(MAKE) -C test -f test.mk ARCH=x86 + rm -rf test_image/ + $(MKDIR_P) test_image/boot + cp -rv $(BINDIR)/* test_image/boot/ + cp -rv test/* test_image/boot/ + $(MKDIR_P) test_image/EFI/BOOT + cp -v $(BINDIR)/BOOT*.EFI test_image/EFI/BOOT/ + xorriso -as mkisofs -R -r -J -b boot/limine-bios-cd.bin -no-emul-boot -boot-load-size 4 -boot-info-table -hfsplus -apm-block-size 2048 --efi-boot boot/limine-uefi-cd.bin -efi-boot-part --efi-boot-image --protective-msdos-label test_image/ -o test.iso + $(BINDIR)/limine bios-install test.iso + qemu-system-x86_64 -m 512M -M q35 -drive if=pflash,unit=0,format=raw,file=edk2-ovmf/ovmf-code-x86_64.fd,readonly=on -net none -smp 4 -cdrom test.iso -debugcon stdio + qemu-system-x86_64 -m 512M -M q35 -drive if=pflash,unit=0,format=raw,file=edk2-ovmf/ovmf-code-x86_64.fd,readonly=on -net none -smp 4 -hda test.iso -debugcon stdio + qemu-system-x86_64 -m 512M -M q35 -drive if=pflash,unit=0,format=raw,file=edk2-ovmf/ovmf-code-ia32.fd,readonly=on -net none -smp 4 -cdrom test.iso -debugcon stdio + qemu-system-x86_64 -m 512M -M q35 -drive if=pflash,unit=0,format=raw,file=edk2-ovmf/ovmf-code-ia32.fd,readonly=on -net none -smp 4 -hda test.iso -debugcon stdio + qemu-system-x86_64 -m 512M -M q35 -net none -smp 4 -cdrom test.iso -debugcon stdio + qemu-system-x86_64 -m 512M -M q35 -net none -smp 4 -hda test.iso -debugcon stdio + +.PHONY: pxe-test +pxe-test: + $(MAKE) test-clean + $(MAKE) limine-bios + $(MAKE) -C test -f test.mk ARCH=x86 + rm -rf test_image/ + $(MKDIR_P) test_image/boot + cp -rv $(BINDIR)/* test_image/boot/ + cp -rv test/* test_image/boot/ + qemu-system-x86_64 -smp 4 -netdev user,id=n0,tftp=./test_image,bootfile=boot/limine-bios-pxe.bin -device rtl8139,netdev=n0,mac=00:00:00:11:11:11 -debugcon stdio + +.PHONY: uefi-x86-64-test +uefi-x86-64-test: + $(MAKE) edk2-ovmf + $(MAKE) test-clean + $(MAKE) test.hdd + $(MAKE) limine-uefi-x86-64 + $(MAKE) -C test -f test.mk ARCH=x86 + rm -rf test_image/ + mkdir test_image + sudo losetup -Pf --show test.hdd > loopback_dev + sudo partprobe `cat loopback_dev` + sudo mkfs.fat -F 32 `cat loopback_dev`p1 + sudo mount `cat loopback_dev`p1 test_image + sudo mkdir test_image/boot + sudo cp -rv $(BINDIR)/* test_image/boot/ + sudo cp -rv test/* test_image/boot/ + sudo $(MKDIR_P) test_image/EFI/BOOT + sudo cp $(BINDIR)/BOOTX64.EFI test_image/EFI/BOOT/ + sync + sudo umount test_image/ + sudo losetup -d `cat loopback_dev` + rm -rf test_image loopback_dev + qemu-system-x86_64 -m 512M -M q35 -drive if=pflash,unit=0,format=raw,file=edk2-ovmf/ovmf-code-x86_64.fd,readonly=on -net none -smp 4 -hda test.hdd -debugcon stdio + +.PHONY: uefi-aa64-test +uefi-aa64-test: + $(MAKE) edk2-ovmf + $(MAKE) test-clean + $(MAKE) test.hdd + $(MAKE) limine-uefi-aarch64 + $(MAKE) -C test -f test.mk ARCH=aarch64 + rm -rf test_image/ + mkdir test_image + sudo losetup -Pf --show test.hdd > loopback_dev + sudo partprobe `cat loopback_dev` + sudo mkfs.fat -F 32 `cat loopback_dev`p1 + sudo mount `cat loopback_dev`p1 test_image + sudo mkdir test_image/boot + sudo cp -rv $(BINDIR)/* test_image/boot/ + sudo cp -rv test/* test_image/boot/ + sudo $(MKDIR_P) test_image/EFI/BOOT + sudo cp $(BINDIR)/BOOTAA64.EFI test_image/EFI/BOOT/ + sync + sudo umount test_image/ + sudo losetup -d `cat loopback_dev` + rm -rf test_image loopback_dev + qemu-system-aarch64 -m 512M -M virt -cpu cortex-a72 -drive if=pflash,unit=0,format=raw,file=edk2-ovmf/ovmf-code-aarch64.fd,readonly=on -net none -smp 4 -device ramfb -device qemu-xhci -device usb-kbd -hda test.hdd -serial stdio + +.PHONY: uefi-rv64-test +uefi-rv64-test: + $(MAKE) edk2-ovmf + $(MAKE) test-clean + $(MAKE) test.hdd + $(MAKE) limine-uefi-riscv64 + $(MAKE) -C test -f test.mk ARCH=riscv64 + rm -rf test_image/ + mkdir test_image + sudo losetup -Pf --show test.hdd > loopback_dev + sudo partprobe `cat loopback_dev` + sudo mkfs.fat -F 32 `cat loopback_dev`p1 + sudo mount `cat loopback_dev`p1 test_image + sudo mkdir test_image/boot + sudo cp -rv $(BINDIR)/* test_image/boot/ + sudo cp -rv test/* test_image/boot/ + sudo $(MKDIR_P) test_image/EFI/BOOT + sudo cp $(BINDIR)/BOOTRISCV64.EFI test_image/EFI/BOOT/ + sync + sudo umount test_image/ + sudo losetup -d `cat loopback_dev` + rm -rf test_image loopback_dev + qemu-system-riscv64 -m 512M -M virt -cpu rv64 -drive if=pflash,unit=0,format=raw,file=edk2-ovmf/ovmf-code-riscv64.fd,readonly=on -net none -smp 4 -device ramfb -device qemu-xhci -device usb-kbd -hda test.hdd -serial stdio + +.PHONY: uefi-loongarch64-test +uefi-loongarch64-test: + $(MAKE) edk2-ovmf + $(MAKE) test-clean + $(MAKE) test.hdd + $(MAKE) limine-uefi-loongarch64 + $(MAKE) -C test -f test.mk ARCH=loongarch64 + rm -rf test_image/ + mkdir test_image + sudo losetup -Pf --show test.hdd > loopback_dev + sudo partprobe `cat loopback_dev` + sudo mkfs.fat -F 32 `cat loopback_dev`p1 + sudo mount `cat loopback_dev`p1 test_image + sudo mkdir test_image/boot + sudo cp -rv $(BINDIR)/* test_image/boot/ + sudo cp -rv test/* test_image/boot/ + sudo $(MKDIR_P) test_image/EFI/BOOT + sudo cp $(BINDIR)/BOOTLOONGARCH64.EFI test_image/EFI/BOOT/ + sync + sudo umount test_image/ + sudo losetup -d `cat loopback_dev` + rm -rf test_image loopback_dev + qemu-system-loongarch64 -m 1G -net none -M virt -cpu la464 -device ramfb -device qemu-xhci -device usb-kbd -drive if=pflash,unit=0,format=raw,file=edk2-ovmf/ovmf-code-loongarch64.fd,readonly=on -hda test.hdd -serial stdio + +.PHONY: uefi-ia32-test +uefi-ia32-test: + $(MAKE) edk2-ovmf + $(MAKE) test-clean + $(MAKE) test.hdd + $(MAKE) limine-uefi-ia32 + $(MAKE) -C test -f test.mk ARCH=x86 + rm -rf test_image/ + mkdir test_image + sudo losetup -Pf --show test.hdd > loopback_dev + sudo partprobe `cat loopback_dev` + sudo mkfs.fat -F 32 `cat loopback_dev`p1 + sudo mount `cat loopback_dev`p1 test_image + sudo mkdir test_image/boot + sudo cp -rv $(BINDIR)/* test_image/boot/ + sudo cp -rv test/* test_image/boot/ + sudo $(MKDIR_P) test_image/EFI/BOOT + sudo cp $(BINDIR)/BOOTIA32.EFI test_image/EFI/BOOT/ + sync + sudo umount test_image/ + sudo losetup -d `cat loopback_dev` + rm -rf test_image loopback_dev + qemu-system-x86_64 -m 512M -M q35 -drive if=pflash,unit=0,format=raw,file=edk2-ovmf/ovmf-code-ia32.fd,readonly=on -net none -smp 4 -hda test.hdd -debugcon stdio diff --git a/limine/test/.gitignore b/limine/test/.gitignore new file mode 100644 index 0000000..4ce83ce --- /dev/null +++ b/limine/test/.gitignore @@ -0,0 +1,2 @@ +test.o +test.elf diff --git a/limine/test/bg.jpg b/limine/test/bg.jpg new file mode 100644 index 0000000..d7ae60f Binary files /dev/null and b/limine/test/bg.jpg differ diff --git a/limine/test/device_tree.dts b/limine/test/device_tree.dts new file mode 100644 index 0000000..8e981e9 --- /dev/null +++ b/limine/test/device_tree.dts @@ -0,0 +1,16 @@ +// Example device tree. + +/dts-v1/; + +/ { + soc { + limine_node: limine@deadbeef { + reg = <0xdeadbeef 0x1000>; + label = "KANKER"; + }; + fake_memory: memory@ffff0000 { + reg = <0xffff0000 0xffff>; + label = "This node will be removed by Limine."; + }; + }; +}; diff --git a/limine/test/e9print.c b/limine/test/e9print.c new file mode 100644 index 0000000..88d3d6a --- /dev/null +++ b/limine/test/e9print.c @@ -0,0 +1,98 @@ +#include +#include +#include + +#if defined (_LIMINE_PROTO) +#include +extern struct flanterm_context *ft_ctx; +#endif + +static const char CONVERSION_TABLE[] = "0123456789abcdef"; + +void e9_putc(char c) { +#if defined (__x86_64__) || defined (__i386__) + __asm__ __volatile__ ("outb %0, %1" :: "a" (c), "Nd" (0xe9) : "memory"); +#endif +#if defined (_LIMINE_PROTO) + if (ft_ctx != NULL) { + if (c == '\n') { + flanterm_write(ft_ctx, "\r", 1); + } + flanterm_write(ft_ctx, &c, 1); + } +#endif +} + +void e9_print(const char *msg) { + for (size_t i = 0; msg[i]; i++) { + e9_putc(msg[i]); + } +} + +void e9_puts(const char *msg) { + e9_print(msg); + e9_putc('\n'); +} + +static void e9_printhex(size_t num) { + int i; + char buf[17]; + + if (!num) { + e9_print("0x0"); + return; + } + + buf[16] = 0; + + for (i = 15; num; i--) { + buf[i] = CONVERSION_TABLE[num % 16]; + num /= 16; + } + + i++; + e9_print("0x"); + e9_print(&buf[i]); +} + +static void e9_printdec(size_t num) { + int i; + char buf[21] = {0}; + + if (!num) { + e9_putc('0'); + return; + } + + for (i = 19; num; i--) { + buf[i] = (num % 10) + 0x30; + num = num / 10; + } + + i++; + e9_print(buf + i); +} + +void e9_printf(const char *format, ...) { + va_list argp; + va_start(argp, format); + + while (*format != '\0') { + if (*format == '%') { + format++; + if (*format == 'x') { + e9_printhex(va_arg(argp, size_t)); + } else if (*format == 'd') { + e9_printdec(va_arg(argp, size_t)); + } else if (*format == 's') { + e9_print(va_arg(argp, char*)); + } + } else { + e9_putc(*format); + } + format++; + } + + e9_putc('\n'); + va_end(argp); +} diff --git a/limine/test/e9print.h b/limine/test/e9print.h new file mode 100644 index 0000000..15aedf0 --- /dev/null +++ b/limine/test/e9print.h @@ -0,0 +1,9 @@ +#pragma once + +#include +#include + +void e9_putc(char c); +void e9_print(const char *msg); +void e9_puts(const char *msg); +void e9_printf(const char *format, ...); diff --git a/limine/test/limine.c b/limine/test/limine.c new file mode 100644 index 0000000..b84e7b7 --- /dev/null +++ b/limine/test/limine.c @@ -0,0 +1,612 @@ +#include +#include +#include +#include +#include +#include +#include + +__attribute__((section(".limine_requests"))) +static volatile uint64_t limine_base_revision[] = LIMINE_BASE_REVISION(5); + +static void limine_main(void); + +__attribute__((used, section(".limine_requests_start_marker"))) +static volatile uint64_t limine_requests_start_marker[] = LIMINE_REQUESTS_START_MARKER; + +__attribute__((used, section(".limine_requests"))) +static volatile struct limine_entry_point_request entry_point_request = { + .id = LIMINE_ENTRY_POINT_REQUEST_ID, + .revision = 0, .response = NULL, + + .entry = limine_main +}; + +__attribute__((section(".limine_requests"))) +static volatile struct limine_framebuffer_request framebuffer_request = { + .id = LIMINE_FRAMEBUFFER_REQUEST_ID, + .revision = 0, .response = NULL +}; + +__attribute__((section(".limine_requests"))) +static volatile struct limine_bootloader_info_request bootloader_info_request = { + .id = LIMINE_BOOTLOADER_INFO_REQUEST_ID, + .revision = 0, .response = NULL +}; + +__attribute__((section(".limine_requests"))) +static volatile struct limine_executable_cmdline_request executable_cmdline_request = { + .id = LIMINE_EXECUTABLE_CMDLINE_REQUEST_ID, + .revision = 0, .response = NULL +}; + +__attribute__((section(".limine_requests"))) +static volatile struct limine_firmware_type_request firmware_type_request = { + .id = LIMINE_FIRMWARE_TYPE_REQUEST_ID, + .revision = 0, .response = NULL +}; + +__attribute__((section(".limine_requests"))) +static volatile struct limine_hhdm_request hhdm_request = { + .id = LIMINE_HHDM_REQUEST_ID, + .revision = 0, .response = NULL +}; + +__attribute__((section(".limine_requests"))) +static volatile struct limine_memmap_request memmap_request = { + .id = LIMINE_MEMMAP_REQUEST_ID, + .revision = 0, .response = NULL +}; + +__attribute__((section(".limine_requests"))) +static volatile struct limine_executable_file_request exec_file_request = { + .id = LIMINE_EXECUTABLE_FILE_REQUEST_ID, + .revision = 0, .response = NULL +}; + +struct limine_internal_module internal_module1 = { + .path = "/boot/test.elf", + .string = "First internal module" +}; + +struct limine_internal_module internal_module2 = { + .path = "test.elf", + .string = "Second internal module" +}; + +struct limine_internal_module internal_module3 = { + .path = "./limine.conf", + .string = "Third internal module" +}; + +struct limine_internal_module *internal_modules[] = { + &internal_module1, + &internal_module2, + &internal_module3 +}; + +__attribute__((section(".limine_requests"))) +static volatile struct limine_module_request module_request = { + .id = LIMINE_MODULE_REQUEST_ID, + .revision = 1, .response = NULL, + + .internal_module_count = 3, + .internal_modules = internal_modules +}; + +__attribute__((section(".limine_requests"))) +static volatile struct limine_rsdp_request rsdp_request = { + .id = LIMINE_RSDP_REQUEST_ID, + .revision = 0, .response = NULL +}; + +__attribute__((section(".limine_requests"))) +static volatile struct limine_smbios_request smbios_request = { + .id = LIMINE_SMBIOS_REQUEST_ID, + .revision = 0, .response = NULL +}; + +__attribute__((section(".limine_requests"))) +static volatile struct limine_efi_system_table_request est_request = { + .id = LIMINE_EFI_SYSTEM_TABLE_REQUEST_ID, + .revision = 0, .response = NULL +}; + +__attribute__((section(".limine_requests"))) +static volatile struct limine_efi_memmap_request efi_memmap_request = { + .id = LIMINE_EFI_MEMMAP_REQUEST_ID, + .revision = 0, .response = NULL +}; + +__attribute__((section(".limine_requests"))) +static volatile struct limine_date_at_boot_request date_at_boot_request = { + .id = LIMINE_DATE_AT_BOOT_REQUEST_ID, + .revision = 0, .response = NULL +}; + +__attribute__((section(".limine_requests"))) +static volatile struct limine_executable_address_request executable_address_request = { + .id = LIMINE_EXECUTABLE_ADDRESS_REQUEST_ID, + .revision = 0, .response = NULL +}; + +#ifndef __loongarch__ +__attribute__((section(".limine_requests"))) +static volatile struct limine_mp_request _mp_request = { + .id = LIMINE_MP_REQUEST_ID, + .revision = 0, .response = NULL +}; +#endif + +__attribute__((section(".limine_requests"))) +static volatile struct limine_dtb_request _dtb_request = { + .id = LIMINE_DTB_REQUEST_ID, + .revision = 0, .response = NULL +}; + +__attribute__((section(".limine_requests"))) +static volatile struct limine_paging_mode_request _pm_request = { + .id = LIMINE_PAGING_MODE_REQUEST_ID, + .revision = 1, .response = NULL, +#if defined (__x86_64__) + .mode = LIMINE_PAGING_MODE_X86_64_5LVL, + .max_mode = LIMINE_PAGING_MODE_X86_64_5LVL, + .min_mode = LIMINE_PAGING_MODE_X86_64_MIN +#elif defined (__aarch64__) + .mode = LIMINE_PAGING_MODE_AARCH64_5LVL, + .max_mode = LIMINE_PAGING_MODE_AARCH64_5LVL, + .min_mode = LIMINE_PAGING_MODE_AARCH64_MIN +#elif defined (__riscv) + .mode = LIMINE_PAGING_MODE_RISCV_SV57, + .max_mode = LIMINE_PAGING_MODE_RISCV_SV57, + .min_mode = LIMINE_PAGING_MODE_RISCV_MIN, +#elif defined (__loongarch__) + .mode = LIMINE_PAGING_MODE_LOONGARCH_DEFAULT, + .max_mode = LIMINE_PAGING_MODE_LOONGARCH_DEFAULT, + .min_mode = LIMINE_PAGING_MODE_LOONGARCH_MIN +#endif +}; + +#ifdef __riscv +__attribute__((section(".limine_requests"))) +static volatile struct limine_riscv_bsp_hartid_request _bsp_request = { + .id = LIMINE_RISCV_BSP_HARTID_REQUEST_ID, + .revision = 0, .response = NULL, +}; +#endif + +__attribute__((section(".limine_requests"))) +static volatile struct limine_bootloader_performance_request _perf_request = { + .id = LIMINE_BOOTLOADER_PERFORMANCE_REQUEST_ID, + .revision = 0, .response = NULL, +}; + +__attribute__((used, section(".limine_requests_end_marker"))) +static volatile uint64_t limine_requests_end_marker[] = LIMINE_REQUESTS_END_MARKER; + +static char *get_memmap_type(uint64_t type) { + switch (type) { + case LIMINE_MEMMAP_USABLE: + return "Usable"; + case LIMINE_MEMMAP_RESERVED: + return "Reserved"; + case LIMINE_MEMMAP_RESERVED_MAPPED: + return "Reserved (Mapped)"; + case LIMINE_MEMMAP_ACPI_RECLAIMABLE: + return "ACPI reclaimable"; + case LIMINE_MEMMAP_ACPI_NVS: + return "ACPI NVS"; + case LIMINE_MEMMAP_BAD_MEMORY: + return "Bad memory"; + case LIMINE_MEMMAP_BOOTLOADER_RECLAIMABLE: + return "Bootloader reclaimable"; + case LIMINE_MEMMAP_EXECUTABLE_AND_MODULES: + return "Executable and modules"; + case LIMINE_MEMMAP_FRAMEBUFFER: + return "Framebuffer"; + default: + return "???"; + } +} + +static char *firmware_type_str(uint64_t t) { + switch (t) { + case LIMINE_FIRMWARE_TYPE_X86BIOS: + return "x86 BIOS"; + case LIMINE_FIRMWARE_TYPE_EFI32: + return "32-bit EFI"; + case LIMINE_FIRMWARE_TYPE_EFI64: + return "64-bit EFI"; + default: + return "???"; + } +} + +static void print_file(struct limine_file *file) { + e9_printf("File->Revision: %d", file->revision); + e9_printf("File->Address: %x", file->address); + e9_printf("File->Size: %x", file->size); + e9_printf("File->Path: %s", file->path); + e9_printf("File->String: %s", file->string); + e9_printf("File->MediaType: %d", file->media_type); + e9_printf("File->PartIndex: %d", file->partition_index); + e9_printf("File->TFTPIP: %d.%d.%d.%d", + (file->tftp_ip & (0xff << 0)) >> 0, + (file->tftp_ip & (0xff << 8)) >> 8, + (file->tftp_ip & (0xff << 16)) >> 16, + (file->tftp_ip & (0xff << 24)) >> 24); + e9_printf("File->TFTPPort: %d", file->tftp_port); + e9_printf("File->MBRDiskId: %x", file->mbr_disk_id); + e9_printf("File->GPTDiskUUID: %x-%x-%x-%x", + file->gpt_disk_uuid.a, + file->gpt_disk_uuid.b, + file->gpt_disk_uuid.c, + *(uint64_t *)file->gpt_disk_uuid.d); + e9_printf("File->GPTPartUUID: %x-%x-%x-%x", + file->gpt_part_uuid.a, + file->gpt_part_uuid.b, + file->gpt_part_uuid.c, + *(uint64_t *)file->gpt_part_uuid.d); + e9_printf("File->PartUUID: %x-%x-%x-%x", + file->part_uuid.a, + file->part_uuid.b, + file->part_uuid.c, + *(uint64_t *)file->part_uuid.d); +} + +uint32_t ctr = 0; + +void ap_entry(struct limine_mp_info *info) { + e9_printf("Hello from AP!"); + +#if defined (__x86_64__) + e9_printf("My LAPIC ID: %x", info->lapic_id); +#elif defined (__aarch64__) + e9_printf("My MPIDR: %x", info->mpidr); +#elif defined (__riscv) + e9_printf("My Hart ID: %x", info->hartid); +#elif defined (__loongarch__) + (void)info; +#endif + + __atomic_fetch_add(&ctr, 1, __ATOMIC_SEQ_CST); + + while (1); +} + +#define FEAT_START do { +#define FEAT_END } while (0); + +extern char executable_start[]; + +struct flanterm_context *ft_ctx = NULL; + +static void limine_main(void) { + e9_printf("\nWe're alive"); + + if (LIMINE_LOADED_BASE_REVISION_VALID(limine_base_revision) == true) { + e9_printf("Bootloader has loaded us using base revision %d", + LIMINE_LOADED_BASE_REVISION(limine_base_revision)); + } + + if (LIMINE_BASE_REVISION_SUPPORTED(limine_base_revision) == false) { + e9_printf("Limine base revision not supported"); + for (;;); + } + + e9_printf(""); + + struct limine_framebuffer *fb = framebuffer_request.response->framebuffers[0]; + + ft_ctx = flanterm_fb_init( + NULL, + NULL, + fb->address, fb->width, fb->height, fb->pitch, + fb->red_mask_size, fb->red_mask_shift, + fb->green_mask_size, fb->green_mask_shift, + fb->blue_mask_size, fb->blue_mask_shift, + NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, 0, 0, 1, + 0, 0, + 0, + FLANTERM_FB_ROTATE_0 + ); + + uint64_t executable_slide = (uint64_t)executable_start - 0xffffffff80000000; + + e9_printf("Executable start: %x", executable_start); + e9_printf("Executable slide: %x", executable_slide); + +FEAT_START + e9_printf(""); + if (bootloader_info_request.response == NULL) { + e9_printf("Bootloader info not passed"); + break; + } + struct limine_bootloader_info_response *bootloader_info_response = bootloader_info_request.response; + e9_printf("Bootloader info feature, revision %d", bootloader_info_response->revision); + e9_printf("Bootloader name: %s", bootloader_info_response->name); + e9_printf("Bootloader version: %s", bootloader_info_response->version); +FEAT_END + +FEAT_START + e9_printf(""); + if (executable_cmdline_request.response == NULL) { + e9_printf("Executable command line not passed"); + break; + } + struct limine_executable_cmdline_response *executable_cmdline_response = executable_cmdline_request.response; + e9_printf("Executable command line feature, revision %d", executable_cmdline_response->revision); + e9_printf("Command line: %s", executable_cmdline_response->cmdline); +FEAT_END + +FEAT_START + e9_printf(""); + if (firmware_type_request.response == NULL) { + e9_printf("Firmware type not passed"); + break; + } + struct limine_firmware_type_response *firmware_type_response = firmware_type_request.response; + e9_printf("Firmware type feature, revision %d", firmware_type_response->revision); + e9_printf("Firmware type: %s", firmware_type_str(firmware_type_response->firmware_type)); +FEAT_END + +FEAT_START + e9_printf(""); + if (executable_address_request.response == NULL) { + e9_printf("Executable address not passed"); + break; + } + struct limine_executable_address_response *exec_addr_response = executable_address_request.response; + e9_printf("Executable address feature, revision %d", exec_addr_response->revision); + e9_printf("Physical base: %x", exec_addr_response->physical_base); + e9_printf("Virtual base: %x", exec_addr_response->virtual_base); +FEAT_END + +FEAT_START + e9_printf(""); + if (hhdm_request.response == NULL) { + e9_printf("HHDM not passed"); + break; + } + struct limine_hhdm_response *hhdm_response = hhdm_request.response; + e9_printf("HHDM feature, revision %d", hhdm_response->revision); + e9_printf("Higher half direct map at: %x", hhdm_response->offset); +FEAT_END + +FEAT_START + e9_printf(""); + if (memmap_request.response == NULL) { + e9_printf("Memory map not passed"); + break; + } + struct limine_memmap_response *memmap_response = memmap_request.response; + e9_printf("Memory map feature, revision %d", memmap_response->revision); + e9_printf("%d memory map entries", memmap_response->entry_count); + for (size_t i = 0; i < memmap_response->entry_count; i++) { + struct limine_memmap_entry *e = memmap_response->entries[i]; + e9_printf("%x->%x %s", e->base, e->base + e->length, get_memmap_type(e->type)); + } +FEAT_END + +FEAT_START + e9_printf(""); + if (framebuffer_request.response == NULL) { + e9_printf("Framebuffer not passed"); + break; + } + struct limine_framebuffer_response *fb_response = framebuffer_request.response; + e9_printf("Framebuffers feature, revision %d", fb_response->revision); + e9_printf("%d framebuffer(s)", fb_response->framebuffer_count); + for (size_t i = 0; i < fb_response->framebuffer_count; i++) { + struct limine_framebuffer *fb = fb_response->framebuffers[i]; + e9_printf("Address: %x", fb->address); + e9_printf("Width: %d", fb->width); + e9_printf("Height: %d", fb->height); + e9_printf("Pitch: %d", fb->pitch); + e9_printf("BPP: %d", fb->bpp); + e9_printf("Memory model: %d", fb->memory_model); + e9_printf("Red mask size: %d", fb->red_mask_size); + e9_printf("Red mask shift: %d", fb->red_mask_shift); + e9_printf("Green mask size: %d", fb->green_mask_size); + e9_printf("Green mask shift: %d", fb->green_mask_shift); + e9_printf("Blue mask size: %d", fb->blue_mask_size); + e9_printf("Blue mask shift: %d", fb->blue_mask_shift); + e9_printf("EDID size: %d", fb->edid_size); + e9_printf("EDID at: %x", fb->edid); + e9_printf("Video modes:"); + for (size_t j = 0; j < fb->mode_count; j++) { + e9_printf(" %dx%dx%d", fb->modes[j]->width, fb->modes[j]->height, fb->modes[j]->bpp); + } + } +FEAT_END + +FEAT_START + e9_printf(""); + if (exec_file_request.response == NULL) { + e9_printf("Executable file not passed"); + break; + } + struct limine_executable_file_response *exec_file_response = exec_file_request.response; + e9_printf("Executable file feature, revision %d", exec_file_response->revision); + print_file(exec_file_response->executable_file); +FEAT_END + +FEAT_START + e9_printf(""); + if (module_request.response == NULL) { + e9_printf("Modules not passed"); + break; + } + struct limine_module_response *module_response = module_request.response; + e9_printf("Modules feature, revision %d", module_response->revision); + e9_printf("%d module(s)", module_response->module_count); + for (size_t i = 0; i < module_response->module_count; i++) { + struct limine_file *f = module_response->modules[i]; + e9_printf("---"); + print_file(f); + } +FEAT_END + +FEAT_START + e9_printf(""); + if (rsdp_request.response == NULL) { + e9_printf("RSDP not passed"); + break; + } + struct limine_rsdp_response *rsdp_response = rsdp_request.response; + e9_printf("RSDP feature, revision %d", rsdp_response->revision); + e9_printf("RSDP at: %x", rsdp_response->address); +FEAT_END + +FEAT_START + e9_printf(""); + if (smbios_request.response == NULL) { + e9_printf("SMBIOS not passed"); + break; + } + struct limine_smbios_response *smbios_response = smbios_request.response; + e9_printf("SMBIOS feature, revision %d", smbios_response->revision); + e9_printf("SMBIOS 32-bit entry at: %x", smbios_response->entry_32); + e9_printf("SMBIOS 64-bit entry at: %x", smbios_response->entry_64); +FEAT_END + +FEAT_START + e9_printf(""); + if (est_request.response == NULL) { + e9_printf("EFI system table not passed"); + break; + } + struct limine_efi_system_table_response *est_response = est_request.response; + e9_printf("EFI system table feature, revision %d", est_response->revision); + e9_printf("EFI system table at: %x", est_response->address); +FEAT_END + +FEAT_START + e9_printf(""); + if (efi_memmap_request.response == NULL) { + e9_printf("EFI memory map not passed"); + break; + } + struct limine_efi_memmap_response *efi_memmap_response = efi_memmap_request.response; + e9_printf("EFI memory map feature, revision %d", efi_memmap_response->revision); + e9_printf("EFI memory map at: %x", efi_memmap_response->memmap); + e9_printf("EFI memory map size: %x", efi_memmap_response->memmap_size); + e9_printf("EFI memory descriptor size: %x", efi_memmap_response->desc_size); + e9_printf("EFI memory descriptor version: %d", efi_memmap_response->desc_version); +FEAT_END + +FEAT_START + e9_printf(""); + if (date_at_boot_request.response == NULL) { + e9_printf("Boot time not passed"); + break; + } + struct limine_date_at_boot_response *date_at_boot_response = date_at_boot_request.response; + e9_printf("Date at boot feature, revision %d", date_at_boot_response->revision); + e9_printf("Timestamp: %d", date_at_boot_response->timestamp); +FEAT_END + +// TODO: LoongArch MP +#ifndef __loongarch__ +FEAT_START + e9_printf(""); + if (_mp_request.response == NULL) { + e9_printf("MP info not passed"); + break; + } + struct limine_mp_response *mp_response = _mp_request.response; + e9_printf("MP feature, revision %d", mp_response->revision); + e9_printf("Flags: %x", mp_response->flags); +#if defined (__x86_64__) + e9_printf("BSP LAPIC ID: %x", mp_response->bsp_lapic_id); +#elif defined (__aarch64__) + e9_printf("BSP MPIDR: %x", mp_response->bsp_mpidr); +#elif defined (__riscv) + e9_printf("BSP Hart ID: %x", mp_response->bsp_hartid); +#endif + e9_printf("CPU count: %d", mp_response->cpu_count); + for (size_t i = 0; i < mp_response->cpu_count; i++) { + struct limine_mp_info *cpu = mp_response->cpus[i]; + e9_printf("Processor ID: %x", cpu->processor_id); +#if defined (__x86_64__) + e9_printf("LAPIC ID: %x", cpu->lapic_id); +#elif defined (__aarch64__) + e9_printf("MPIDR: %x", cpu->mpidr); +#elif defined (__riscv) + e9_printf("Hart ID: %x", cpu->hartid); +#endif + + +#if defined (__x86_64__) + if (cpu->lapic_id != mp_response->bsp_lapic_id) { +#elif defined (__aarch64__) + if (cpu->mpidr != mp_response->bsp_mpidr) { +#elif defined (__riscv) + if (cpu->hartid != mp_response->bsp_hartid) { +#endif + uint32_t old_ctr = __atomic_load_n(&ctr, __ATOMIC_SEQ_CST); + + __atomic_store_n(&cpu->goto_address, ap_entry, __ATOMIC_SEQ_CST); + + while (__atomic_load_n(&ctr, __ATOMIC_SEQ_CST) == old_ctr) + ; + } + } +FEAT_END +#endif + +FEAT_START + e9_printf(""); + if (_dtb_request.response == NULL) { + e9_printf("Device tree blob not passed"); + break; + } + struct limine_dtb_response *dtb_response = _dtb_request.response; + e9_printf("Device tree blob feature, revision %d", dtb_response->revision); + e9_printf("Device tree blob pointer: %x", dtb_response->dtb_ptr); + uint32_t dtb_magic = *(uint32_t*)dtb_response->dtb_ptr; + e9_printf("Device tree header magic: %x", dtb_magic); +FEAT_END + +FEAT_START + e9_printf(""); + if (_pm_request.response == NULL) { + e9_printf("Paging mode not passed"); + break; + } + struct limine_paging_mode_response *pm_response = _pm_request.response; + e9_printf("Paging mode feature, revision %d", pm_response->revision); + e9_printf(" mode: %d", pm_response->mode); +FEAT_END + +#if defined (__riscv) +FEAT_START + e9_printf(""); + struct limine_riscv_bsp_hartid_response *bsp_response = _bsp_request.response; + if (bsp_response == NULL) { + e9_printf("RISC-V BSP Hart ID was not passed"); + break; + } + e9_printf("RISC-V BSP Hart ID: %x", bsp_response->bsp_hartid); +FEAT_END +#endif + +FEAT_START + e9_printf(""); + struct limine_bootloader_performance_response *perf_response = _perf_request.response; + if (perf_response == NULL) { + e9_printf("Bootloader performance not passed"); + break; + } + e9_printf("Bootloader performance feature, revision %d", perf_response->revision); + e9_printf("Reset time: %d usec", perf_response->reset_usec); + e9_printf("Init time: %d usec", perf_response->init_usec); + e9_printf("Exec time: %d usec", perf_response->exec_usec); +FEAT_END + + for (;;); +} diff --git a/limine/test/limine.conf b/limine/test/limine.conf new file mode 100644 index 0000000..bae7f13 --- /dev/null +++ b/limine/test/limine.conf @@ -0,0 +1,59 @@ +# Some example macros +${TEST_KERNEL}=boot():/boot/test.elf +${WALLPAPER_PATH}=boot():/boot/bg.jpg + +default_entry: 1 +timeout: 3 +verbose: yes + +wallpaper: ${WALLPAPER_PATH} +backdrop: 008080 + +interface_help_colour: 3 + +/Limine Test + comment: Test of the Limine boot protocol. ${ARCH} ${FW_TYPE} + + protocol: limine + path: ${TEST_KERNEL} + cmdline: This is an example command line. + + module_path: ${WALLPAPER_PATH} + module_string: This is the first module. + + module_path: boot():/boot/bg.jpg + +/Multiboot2 Test + comment: Test of the multiboot2 boot protocol. + + protocol: multiboot2 + kernel_path: boot():/boot/multiboot2.elf + kernel_cmdline: This is an example kernel command line. + + module_path: boot():/boot/bg.jpg + module_string: This is the first module. + +/EFI Chainloading + comment: Test EFI image chainloading. + + protocol: efi_chainload + image_path: boot():/EFI/BOOT/BOOTX64.EFI + +/BIOS Chainloading + comment: Test BIOS chainloading. + + protocol: bios_chainload + drive: 1 + +/+Legacy + comment: Directory containing legacy entries. + + //Multiboot1 Test + comment: Test of the multiboot1 boot protocol. + + protocol: multiboot1 + kernel_path: boot():/boot/multiboot.elf + kernel_cmdline: This is an example kernel command line. + + module_path: boot():/boot/bg.jpg + module_string: This is the first module. diff --git a/limine/test/linker.ld b/limine/test/linker.ld new file mode 100644 index 0000000..2afa054 --- /dev/null +++ b/limine/test/linker.ld @@ -0,0 +1,53 @@ +PHDRS +{ + headers PT_PHDR PHDRS; + text PT_LOAD FILEHDR PHDRS; + rodata PT_LOAD; + data PT_LOAD; + dynamic PT_DYNAMIC; +} + +SECTIONS +{ + . = SIZEOF_HEADERS; + executable_start = . - SIZEOF_HEADERS; + + .text : { + *(.text .text.*) + } :text + + . = ALIGN(CONSTANT(MAXPAGESIZE)); + + .rodata : { + *(.rodata .rodata.*) + *(.srodata .srodata.*) + *(.sdata2 .sdata2.*) + } :rodata + + . = ALIGN(CONSTANT(MAXPAGESIZE)); + + .data : { + *(.data .data.*) + *(.sdata .sdata.*) + + KEEP(*(.limine_requests_start_marker)) + KEEP(*(.limine_requests)) + KEEP(*(.limine_requests_end_marker)) + } :data + + .dynamic : { + *(.dynamic) + } :data :dynamic + + .bss : { + *(.bss .bss.*) + *(.sbss .sbss.*) + *(COMMON) + } :data + + /DISCARD/ : { + *(.eh_frame*) + *(.note .note.*) + *(.interp) + } +} diff --git a/limine/test/memory.c b/limine/test/memory.c new file mode 100644 index 0000000..d597807 --- /dev/null +++ b/limine/test/memory.c @@ -0,0 +1,52 @@ +#include +#include + +void *memcpy(void *dest, const void *src, size_t n) { + uint8_t *pdest = dest; + const uint8_t *psrc = src; + + for (size_t i = 0; i < n; i++) { + pdest[i] = psrc[i]; + } + + return dest; +} + +void *memset(void *s, int c, size_t n) { + uint8_t *p = s; + + for (size_t i = 0; i < n; i++) { + p[i] = (uint8_t)c; + } + + return s; +} + +void *memmove(void *dest, const void *src, size_t n) { + uint8_t *pdest = dest; + const uint8_t *psrc = src; + + if ((uintptr_t)src > (uintptr_t)dest) { + for (size_t i = 0; i < n; i++) { + pdest[i] = psrc[i]; + } + } else if ((uintptr_t)src < (uintptr_t)dest) { + for (size_t i = n; i > 0; i--) { + pdest[i-1] = psrc[i-1]; + } + } + + return dest; +} + +int memcmp(const void *s1, const void *s2, size_t n) { + const uint8_t *p1 = s1; + const uint8_t *p2 = s2; + + for (size_t i = 0; i < n; i++) { + if (p1[i] != p2[i]) + return p1[i] < p2[i] ? -1 : 1; + } + + return 0; +} diff --git a/limine/test/multiboot.c b/limine/test/multiboot.c new file mode 100644 index 0000000..b1fd8e7 --- /dev/null +++ b/limine/test/multiboot.c @@ -0,0 +1,92 @@ +#include +#include +#include + +#define MULTIBOOT_BOOTLOADER_MAGIC 0x2badb002 + +void multiboot_main(uint32_t magic, struct multiboot1_info *info) { + if (magic != MULTIBOOT_BOOTLOADER_MAGIC) { + e9_printf("multiboot: Invalid magic: %x\n", magic); + goto out; + } + + e9_printf("Welcome to the multiboot1 test kernel: "); + + e9_printf("\t flags: %x", info->flags); + + e9_printf("\t mem_lower: %x", info->mem_lower); + e9_printf("\t mem_upper: %x", info->mem_upper); + + e9_printf("\t boot_device: %x", info->boot_device); + e9_printf("\t cmdline: %s", info->cmdline); + + { + struct multiboot1_module *start = (struct multiboot1_module *)info->mods_addr; + struct multiboot1_module *end = (struct multiboot1_module *)(info->mods_addr + info->mods_count); + + e9_printf("\t modules:"); + for (struct multiboot1_module* entry = start; entry < end; entry++) { + e9_printf("\t\t begin=%x", entry->begin); + e9_printf("\t\t end=%x", entry->end); + e9_printf("\t\t cmdline=%s", entry->cmdline); + } + } + + { + struct multiboot1_mmap_entry *start = (struct multiboot1_mmap_entry *)info->mmap_addr; + struct multiboot1_mmap_entry *end = (struct multiboot1_mmap_entry *)(info->mmap_addr + info->mmap_length); + + e9_printf("\t usable_entries_mmap:"); + + size_t total_mem = 0; + + // For now we only print the usable memory map entries since + // printing the whole memory map blows my terminal up. We also + // iterate through the available memory map entries and add up + // to find the total amount of usable memory. + for (struct multiboot1_mmap_entry* entry = start; entry < end; entry++) { + // Check if the memory map entry is marked as usable! + if (entry->type != 1) { + continue; + } + + e9_printf("\t\t addr=%x", entry->addr); + e9_printf("\t\t length=%x", entry->len); + e9_printf("\t\t type=Usable"); + + // Now this might be a bit confusing since but `entry->size` represents the + // is the size of the associated structure in bytes and `entry->len` represents the + // size of the memory region. + total_mem += entry->len; + } + + e9_printf("Total usable memory: %x", total_mem); + } + + // TODO(Andy-Python-Programmer): Drives are unimplemented + // TODO(Andy-Python-Programmer): ROM config is unimplemented + + e9_printf("\t bootloader_name: %s", info->bootloader_name); + + // TODO(Andy-Python-Programmer): APM table is unimplemented + // TODO(Andy-Python-Programmer): VBE tag is unimplemented + + e9_printf("\t fb_addr: %x", info->fb_addr); + e9_printf("\t fb_pitch: %x", info->fb_pitch); + e9_printf("\t fb_width: %x", info->fb_width); + e9_printf("\t fb_height: %x", info->fb_height); + e9_printf("\t fb_bpp: %x", info->fb_bpp); + e9_printf("\t fb_type: %x", info->fb_type); + + e9_printf("\t fb_red_mask_shift: %x", info->fb_red_mask_shift); + e9_printf("\t fb_red_mask_size: %x", info->fb_red_mask_size); + + e9_printf("\t fb_green_mask_shift: %x", info->fb_green_mask_shift); + e9_printf("\t fb_green_mask_size: %x", info->fb_green_mask_size); + + e9_printf("\t fb_blue_mask_shift: %x", info->fb_blue_mask_shift); + e9_printf("\t fb_blue_mask_size: %x", info->fb_blue_mask_size); + +out: + for (;;); +} diff --git a/limine/test/multiboot.ld b/limine/test/multiboot.ld new file mode 100644 index 0000000..c602227 --- /dev/null +++ b/limine/test/multiboot.ld @@ -0,0 +1,35 @@ +ENTRY(_start) + +SECTIONS { + . = 1M; + + .boot : + { + /* Ensure that the multiboot header is at the beginning! */ + *(.multiboot_header) + } + + . = ALIGN(4K); + .text : + { + *(.text .text.*) + } + + . = ALIGN(4K); + .rodata : + { + *(.rodata .rodata.*) + } + + . = ALIGN(4K); + .data : + { + *(.data .data.*) + } + + . = ALIGN(4K); + .bss : + { + *(.bss .bss.*) + } +} diff --git a/limine/test/multiboot2.c b/limine/test/multiboot2.c new file mode 100644 index 0000000..e1e9fd2 --- /dev/null +++ b/limine/test/multiboot2.c @@ -0,0 +1,181 @@ +#include +#include +#include + +struct multiboot_info { + uint32_t size; + uint32_t reserved; + struct multiboot_tag *first; +}; + +void multiboot2_main(uint32_t magic, struct multiboot_info* mb_info_addr) { + if (magic != MULTIBOOT2_BOOTLOADER_MAGIC) { + e9_printf("multiboot2: Invalid magic: %x\n", magic); + goto out; + } + + e9_printf("Welcome to the multiboot2 test kernel: "); + e9_printf("\t size=%d", mb_info_addr->size); + e9_printf("\t reserved=%d", mb_info_addr->reserved); + + e9_print("\nTags:\n"); + + size_t add_size = 0; + + // NOTE: We set i to 8 to skip size and reserved fields: + for (size_t i = 8; i < mb_info_addr->size; i += add_size) { + struct multiboot_tag *tag = (struct multiboot_tag *)((uint8_t *)mb_info_addr + i); + + if (tag->type == MULTIBOOT_TAG_TYPE_END) { + break; + } + + switch (tag->type) { + case MULTIBOOT_TAG_TYPE_CMDLINE: { + struct multiboot_tag_string *cmdline = (struct multiboot_tag_string *)tag; + e9_printf("\t cmdline:"); + e9_printf("\t\t string=%s", cmdline->string); + break; + } + + case MULTIBOOT_TAG_TYPE_BOOT_LOADER_NAME: { + struct multiboot_tag_string *name = (struct multiboot_tag_string *)tag; + e9_printf("\t bootloader_name:"); + e9_printf("\t\t string=%s", name->string); + break; + } + + case MULTIBOOT_TAG_TYPE_ACPI_OLD: { + struct multiboot_tag_old_acpi *old_acpi = (struct multiboot_tag_old_acpi *)tag; + e9_printf("\t acpi_old:"); + e9_printf("\t\t rsdp=%s", old_acpi->rsdp); + break; + } + + case MULTIBOOT_TAG_TYPE_ACPI_NEW: { + struct multiboot_tag_new_acpi *new_acpi = (struct multiboot_tag_new_acpi *)tag; + e9_printf("\t acpi_new:"); + e9_printf("\t\t rsdp=%s", new_acpi->rsdp); + break; + } + + case MULTIBOOT_TAG_TYPE_MODULE: { + struct multiboot_tag_module *module = (struct multiboot_tag_module *)tag; + e9_printf("\t module:"); + e9_printf("\t\t mod_start=%x", module->mod_start); + e9_printf("\t\t mod_end=%x", module->mod_end); + e9_printf("\t\t cmdline=%s", module->cmdline); + break; + } + + case MULTIBOOT_TAG_TYPE_BASIC_MEMINFO: { + struct multiboot_tag_basic_meminfo *meminfo = (struct multiboot_tag_basic_meminfo *)tag; + e9_printf("\t basic_meminfo:"); + e9_printf("\t\t mem_lower=%x", meminfo->mem_lower); + e9_printf("\t\t mem_upper=%x", meminfo->mem_upper); + break; + } + + // unimplemented(Andy-Python-Programmer): MULTIBOOT_TAG_TYPE_BOOTDEV + + case MULTIBOOT_TAG_TYPE_MMAP: { + struct multiboot_tag_mmap *mmap = (struct multiboot_tag_mmap *)tag; + e9_printf("\t mmap:"); + e9_printf("\t\t entry_size=%d", mmap->entry_size); + e9_printf("\t\t entry_version=%d", mmap->entry_version); + e9_printf("\t\t entries:"); + + struct multiboot_mmap_entry *m = (struct multiboot_mmap_entry *)(mmap->entries); + + size_t entry_count = (mmap->size - sizeof(struct multiboot_tag_mmap)) / sizeof(struct multiboot_mmap_entry); + e9_printf("\t\t entry count: %d", entry_count); + + for (size_t i = 0; i < entry_count; i++) { + e9_printf("\t\t\t addr=%x", m[i].addr); + e9_printf("\t\t\t len=%x", m[i].len); + e9_printf("\t\t\t type=%x", m[i].type); + } + + break; + } + + case MULTIBOOT_TAG_TYPE_EFI_MMAP: { + struct multiboot_tag_efi_mmap *mmap = (struct multiboot_tag_efi_mmap *)tag; + e9_printf("\t efi_mmap:"); + e9_printf("\t\t descr_vers=%d", mmap->descr_vers); + e9_printf("\t\t descr_size=%d", mmap->descr_size); + e9_printf("\t\t size=%d", mmap->size); + e9_printf("\t\t entries:"); + + struct memory_descriptor { + uint32_t type; + uint32_t pad; + uint64_t physical_start; + uint64_t virtual_start; + uint64_t pages; + uint64_t attribute; + }; + + size_t entry_count = (mmap->size - sizeof(struct multiboot_tag_efi_mmap)) / mmap->descr_size; + e9_printf("\t\t entry count: %d", entry_count); + + for (size_t i = 0; i < entry_count; i++) { + struct memory_descriptor *m = (struct memory_descriptor *)(mmap->efi_mmap + i * mmap->descr_size); + + e9_printf("\t\t\t type=%x", m->type); + e9_printf("\t\t\t physical_start=%x", m->physical_start); + e9_printf("\t\t\t virtual_start=%x", m->virtual_start); + e9_printf("\t\t\t pages=%x", m->pages); + e9_printf("\t\t\t attribute=%x", m->attribute); + } + + break; + } + + // unimplemented(Andy-Python-Programmer): MULTIBOOT_TAG_TYPE_VBE + + case MULTIBOOT_TAG_TYPE_FRAMEBUFFER: { + struct multiboot_tag_framebuffer *fb = (struct multiboot_tag_framebuffer *)tag; + + e9_printf("\t framebuffer:"); + e9_printf("\t\t framebuffer_pitch: %d", fb->common.framebuffer_pitch); + e9_printf("\t\t framebuffer_width: %d", fb->common.framebuffer_width); + e9_printf("\t\t framebuffer_height: %d", fb->common.framebuffer_height); + e9_printf("\t\t framebuffer_bpp: %d", fb->common.framebuffer_bpp); + e9_printf("\t\t framebuffer_type: %d", fb->common.framebuffer_type); + e9_printf("\t\t framebuffer_address: %x", fb->common.framebuffer_addr); + + switch (fb->common.framebuffer_type) { + case MULTIBOOT_FRAMEBUFFER_TYPE_RGB: { + e9_printf("\t\t framebuffer_red_field_position: %x", fb->framebuffer_red_field_position); + e9_printf("\t\t framebuffer_red_mask_size: %x", fb->framebuffer_red_mask_size); + e9_printf("\t\t framebuffer_green_field_position: %x", fb->framebuffer_green_field_position); + e9_printf("\t\t framebuffer_green_mask_size: %x", fb->framebuffer_green_mask_size); + e9_printf("\t\t framebuffer_blue_field_position: %x", fb->framebuffer_blue_field_position); + e9_printf("\t\t framebuffer_blue_mask_size: %x", fb->framebuffer_blue_mask_size); + break; + } + + // Rest are unimplemented(Andy-Python-Programmer): + } + + break; + } + + case MULTIBOOT_TAG_TYPE_NETWORK: { + struct multiboot_tag_network *network = (struct multiboot_tag_network *)tag; + e9_printf("\t network tag exists"); + break; + } + } + + add_size = tag->size; + + // Align the size to 8 bytes. + if ((add_size % 8) != 0) + add_size += (8 - add_size % 8); + } + +out: + for (;;); +} diff --git a/limine/test/multiboot2.ld b/limine/test/multiboot2.ld new file mode 100644 index 0000000..c602227 --- /dev/null +++ b/limine/test/multiboot2.ld @@ -0,0 +1,35 @@ +ENTRY(_start) + +SECTIONS { + . = 1M; + + .boot : + { + /* Ensure that the multiboot header is at the beginning! */ + *(.multiboot_header) + } + + . = ALIGN(4K); + .text : + { + *(.text .text.*) + } + + . = ALIGN(4K); + .rodata : + { + *(.rodata .rodata.*) + } + + . = ALIGN(4K); + .data : + { + *(.data .data.*) + } + + . = ALIGN(4K); + .bss : + { + *(.bss .bss.*) + } +} diff --git a/limine/test/multiboot2_trampoline.asm b/limine/test/multiboot2_trampoline.asm new file mode 100644 index 0000000..8c22ba5 --- /dev/null +++ b/limine/test/multiboot2_trampoline.asm @@ -0,0 +1,45 @@ +extern multiboot2_main + +global _start + +section .multiboot_header +header_start: + dd 0xe85250d6 ; Magic number (multiboot 2) + dd 0 ; Architecture 0 (protected mode i386) + dd header_end - header_start ; Header length + dd 0x100000000 - (0xe85250d6 + 0 + (header_end - header_start)) ; Checksum + +align 8 +framebuffer_tag_start: + dw 5 ; type + dw 1 ; flags + dd framebuffer_tag_end - framebuffer_tag_start ; size + dd 800 ; width + dd 600 ; height + dd 32 ; depth +framebuffer_tag_end: + +align 8 + ; Required end tag: + dw 0 ; type + dw 0 ; flags + dw 8 ; size +header_end: + +section .text +bits 32 + +_start: + cli + + mov esp, stack_top + + push ebx + push eax + + call multiboot2_main ; Jump to our multiboot test kernel + +section .bss +stack_bottom: + resb 4096 * 16 +stack_top: diff --git a/limine/test/multiboot_trampoline.asm b/limine/test/multiboot_trampoline.asm new file mode 100644 index 0000000..4160faa --- /dev/null +++ b/limine/test/multiboot_trampoline.asm @@ -0,0 +1,37 @@ +%define MULTIBOOT_HEADER_MAGIC 0x1badb002 + +; Flags: +; +; bit 2: request framebuffer +%define MULTIBOOT_HEADER_FLAGS (1 << 2) + +extern multiboot_main + +global _start + +section .multiboot_header + +align 4 +header_start: + dd MULTIBOOT_HEADER_MAGIC ; Magic number (multiboot 1) + dd MULTIBOOT_HEADER_FLAGS ; Flags + dd -(MULTIBOOT_HEADER_MAGIC + MULTIBOOT_HEADER_FLAGS) ; Checksum +header_end: + +section .text +bits 32 + +_start: + cli + + mov esp, stack_top + + push ebx + push eax + + call multiboot_main ; Jump to our multiboot test kernel + +section .bss +stack_bottom: + resb 4096 * 16 +stack_top: diff --git a/limine/test/test.mk b/limine/test/test.mk new file mode 100644 index 0000000..2e02810 --- /dev/null +++ b/limine/test/test.mk @@ -0,0 +1,163 @@ +override CC := $(CC_FOR_TARGET) +override CFLAGS := -O2 -g -Wall -Wextra +override LDFLAGS := +override LD := $(LD_FOR_TARGET) + +override CC_IS_CLANG := $(shell ! $(CC) --version 2>/dev/null | $(GREP) -q '^Target: '; echo $$?) + +ifeq ($(ARCH),x86) +ifeq ($(CC_IS_CLANG),1) +override CC += \ + -target x86_64-unknown-none-elf +endif +override LDFLAGS += \ + -m elf_x86_64 +endif +ifeq ($(ARCH),aarch64) +ifeq ($(CC_IS_CLANG),1) +override CC += \ + -target aarch64-unknown-none-elf +endif +override LDFLAGS += \ + -m aarch64elf +endif +ifeq ($(ARCH),riscv64) +ifeq ($(CC_IS_CLANG),1) +override CC += \ + -target riscv64-unknown-none-elf +endif +override LDFLAGS += \ + -m elf64lriscv +endif +ifeq ($(ARCH),loongarch64) +ifeq ($(CC_IS_CLANG),1) +override CC += \ + -target loongarch64-unknown-none-elf +endif +override LDFLAGS += \ + -m elf64loongarch +endif + +override LDFLAGS += \ + -Tlinker.ld \ + -nostdlib \ + -zmax-page-size=0x1000 \ + -pie \ + -ztext + +override LDFLAGS_MB2 := \ + -m elf_i386 \ + -Tmultiboot2.ld \ + -nostdlib \ + -zmax-page-size=0x1000 \ + -static + +override LDFLAGS_MB1 := \ + -m elf_i386 \ + -Tmultiboot.ld \ + -nostdlib \ + -zmax-page-size=0x1000 \ + -static + +override CFLAGS += \ + -std=c11 \ + -nostdinc \ + -ffreestanding \ + -fno-stack-protector \ + -fno-stack-check \ + -fno-lto \ + -fPIE \ + -I. \ + -I../limine-protocol/include \ + -I../flanterm/src \ + -isystem ../freestnd-c-hdrs/include \ + -D_LIMINE_PROTO \ + -DLIMINE_API_REVISION=4 + +ifeq ($(ARCH),x86) +override CFLAGS += \ + -m64 \ + -march=x86-64 \ + -mabi=sysv \ + -mgeneral-regs-only \ + -mno-red-zone +endif + +ifeq ($(ARCH),aarch64) +override CFLAGS += \ + -mcpu=generic \ + -march=armv8-a+nofp+nosimd \ + -mgeneral-regs-only +endif + +ifeq ($(ARCH),riscv64) +override CFLAGS += \ + -march=rv64imac \ + -mabi=lp64 \ + -mno-relax +override LDFLAGS += \ + --no-relax +endif + +ifeq ($(ARCH),loongarch64) +override CFLAGS += \ + -march=loongarch64 \ + -mabi=lp64s \ + -mfpu=none \ + -msimd=none +endif + +override CFLAGS_MB := \ + -std=c11 \ + -nostdinc \ + -ffreestanding \ + -fno-stack-protector \ + -fno-stack-check \ + -fno-lto \ + -fno-PIC \ + -m32 \ + -march=i686 \ + -mabi=sysv \ + -mgeneral-regs-only \ + -I. \ + -I../common/protos \ + -isystem ../freestnd-c-hdrs/include + +ifeq ($(ARCH),x86) +all: test.elf multiboot2.elf multiboot.elf +else +all: test.elf +endif + +flanterm.o: ../flanterm/src/flanterm.c + $(CC) $(CFLAGS) -c $< -o $@ + +flanterm_fb.o: ../flanterm/src/flanterm_backends/fb.c + $(CC) $(CFLAGS) -c $< -o $@ + +test.elf: limine.o e9print.o memory.o flanterm.o flanterm_fb.o + $(LD) $(LDFLAGS) $^ -o $@ + +multiboot2.elf: multiboot2_trampoline.o + $(CC) $(CFLAGS_MB) -c memory.c -o memory.o + $(CC) $(CFLAGS_MB) -c multiboot2.c -o multiboot2.o + $(CC) $(CFLAGS_MB) -c e9print.c -o e9print.o + $(LD) $(LDFLAGS_MB2) $^ memory.o multiboot2.o e9print.o -o $@ + +multiboot.elf: multiboot_trampoline.o + $(CC) $(CFLAGS_MB) -c memory.c -o memory.o + $(CC) $(CFLAGS_MB) -c multiboot.c -o multiboot.o + $(CC) $(CFLAGS_MB) -c e9print.c -o e9print.o + $(LD) $(LDFLAGS_MB1) $^ memory.o multiboot.o e9print.o -o $@ + +%.o: %.c + $(CC) $(CFLAGS) -c $< -o $@ + +%.o: %.asm + nasm -felf32 -F dwarf -g $< -o $@ + +clean: + rm -rf test.elf limine.o e9print.o memory.o + rm -rf flanterm.o flanterm_fb.o + rm -rf multiboot2.o multiboot2.elf multiboot2_trampoline.o + rm -rf multiboot.o multiboot_trampoline.o multiboot.elf diff --git a/limine/version.sh b/limine/version.sh new file mode 100755 index 0000000..fe6d4b3 --- /dev/null +++ b/limine/version.sh @@ -0,0 +1,29 @@ +#! /bin/sh + +LC_ALL=C +export LC_ALL + +srcdir="$(dirname "$0")" +test -z "$srcdir" && srcdir=. + +cd "$srcdir" + +if test -f version; then + printf '%s' "$(cat version)" + exit 0 +fi + +if ! test -d .git || ! git log -n1 --pretty='%h' >/dev/null 2>&1; then + printf 'UNVERSIONED' + exit 0 +fi + +tmpfile="$(mktemp)" + +if ! git describe --exact-match --tags $(git log -n1 --pretty='%h') >"$tmpfile" 2>/dev/null; then + echo g$(git log -n1 --pretty='%h') >"$tmpfile" +fi + +printf '%s' "$(sed 's/^v//g' <"$tmpfile")" + +rm -f "$tmpfile"