diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index db3614a..303dbd7 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -14,11 +14,19 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 + - uses: dtolnay/rust-toolchain@nightly with: - profile: minimal - toolchain: nightly + targets: x86_64-unknown-linux-gnu, aarch64-unknown-linux-gnu, riscv64gc-unknown-linux-gnu + components: rustfmt, clippy - name: Rust Version run: rustup --version && rustc --version - - name: Build the limine-rs crate - run: cargo build --verbose + - name: Run clippy x86_64 + run: cargo clippy --verbose --target x86_64-unknown-linux-gnu --all-features -- -D warnings + - name: Run clippy aarch64 + run: cargo clippy --verbose --target aarch64-unknown-linux-gnu --all-features -- -D warnings + - name: Run clippy riscv64 + run: cargo clippy --verbose --target riscv64gc-unknown-linux-gnu --all-features -- -D warnings + - name: Reformat code + uses: mbrobbel/rustfmt-check@master + with: + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 9abef2f..71ffe6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# 0.2.0 **BREAKING CHANGES** +* Complete rewrite. +* Removed limine-proc. +* Added support for platform-specific requests and responses. +* Fixed safety concerns. +* Simplified the API heavily. + # 0.1.12 * Add the `BaseRevision` tag. diff --git a/Cargo.lock b/Cargo.lock index 20f1364..862f042 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,60 +3,21 @@ version = 3 [[package]] -name = "limine" -version = "0.1.11" -dependencies = [ - "limine-proc", - "uuid", -] - -[[package]] -name = "limine-proc" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1044abd8ac7bd1002d5c1e045314dd7d1c31896442f4b9270fd56aa234b0de81" -dependencies = [ - "quote", - "syn", -] - -[[package]] -name = "proc-macro2" -version = "1.0.38" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9027b48e9d4c9175fa2218adf3557f91c1137021739951d4932f5f8268ac48aa" -dependencies = [ - "unicode-xid", -] - -[[package]] -name = "quote" -version = "1.0.18" +name = "bitflags" +version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" -dependencies = [ - "proc-macro2", -] +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" [[package]] -name = "syn" -version = "1.0.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ff7c592601f11445996a06f8ad0c27f094a58857c2f89e97974ab9235b92c52" +name = "limine" +version = "0.2.0" dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", + "bitflags", + "uuid", ] -[[package]] -name = "unicode-xid" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" - [[package]] name = "uuid" -version = "1.1.2" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd6469f4314d5f1ffec476e05f17cc9a78bc7a27a6a857842170bdf8d6f98d2f" +checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a" diff --git a/Cargo.toml b/Cargo.toml index 1ea4c19..4b50ebb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,20 +1,22 @@ [package] name = "limine" description = "Rust crate for parsing the limine boot protocol structures" -version = "0.1.12" +version = "0.2.0" edition = "2021" -authors = ["Anhad Singh "] -license = "MIT/Apache-2.0" +authors = [ + "Anhad Singh ", + "Lysander Mealy ", +] +license = "MIT OR Apache-2.0" repository = "https://github.com/limine-bootloader/limine-rs" readme = "README.md" categories = ["no-std"] -[features] -requests-section = ["limine-proc"] -into-uuid = ["uuid"] -default = [] - [dependencies] -limine-proc = { optional = true, version = "0.1.0" } -uuid = { optional = true, version = "1.1.2", default-features = false } +bitflags = "2" +uuid = { version = "1", default-features = false, optional = true } + +[features] +uuid = ["dep:uuid"] +ipaddr = [] diff --git a/LICENSE-APACHE b/LICENSE-APACHE index 62bf906..35701ff 100644 --- a/LICENSE-APACHE +++ b/LICENSE-APACHE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2021 Anhad Singh + Copyright 2021 Anhad Singh, 2024 Lysander Mealy Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/LICENSE-MIT b/LICENSE-MIT index 6dab349..5831258 100644 --- a/LICENSE-MIT +++ b/LICENSE-MIT @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021 Anhad Singh +Copyright (c) 2021 Anhad Singh, 2024 Lysander Mealy Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/limine-proc/Cargo.lock b/limine-proc/Cargo.lock deleted file mode 100644 index 93f84ae..0000000 --- a/limine-proc/Cargo.lock +++ /dev/null @@ -1,46 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "limine-proc" -version = "0.1.0" -dependencies = [ - "quote", - "syn", -] - -[[package]] -name = "proc-macro2" -version = "1.0.38" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9027b48e9d4c9175fa2218adf3557f91c1137021739951d4932f5f8268ac48aa" -dependencies = [ - "unicode-xid", -] - -[[package]] -name = "quote" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "syn" -version = "1.0.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ff7c592601f11445996a06f8ad0c27f094a58857c2f89e97974ab9235b92c52" -dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", -] - -[[package]] -name = "unicode-xid" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" diff --git a/limine-proc/Cargo.toml b/limine-proc/Cargo.toml deleted file mode 100644 index f5b4c1b..0000000 --- a/limine-proc/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -name = "limine-proc" -version = "0.1.0" -edition = "2021" -description = "Rust crate for parsing the limine boot protocol structures" -authors = ["Anhad Singh "] -license = "MIT/Apache-2.0" -repository = "https://github.com/limine-bootloader/limine-rs" -categories = ["no-std"] - -[lib] -proc-macro = true - -[dependencies] -quote = "1.0.18" - -[dependencies.syn] -features = ["full"] -version = "1.0.91" diff --git a/limine-proc/src/lib.rs b/limine-proc/src/lib.rs deleted file mode 100644 index 127b9b7..0000000 --- a/limine-proc/src/lib.rs +++ /dev/null @@ -1,31 +0,0 @@ -extern crate proc_macro; - -use proc_macro::TokenStream; - -/// Adds the limine request to the `.limine_reqs` section. -/// -/// ## Note -/// If the executable kernel file contains a `.limine_reqs` section, the bootloader -/// will, instead of scanning the executable for requests, fetch the requests from -/// a NULL-terminated array of pointers to the provided requests, contained inside -/// said section. -#[proc_macro_attribute] -pub fn limine_tag(_: TokenStream, item: TokenStream) -> TokenStream { - let input = syn::parse_macro_input!(item as syn::ItemStatic); - - let typ = input.ty.clone(); - let name = input.ident.clone(); - let ptr_name = quote::format_ident!("{name}_PTR"); - - quote::quote! { - #input - - const _: () = { - // Limine expects a NULL-terminated array of pointers to the provided requests. - #[used] - #[link_section = ".limine_reqs"] - static #ptr_name: &#typ = &#name; - }; - } - .into() -} diff --git a/rust-toolchain b/rust-toolchain deleted file mode 100644 index 5d56faf..0000000 --- a/rust-toolchain +++ /dev/null @@ -1,2 +0,0 @@ -[toolchain] -channel = "nightly" diff --git a/src/file.rs b/src/file.rs new file mode 100644 index 0000000..2ae64df --- /dev/null +++ b/src/file.rs @@ -0,0 +1,172 @@ +//! File structure and related functions. + +use core::{ + ffi::{c_char, c_void, CStr}, + mem::MaybeUninit, + num::NonZeroU32, +}; + +#[cfg(feature = "ipaddr")] +use core::net::{Ipv4Addr, SocketAddrV4}; + +/// A UUID. With the `uuid` feature, this can be converted directly to +/// [`uuid::Uuid`] via [`Into`], and the reverse via [`From`]. +#[repr(C)] +#[derive(PartialEq, Eq, Clone, Copy)] +pub struct Uuid { + /// The first 32 bits of the UUID. + pub a: u32, + /// The next 16 bits of the UUID. + pub b: u16, + /// The next 16 bits of the UUID. + pub c: u16, + /// The last 64 bits of the UUID. + pub d: [u8; 8], +} +impl Uuid { + fn non_zero(&self) -> Option { + (self.a != 0 || self.b != 0 || self.c != 0 || self.d != [0; 8]).then_some(*self) + } +} +#[cfg(feature = "uuid")] +impl From for Uuid { + fn from(uuid: uuid::Uuid) -> Self { + Self { + a: uuid.as_fields().0, + b: uuid.as_fields().1, + c: uuid.as_fields().2, + d: *uuid.as_fields().3, + } + } +} +#[cfg(feature = "uuid")] +impl From for uuid::Uuid { + fn from(uuid: Uuid) -> Self { + Self::from_fields(uuid.a, uuid.b, uuid.c, &uuid.d) + } +} + +/// A media type for a file. +#[derive(PartialEq, Eq, Clone, Copy)] +#[repr(transparent)] +pub struct MediaType(u32); +impl MediaType { + /// Unknown media type. + pub const GENERIC: Self = Self(0); + /// A CD-ROM. + pub const OPTICAL: Self = Self(1); + /// A TFTP server. + pub const TFTP: Self = Self(2); +} + +/// A file loaded by the bootloader. Returned from +/// [`KernelFileRequest`](crate::request::KernelFileRequest) and +/// [`ModuleRequest`](crate::request::ModuleRequest). +#[repr(C)] +pub struct File { + revision: u64, + addr: *mut c_void, + size: u64, + path: *const c_char, + cmdline: *const c_char, + media_type: MediaType, + _unused: MaybeUninit, + tftp_ip: Option, + tftp_port: Option, + partition_idx: Option, + mbr_disk_id: Option, + gpt_disk_id: Uuid, + gpt_partition_id: Uuid, + partition_uuid: Uuid, +} +impl File { + /// Get the revision of the file. Currently, this is always 0. + pub fn revision(&self) -> u64 { + self.revision + } + + /// The base address of the file. Note that this is not necessarily a + /// pointer to executable code. It simply points to the raw file. + pub fn addr(&self) -> *mut u8 { + self.addr.cast() + } + /// The size of the file, in bytes. + pub fn size(&self) -> u64 { + self.size + } + + /// The path of the file. This is the path that was passed to the bootloader + /// in either the configuration file or the `internal_modules` field of the + /// [`ModuleRequest`](crate::request::ModuleRequest). + /// + /// It is returned as a raw byte slice, and the encoding is unspecified. + pub fn path(&self) -> &[u8] { + let c_str = unsafe { CStr::from_ptr(self.path) }; + c_str.to_bytes() + } + /// The command line of the file. This is the command line that was passed + /// to the bootloader in either the configuration file or the + /// `internal_modules` field of the + /// [`ModuleRequest`](crate::request::ModuleRequest). + /// + /// It is returned as a raw byte slice, and the encoding is unspecified. + pub fn cmdline(&self) -> &[u8] { + let c_str = unsafe { CStr::from_ptr(self.cmdline) }; + c_str.to_bytes() + } + + /// The media type of the file. See [`MediaType`] for more information. + pub fn media_type(&self) -> MediaType { + self.media_type + } + + /// The IP address of the TFTP server, if the file was loaded from a TFTP. + #[cfg(feature = "ipaddr")] + pub fn tftp_ip(&self) -> Option { + self.tftp_ip.map(|v| { + let bytes = v.get().to_ne_bytes(); + Ipv4Addr::new(bytes[0], bytes[1], bytes[2], bytes[3]) + }) + } + /// The IP address of the TFTP server, if the file was loaded from a TFTP. + #[cfg(not(feature = "ipaddr"))] + pub fn tftp_ip(&self) -> Option { + self.tftp_ip + } + /// The port of the TFTP server, if the file was loaded from a TFTP. + pub fn tftp_port(&self) -> Option { + self.tftp_port + } + /// The address of the TFTP server, if the file was loaded from a TFTP. This + /// is simply a combination of [`tftp_ip`](Self::tftp_ip) and + /// [`tftp_port`](Self::tftp_port). + #[cfg(feature = "ipaddr")] + pub fn tftp_addr(&self) -> Option { + self.tftp_ip() + .and_then(|ip| Some(SocketAddrV4::new(ip, self.tftp_port()?.get() as u16))) + } + + /// The partition index of the file, if the file was loaded from a partition. + pub fn partition_idx(&self) -> Option { + self.partition_idx + } + /// The MBR disk ID of the file, if the file was loaded from an MBR disk. + pub fn mbr_disk_id(&self) -> Option { + self.mbr_disk_id + } + + /// The GPT disk UUID of the file, if the file was loaded from a GPT disk. + pub fn gpt_disk_id(&self) -> Option { + self.gpt_disk_id.non_zero() + } + /// The GPT partition UUID of the file, if the file was loaded from a GPT + /// partition. + pub fn gpt_partition_id(&self) -> Option { + self.gpt_partition_id.non_zero() + } + /// The partition UUID of the file, if the file was loaded from a partition + /// with a UUID. + pub fn partition_uuid(&self) -> Option { + self.partition_uuid.non_zero() + } +} diff --git a/src/framebuffer.rs b/src/framebuffer.rs new file mode 100644 index 0000000..2936404 --- /dev/null +++ b/src/framebuffer.rs @@ -0,0 +1,184 @@ +//! Auxiliary types for the [framebuffer request](crate::request::FramebufferRequest) + +use core::ffi::c_void; + +#[derive(Clone, Copy)] +#[repr(C)] +pub(crate) struct RawFramebufferV0 { + addr: *mut c_void, + width: u64, + height: u64, + pitch: u64, + bpp: u16, + memory_model: MemoryModel, + red_mask_size: u8, + red_mask_shift: u8, + green_mask_size: u8, + green_mask_shift: u8, + blue_mask_size: u8, + blue_mask_shift: u8, + _unused: [u8; 7], + edid_size: u64, + edid: *mut u8, +} + +#[derive(Clone, Copy)] +#[repr(C)] +pub(crate) struct RawFramebufferV1 { + _v0: RawFramebufferV0, + mode_ct: u64, + modes: *const *const VideoMode, +} + +#[repr(C)] +pub(crate) union RawFramebuffer { + v0: RawFramebufferV0, + v1: RawFramebufferV1, +} + +/// A memory model used by a framebuffer. Currently only +/// [`MemoryModel::RGB`](Self::RGB) is defined. +#[repr(transparent)] +#[derive(PartialEq, Eq, Clone, Copy)] +pub struct MemoryModel(u8); +impl MemoryModel { + /// This is an RGB framebuffer. + pub const RGB: Self = Self(1); +} + +/// A mode supported by the current framebuffer. +#[repr(C)] +pub struct VideoMode { + /// The pitch (distance between rows, in bytes). This is not always the same + /// as `(width * bpp) / 8`, as padding bytes may be added to achieve a + /// particular alignment. + pub pitch: u64, + /// The width of the framebuffer, in pixels. + pub width: u64, + /// The height of the framebuffer, in pixels. + pub height: u64, + /// The number of **bits** (*not bytes*) per single pixel in the framebuffer. + pub bpp: u16, + /// The memory model used by this video mode. See [`MemoryModel`] for + /// specific values. + pub memory_model: MemoryModel, + /// The size of the red mask, in bits. This part of the mask can be applied + /// with `red_value & ((1 << red_mask_size) - 1)`. + pub red_mask_size: u8, + /// The number of bits to shift the red mask to the left. This part of the + /// mask can be applied with `red_value << red_mask_shift`. + pub red_mask_shift: u8, + /// The size of the green mask, in bits. This part of the mask can be + /// applied with `green_value & ((1 << green_mask_size) - 1)`. + pub green_mask_size: u8, + /// The number of bits to shift the green mask to the left. This part of the + /// mask can be applied with `green_value << green_mask_shift`. + pub green_mask_shift: u8, + /// The size of the blue mask, in bits. This part of the mask can be applied + /// with `blue_value & ((1 << blue_mask_size) - 1)`. + pub blue_mask_size: u8, + /// The number of bits to shift the blue mask to the left. This part of the + /// mask can be applied with `blue_value << blue_mask_shift`. + pub blue_mask_shift: u8, +} + +/// A pointer to a framebuffer. +/// +/// # Why is this a wrapper type? +/// Two revisions currently exist of the framebuffer type. However, the type +/// itself has no revision field. In order to keep this type safe, we wrap the +/// pointer with its associated revision taken from the response. +pub struct Framebuffer<'a> { + revision: u64, + inner: &'a RawFramebuffer, +} +impl<'a> Framebuffer<'a> { + pub(crate) fn new(revision: u64, inner: &'a RawFramebuffer) -> Self { + Self { revision, inner } + } + + /// The address of the framebuffer. Note that no synchronization is done on + /// this pointer, so you yourself must synchronize all access. In addition, + /// the pointer may point to uninitialized bytes at boot, so dereferencing + /// it will cause UB. + pub fn addr(&self) -> *mut u8 { + unsafe { self.inner.v0 }.addr.cast() + } + + /// The width of the framebuffer in its current mode. + pub fn width(&self) -> u64 { + unsafe { self.inner.v0 }.width + } + /// The height of the framebuffer in its current mode. + pub fn height(&self) -> u64 { + unsafe { self.inner.v0 }.height + } + + /// The pitch (distance between rows, in bytes) of the framebuffer in the + /// current mode. This is not always the same as `(width * bpp) / 8`, as + /// padding bytes may be added to achieve a particular alignment. + pub fn pitch(&self) -> u64 { + unsafe { self.inner.v0 }.pitch + } + /// The number of **bits** (*not bytes*) per pixel of the framebuffer in the + /// current mode. + pub fn bpp(&self) -> u16 { + unsafe { self.inner.v0 }.bpp + } + + /// The memory model of the framebuffer in the current mode. See + /// [`MemoryModel`] for specific values. + pub fn memory_model(&self) -> MemoryModel { + unsafe { self.inner.v0 }.memory_model + } + + /// The size of the red mask, in bits. This part of the mask can be applied + /// with `red_value & ((1 << red_mask_size) - 1)`. + pub fn red_mask_size(&self) -> u8 { + unsafe { self.inner.v0 }.red_mask_size + } + /// The number of bits to shift the red mask to the left. This part of the + /// mask can be applied with `red_value << red_mask_shift`. + pub fn red_mask_shift(&self) -> u8 { + unsafe { self.inner.v0 }.red_mask_shift + } + /// The size of the green mask, in bits. This part of the mask can be + /// applied with `green_value & ((1 << green_mask_size) - 1)`. + pub fn green_mask_size(&self) -> u8 { + unsafe { self.inner.v0 }.green_mask_size + } + /// The number of bits to shift the green mask to the left. This part of the + /// mask can be applied with `green_value << green_mask_shift`. + pub fn green_mask_shift(&self) -> u8 { + unsafe { self.inner.v0 }.green_mask_shift + } + /// The size of the blue mask, in bits. This part of the mask can be applied + /// with `blue_value & ((1 << blue_mask_size) - 1)`. + pub fn blue_mask_size(&self) -> u8 { + unsafe { self.inner.v0 }.blue_mask_size + } + /// The number of bits to shift the blue mask to the left. This part of the + /// mask can be applied with `blue_value << blue_mask_shift`. + pub fn blue_mask_shift(&self) -> u8 { + unsafe { self.inner.v0 }.blue_mask_shift + } + + /// The raw EDID bytes of the display attached to this framebuffer. + pub fn edid(&self) -> &[u8] { + unsafe { core::slice::from_raw_parts(self.inner.v0.edid, self.inner.v0.edid_size as usize) } + } + + /// The video modes supported on this framebuffer. Only available on + /// revision 1 and above. + pub fn modes(&self) -> Option<&[&VideoMode]> { + match self.revision { + 0 => None, + 1.. => Some(unsafe { + core::slice::from_raw_parts( + self.inner.v1.modes.cast(), + self.inner.v1.mode_ct as usize, + ) + }), + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 96068d9..da592a1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,777 +1,96 @@ -//! Rust crate for parsing the limine boot protocol structures. -//! -//! ## Resources -//! * [Limine Boot Protocol Specification](https://github.com/limine-bootloader/limine/blob/trunk/PROTOCOL.md) - #![no_std] -#![feature(const_nonnull_new)] -#![allow(deprecated)] +#![deny(missing_docs)] -#[cfg(feature = "requests-section")] -pub use limine_proc::*; +//! Rust Bindings for the limine boot protocol. +//! +//! # Examples +//! An example kernel using this crate can be found +//! [here](https://github.com/limine-bootloader/limine-rust-barebones). For +//! smaller usage examples, see [usage](#usage). +//! +//! # Crate features +//! - `uuid`: Implements `Into` and `From` for [`file::Uuid`]. +//! - `ipaddr`: Enables functions in [`file::File`] to return `Ipv4Addr`. This +//! is feature gated because it will only appear in stable on Rust 1.77.0, on +//! March 21st. +//! +//! # Revisions +//! Many types in the limine boot protocol have associated revisions. These +//! signify various added fields and changes to the protocol. For requests, if a +//! bootloader doesn't support a revision, it will simply process it as if it is +//! the highest revision it does support. The bootloader will only return the +//! latest supported revision in responses. The response revision is +//! automatically checked by the types in this crate, and types that may not be +//! returned are wrapped in an [`Option`]. +//! +//! In this crate, you may specify the revisions of your requests via +//! `with_revision`, but you likely just want to use `new`. This will use the +//! latest revision supported by this crate. +//! +//! # Usage +//! The first thing you need to do is place a [`BaseRevision`] tag somewhere in +//! your code. This tag is used to identify what revision of the protocol your +//! kernel requires. Without this tag, the bootloader will assume revision 0, +//! which is likely not what you want. +//! +//! The [`BaseRevision`] tag can be placed anywhere in your code, like so: +//! ```rust +//! use limine::BaseRevision; +//! +//! // Require version 1 or higher +//! pub static BASE_REVISION: BaseRevision = BaseRevision::new(); +//! ``` +//! +//! Next, you can place any requests you would like. For example, to request a +//! larger stack (*recommended on debug Rust builds*), you can do the following: +//! ```rust +//! use limine::request::StackSizeRequest; +//! +//! // Some reasonable size +//! pub const STACK_SIZE: u64 = 0x100000; +//! +//! // Request a larger stack +//! pub static STACK_SIZE_REQUEST: StackSizeRequest = StackSizeRequest::new().with_size(STACK_SIZE); +//! ``` use core::cell::UnsafeCell; -use core::ffi::{c_char, CStr}; -use core::fmt::Debug; -use core::marker::PhantomData; -use core::ops::{Deref, DerefMut}; -use core::ptr::NonNull; - -#[derive(Debug)] -#[repr(transparent)] -pub struct EntryPoint(*const ()); - -/// [`NonNull`] with the dereference traits implemented. -#[repr(transparent)] -pub struct NonNullPtr { - ptr: NonNull, - // This marker does not affect the variance but is required for - // dropck to undestand that we logically own a `T`. - // - // TODO: Use `Unique` when stabalized! - _phantom: PhantomData, -} - -impl NonNullPtr { - pub fn as_ptr(&self) -> *mut T { - self.ptr.as_ptr() - } -} - -impl Deref for NonNullPtr { - type Target = T; - - fn deref(&self) -> &Self::Target { - // SAFETY: We have shared reference to the data. - unsafe { self.ptr.as_ref() } - } -} - -impl DerefMut for NonNullPtr { - fn deref_mut(&mut self) -> &mut Self::Target { - // SAFETY: We have exclusive reference to the data. - unsafe { self.ptr.as_mut() } - } -} - -impl Debug for NonNullPtr { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - let value: &T = &*self; - - f.debug_tuple("NonNullPtr") - .field(&format_args!("{:#x?}", value)) - .finish() - } -} - -// SAFETY: The underlying type (`T`) implements {Send, Sync} so, it is safe -// for NonNullPtr to implement {Send, Sync}. -unsafe impl Send for NonNullPtr {} -unsafe impl Sync for NonNullPtr {} - -#[repr(transparent)] -pub struct Ptr { - ptr: Option>, - // This marker does not affect the variance but is required for - // dropck to undestand that we logically own a `T`. - // - // TODO: Use `Unique` when stabalized! - _phantom: PhantomData, -} - -impl Ptr { - const DEFAULT: Ptr = Self { - ptr: None, - _phantom: PhantomData, - }; - - #[inline] - pub fn as_ptr(&self) -> Option<*mut T> { - Some(self.ptr?.as_ptr()) - } - - #[inline] - pub fn get<'a>(&self) -> Option<&'a T> { - // SAFETY: According to the specication the bootloader provides a aligned - // pointer and there is no public API to construct a [`Ptr`] - // so, its safe to assume that the [`NonNull::as_ref`] are applied. - // If not, its the bootloader's fault that they have violated the - // specification!. - // - // Also, we have a shared reference to the data and there is no - // legal way to mutate it, unless through [`Ptr::as_ptr`] - // (requires pointer dereferencing which is unsafe) or [`Ptr::get_mut`] - // (requires exclusive access to the [`Ptr`]). - self.ptr.map(|e| unsafe { e.as_ref() }) - } - - #[inline] - pub fn get_mut<'a>(&mut self) -> Option<&'a mut T> { - // SAFETY: Check the safety for [`Ptr::get`] and we have - // exclusive access to the data. - self.ptr.as_mut().map(|e| unsafe { e.as_mut() }) - } -} - -impl Ptr { - /// Converts the string pointer into a rust string. - pub fn to_str(&self) -> Option<&CStr> { - // SAFETY: According to the specification, the pointer points - // to a valid C string with a NULL terminator of size less than - // `isize::MAX`. Also we know that the `Ptr` is a valid C - // string, because it has a `T` of `c_char`. See the [`Ptr::get`] - // for more details. - unsafe { Some(CStr::from_ptr(self.as_ptr()?)) } - } -} - -impl Ptr { - #[inline] - pub const fn new(entry_point: fn() -> !) -> Self { - Self { - ptr: NonNull::new(entry_point as *mut _), - _phantom: PhantomData, - } - } -} - -impl Debug for Ptr { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_tuple("Ptr") - .field(&format_args!("{:#x?}", self.get())) - .finish() - } -} - -// SAFETY: The underlying type (`T`) implements {Send, Sync} so, it is safe -// for Ptr to implement {Send, Sync}. -unsafe impl Send for Ptr {} -unsafe impl Sync for Ptr {} - -type ArrayPtr = NonNullPtr>; - -impl ArrayPtr { - fn into_slice<'a>(&'a self, len: usize) -> &'a [NonNullPtr] { - // SAFETY: We have shared reference to the array. - unsafe { core::slice::from_raw_parts(self.as_ptr(), len) } - } - - fn into_slice_mut<'a>(&'a mut self, len: usize) -> &'a mut [NonNullPtr] { - // SAFETY: We have exculusive access to the array. - unsafe { core::slice::from_raw_parts_mut(self.as_ptr(), len) } - } -} - -/// Used to create the request struct. -macro_rules! make_struct { - ( - $(#[$meta:meta])* - struct $name:ident: [$id1:expr, $id2:expr] => $response_ty:ty { - $($(#[$field_meta:meta])* $field_name:ident : $field_ty:ty = $field_default:expr),* - }; - ) => { - $(#[$meta])* - #[repr(C)] - #[derive(Debug)] - pub struct $name { - id: [u64; 4], - revision: u64, - // XXX: The response is required to be wrapped inside an unsafe cell, since - // by default the response is set to NULL and when the compiler does not see - // any writes to the field, it is free to assume that the response is NULL. In - // our situation the bootloader mutates the field and we need to ensure that - // the compiler does not optimize the read away. - response: UnsafeCell>, - $(pub $field_name: $field_ty),* - } - - impl $name { - pub const fn new(revision: u64) -> Self { - Self { - // The request ID is composed of 4 64-bit wide unsigned integers but the first - // two remain constant. This is refered as `_COMMON_MAGIC` in the protocol - // header. - id: [0xc7b1dd30df4c8b88, 0x0a82e883a194f07b, $id1, $id2], - revision, - - response: UnsafeCell::new(Ptr::DEFAULT), - $($field_name: $field_default),* - } - } - - pub fn id(&self) -> [u64; 4] { - self.id - } - - pub fn get_response(&self) -> Ptr<$response_ty> { - unsafe { core::ptr::read_volatile(self.response.get()) } - } - - // generate a setter method for each field: - $($(#[$field_meta])* pub const fn $field_name(mut self, value: $field_ty) -> Self { - self.$field_name = value; - self - })* - } - - // maker trait implementations for request struct: - unsafe impl Sync for $name {} - }; -} - -// misc structures: - -#[repr(C)] -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -pub struct Uuid { - pub a: u32, - pub b: u16, - pub c: u16, - pub d: [u8; 8], -} - -#[cfg(feature = "into-uuid")] -impl From for uuid::Uuid { - fn from(lim: Uuid) -> Self { - Self::from_fields(lim.a, lim.b, lim.c, &lim.d) - } -} - -#[cfg(feature = "into-uuid")] -impl From for Uuid { - fn from(uuid: uuid::Uuid) -> Self { - let (a, b, c, d) = uuid.as_fields(); - Self { - a, - b, - c, - d: d.clone(), - } - } -} - -#[repr(C)] -#[derive(Debug)] -pub struct File { - /// Revision of this structure. - pub revision: u64, - /// The address of the file. - pub base: Ptr, - /// The size of the file. - pub length: u64, - /// The path of the file within the volume, with a leading slash. - pub path: Ptr, - /// A command line associated with the file. - pub cmdline: Ptr, - /// Type of media file resides on. - pub media_type: u64, - pub unused: u32, - /// If non-0, this is the IP of the TFTP server the file was loaded from. - pub tftp_ip: u32, - /// Likewise, but port. - pub tftp_port: u32, - /// 1-based partition index of the volume from which the file was loaded. If 0, it - /// means invalid or unpartitioned. - pub partition_index: u32, - /// If non-0, this is the ID of the disk the file was loaded from as reported in its MBR. - pub mbr_disk_id: u32, - /// If non-0, this is the UUID of the disk the file was loaded from as reported in its GPT. - pub gpt_disk_uuid: Uuid, - /// If non-0, this is the UUID of the partition the file was loaded from as reported in the GPT. - pub gpt_part_uuid: Uuid, - /// If non-0, this is the UUID of the filesystem of the partition the file was loaded from. - pub part_uuid: Uuid, -} - -#[repr(C)] -#[derive(Debug)] +pub mod file; +pub mod framebuffer; +pub mod memory_map; +pub mod modules; +pub mod paging; +pub mod request; +pub mod response; +pub mod smp; + +/// A tag setting the base revision supported by the kernel. Set this in your +/// kernel in order to require a higher revision. Without this tag, the +/// bootloader will assume revision 0. +/// +/// The latest revision is 1. pub struct BaseRevision { - id: [u64; 2], + _id: [u64; 2], revision: UnsafeCell, } - impl BaseRevision { - pub const fn new(revision: u64) -> Self { + /// Create a new base revision tag with the latest revision. + pub const fn new() -> Self { + Self::with_revision(1) + } + + /// Create a new base revision tag with the given revision. + pub const fn with_revision(revision: u64) -> Self { Self { - id: [0xf9562b2d5c95a6c8, 0x6a7b384944536bdc], + _id: [0xf9562b2d5c95a6c8, 0x6a7b384944536bdc], revision: UnsafeCell::new(revision), } } + /// Check whether the given revision is supported by the bootloader. pub fn is_supported(&self) -> bool { - let revision: u64 = unsafe { core::ptr::read_volatile(self.revision.get()) }; - revision == 0 + (unsafe { *self.revision.get() }) == 0 } } - unsafe impl Sync for BaseRevision {} - -// boot info request tag: -#[repr(C)] -#[derive(Debug)] -pub struct BootInfoResponse { - pub revision: u64, - /// Null-terminated string containing the name of the bootloader. - pub name: Ptr, - /// Null-terminated string containg the version of the bootloader. - pub version: Ptr, -} - -make_struct!( - struct BootInfoRequest: [0xf55038d8e2a1202f, 0x279426fcf5f59740] => BootInfoResponse {}; -); - -// stack size request tag: -#[repr(C)] -#[derive(Debug)] -pub struct StackSizeResponse { - pub revision: u64, -} - -make_struct!( - struct StackSizeRequest: [0x224ef0460a8e8926, 0xe1cb0fc25f46ea3d] => StackSizeResponse { - /// The requested stack size (also used for SMP processors). - stack_size: u64 = 0 - }; -); - -// HHDM request tag: -#[repr(C)] -#[derive(Debug)] -pub struct HhdmResponse { - pub revision: u64, - /// The virtual address offset of the beginning of the higher half direct map. - pub offset: u64, -} - -make_struct!( - struct HhdmRequest: [0x48dcf1cb8ad2b852, 0x63984e959a98244b] => HhdmResponse {}; -); - -// framebuffer request tag: -#[repr(C)] -#[derive(Debug)] -pub struct Framebuffer { - pub address: Ptr, - pub width: u64, - pub height: u64, - pub pitch: u64, - pub bpp: u16, - pub memory_model: u8, - pub red_mask_size: u8, - pub red_mask_shift: u8, - pub green_mask_size: u8, - pub green_mask_shift: u8, - pub blue_mask_size: u8, - pub blue_mask_shift: u8, - pub reserved: [u8; 7], - pub edid_size: u64, - pub edid: Ptr, -} - -impl Framebuffer { - /// Returns the size of the framebuffer. - pub fn size(&self) -> usize { - self.pitch as usize * self.height as usize * (self.bpp as usize / 8) - } -} - -#[repr(C)] -#[derive(Debug)] -pub struct FramebufferResponse { - pub revision: u64, - /// How many framebuffers are present. - pub framebuffer_count: u64, - /// Pointer to an array of `framebuffer_count` pointers to struct [`Framebuffer`] structures. - pub framebuffers: ArrayPtr, -} - -impl FramebufferResponse { - pub fn framebuffers(&self) -> &[NonNullPtr] { - self.framebuffers - .into_slice(self.framebuffer_count as usize) - } -} - -make_struct!( - struct FramebufferRequest: [0x9d5827dcd881dd75, 0xa3148604f6fab11b] => FramebufferResponse {}; -); - -// terminal request tag: -#[repr(C)] -#[derive(Debug)] -#[deprecated(note = "This feature is deprecated, do not use if possible.")] -pub struct Terminal { - /// Number of columns provided by the terminal. - pub cols: u64, - /// Number of rows provided by the terminal. - pub rows: u64, - /// The framebuffer associated with this terminal. - pub framebuffer: Ptr, -} - -#[deprecated(note = "This feature is deprecated, do not use if possible.")] -type TerminalWrite = - Option; - -#[repr(C)] -#[derive(Debug)] -#[deprecated(note = "This feature is deprecated, do not use if possible.")] -pub struct TerminalResponse { - pub revision: u64, - - /// How many terminals are present. - pub terminal_count: u64, - /// Pointer to an array of `terminal_count` pointers to struct `_terminal` structures. - pub terminals: ArrayPtr, - /// Physical pointer to the terminal `write()` function. The function is not thread-safe, nor - /// reentrant, per-terminal. This means multiple terminals may be called simultaneously, and - /// multiple callbacks may be handled simultaneously. The terminal parameter points to the - /// [`Terminal`] structure to use to output the string; the string parameter points to - /// a string to print; the length paremeter contains the length, in bytes, of the string to print. - write_fn: TerminalWrite, -} - -#[deprecated(note = "This feature is deprecated, do not use if possible.")] -impl TerminalResponse { - pub fn terminals(&self) -> &[NonNullPtr] { - self.terminals.into_slice(self.terminal_count as usize) - } - - pub fn write(&self) -> Option { - let term_func = self.write_fn?; - - Some(move |terminal: &Terminal, txt: &str| unsafe { - term_func(terminal as *const _, txt.as_ptr(), txt.len() as u64); - }) - } -} - -make_struct!( - /// Omitting this request will cause the bootloader to not initialise the terminal service. - #[deprecated(note = "This feature is deprecated, do not use if possible.")] - struct TerminalRequest: [0xc8ac59310c2b0844, 0xa68d0c7265d38878] => TerminalResponse { - callback: Ptr<()> = Ptr::DEFAULT - }; -); - -// 5-level paging request tag: -#[repr(C)] -#[derive(Debug)] -#[deprecated(note = "This feature is deprecated, do not use if possible.")] -pub struct Level5PagingResponse { - pub revision: u64, -} - -make_struct!( - /// The presence of this request will prompt the bootloader to turn on x86_64 5-level paging. It will not be - /// turned on if this request is not present. If the response pointer is unchanged, 5-level paging is engaged. - #[deprecated(note = "This feature is deprecated, do not use if possible.")] - struct Level5PagingRequest: [0x94469551da9b3192, 0xebe5e86db7382888] => Level5PagingResponse {}; -); - -#[repr(u64)] -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum PagingMode { - #[cfg(target_arch = "x86_64")] - Lvl4 = 0, - #[cfg(target_arch = "x86_64")] - Lvl5 = 1, - - #[cfg(target_arch = "aarch64")] - Lvl4 = 0, - #[cfg(target_arch = "aarch64")] - Lvl5 = 1, - - #[cfg(target_arch = "riscv64")] - Sv39 = 0, - #[cfg(target_arch = "riscv64")] - Sv48 = 1, - #[cfg(target_arch = "riscv64")] - Sv57 = 2, -} - -impl PagingMode { - #[cfg(target_arch = "x86_64")] - pub const fn default() -> Self { - PagingMode::Lvl4 - } - - #[cfg(target_arch = "aarch64")] - pub const fn default() -> Self { - PagingMode::Lvl4 - } - - #[cfg(target_arch = "riscv64")] - pub const fn default() -> Self { - PagingMode::Sv48 - } -} - -// Paging mode request tag: -#[repr(C)] -#[derive(Debug)] -pub struct PagingModeResponse { - pub revision: u64, - pub mode: PagingMode, - pub flags: u64, -} - -make_struct!( - struct PagingModeRequest: [0x95c1a0edab0944cb, 0xa4e5cb3842f7488a] => PagingModeResponse { - mode: PagingMode = PagingMode::default(), - flags: u64 = 0 - }; -); - -// smp request tag: -#[repr(C)] -#[derive(Debug)] -pub struct SmpInfo { - /// ACPI Processor UID as specified by the MADT. - pub processor_id: u32, - /// Local APIC ID of the processor as specified by the MADT. - pub lapic_id: u32, - pub reserved: u64, - /// An atomic write to this field causes the parked CPU to jump to the - /// written address, on a 64KiB (or Stack Size Request size) stack. A pointer - /// to the struct [`SmpInfo`] structure of the CPU is passed in RDI. Other - /// than that, the CPU state will be the same as described for the bootstrap - /// processor. This field is unused for the structure describing the bootstrap - /// processor. - pub goto_address: extern "C" fn(info: *const SmpInfo) -> !, - /// A free for use field. - pub extra_argument: u64, -} - -#[repr(C)] -#[derive(Debug)] -pub struct SmpResponse { - pub revision: u64, - /// Bit 0: X2APIC has been enabled. - pub flags: u32, - /// The Local APIC ID of the bootstrap processor. - pub bsp_lapic_id: u32, - /// How many CPUs are present. It includes the bootstrap processor. - pub cpu_count: u64, - /// Pointer to an array of `cpu_count` pointers to struct [`SmpInfo`] - /// structures. - pub cpus: ArrayPtr, -} - -impl SmpResponse { - /// Return's the SMP info array pointer as a mutable rust slice. - /// - /// ## Safety - /// - /// If this tag was returned by a bootloader mutating the slice must conform to the following - /// rules in order to not trigger UB: - /// - /// - Writing to [`SmpInfo::goto_address`] will cause it to start executing at the - /// provided address. - /// - The address pointed by [`SmpInfo::goto_address`] must be that of a - /// `extern "C" fn(&'static SmpInfo) -> !`, this also means that once written this - /// struct must not be mutated any further. - pub fn cpus(&mut self) -> &mut [NonNullPtr] { - self.cpus.into_slice_mut(self.cpu_count as usize) - } -} - -make_struct!( - /// The presence of this request will prompt the bootloader to bootstrap the - /// secondary processors. This will not be done if this request is not present. - struct SmpRequest: [0x95a67b819a1b857e, 0xa0b61b723b6a73e0] => SmpResponse { - /// Bit 0: Enable X2APIC, if possible. - flags: u32 = 0 - }; -); - -// memory map request tag: -#[repr(u32)] -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum MemoryMapEntryType { - Usable = 0, - Reserved = 1, - AcpiReclaimable = 2, - AcpiNvs = 3, - BadMemory = 4, - BootloaderReclaimable = 5, - /// The kernel and modules loaded are not marked as usable memory. They are - /// marked as Kernel/Modules. The entries are guaranteed to be sorted by base - /// address, lowest to highest. Usable and bootloader reclaimable entries are - /// guaranteed to be 4096 byte aligned for both base and length. Usable and - /// bootloader reclaimable entries are guaranteed not to overlap with any - /// other entry. To the contrary, all non-usable entries (including kernel/modules) - /// are not guaranteed any alignment, nor is it guaranteed that they do not - /// overlap other entries. - KernelAndModules = 6, - Framebuffer = 7, -} - -#[repr(C)] -#[derive(Debug)] -pub struct MemmapEntry { - pub base: u64, - pub len: u64, - pub typ: MemoryMapEntryType, -} - -#[repr(C)] -#[derive(Debug)] -pub struct MemmapResponse { - pub revision: u64, - /// How many memory map entries are present. - pub entry_count: u64, - /// Pointer to an array of `entry_count` pointers to struct [`MemmapEntry`] structures. - pub entries: ArrayPtr, -} - -impl MemmapResponse { - pub fn memmap(&self) -> &[NonNullPtr] { - self.entries.into_slice(self.entry_count as usize) - } - - pub fn memmap_mut(&mut self) -> &mut [NonNullPtr] { - self.entries.into_slice_mut(self.entry_count as usize) - } -} - -make_struct!( - struct MemmapRequest: [0x67cf3d9d378a806f, 0xe304acdfc50c3c62] => MemmapResponse {}; -); - -// entry point request tag: -#[repr(C)] -#[derive(Debug)] -pub struct EntryPointResponse { - pub revision: u64, -} - -make_struct!( - struct EntryPointRequest: [0x13d86c035a1cd3e1, 0x2b0caa89d8f3026a] => EntryPointResponse { - /// The requested entry point. - entry: Ptr = Ptr::DEFAULT - }; -); - -// kernel file request tag: -#[repr(C)] -#[derive(Debug)] -pub struct KernelFileResponse { - pub revision: u64, - /// Pointer to the struct [`File`] structure for the kernel file. - pub kernel_file: Ptr, -} - -make_struct!( - struct KernelFileRequest: [0xad97e90e83f1ed67, 0x31eb5d1c5ff23b69] => KernelFileResponse {}; -); - -// module request tag: -#[repr(C)] -#[derive(Debug)] -pub struct ModuleResponse { - pub revision: u64, - /// How many modules are present. - pub module_count: u64, - /// Pointer to an array of `module_count` pointers to struct [`File`] structures - pub modules: ArrayPtr, -} - -impl ModuleResponse { - pub fn modules(&self) -> &[NonNullPtr] { - self.modules.into_slice(self.module_count as usize) - } -} - -make_struct!( - struct ModuleRequest: [0x3e7e279702be32af, 0xca1c4f3bd1280cee] => ModuleResponse {}; -); - -// RSDP request tag: -#[repr(C)] -#[derive(Debug)] -pub struct RsdpResponse { - pub revision: u64, - /// Address of the RSDP table. - pub address: Ptr, -} - -make_struct!( - struct RsdpRequest: [0xc5e77b6b397e7b43, 0x27637845accdcf3c] => RsdpResponse {}; -); - -// SMBIOS request tag: -#[repr(C)] -#[derive(Debug)] -pub struct SmbiosResponse { - pub revision: u64, - /// Address of the 32-bit SMBIOS entry point. NULL if not present. - pub entry_32: Ptr, - /// Address of the 64-bit SMBIOS entry point. NULL if not present. - pub entry_64: Ptr, -} - -make_struct!( - struct SmbiosRequest: [0x9e9046f11e095391, 0xaa4a520fefbde5ee] => SmbiosResponse {}; -); - -// EFI system table request tag: -#[repr(C)] -#[derive(Debug)] -pub struct EfiSystemTableResponse { - pub revision: u64, - /// Address of EFI system table. - pub address: Ptr, -} - -make_struct!( - struct EfiSystemTableRequest: [0x5ceba5163eaaf6d6, 0x0a6981610cf65fcc] => EfiSystemTableResponse {}; -); - -// boot time request tag: -#[repr(C)] -#[derive(Debug)] -pub struct BootTimeResponse { - pub revision: u64, - /// The UNIX time on boot, in seconds, taken from the system RTC. - pub boot_time: i64, -} - -make_struct!( - struct BootTimeRequest: [0x502746e184c088aa, 0xfbc5ec83e6327893] => BootTimeResponse {}; -); - -// kernel address request tag: -#[repr(C)] -#[derive(Debug)] -pub struct KernelAddressResponse { - pub revision: u64, - /// The physical base address of the kernel. - pub physical_base: u64, - /// The virtual base address of the kernel. - pub virtual_base: u64, -} - -make_struct!( - struct KernelAddressRequest: [0x71ba76863cc55f63, 0xb2644a48c516a487] => KernelAddressResponse {}; -); - -// device tree blob request tag: - -/// ## Notes -/// * Information contained in the `/chosen` node may not reflect the information -/// given by bootloader tags, and as such the `/chosen` node properties should -/// be ignored. -#[repr(C)] -#[derive(Debug)] -pub struct DtbResponse { - pub revision: u64, - /// Virtual pointer to the device tree blob. - pub dtb_ptr: Ptr, -} - -make_struct!( - struct DtbRequest: [0xb40ddb48fb54bac7, 0x545081493f81ffb7] => DtbResponse {}; -); +unsafe impl Send for BaseRevision {} diff --git a/src/memory_map.rs b/src/memory_map.rs new file mode 100644 index 0000000..de0587f --- /dev/null +++ b/src/memory_map.rs @@ -0,0 +1,44 @@ +//! Auxiliary types for the [memory map request](crate::request::MemoryMapRequest) + +/// A type of entry within the memory map. +#[repr(transparent)] +#[derive(PartialEq, Eq, Clone, Copy)] +pub struct EntryType(u64); +impl EntryType { + /// The memory region is freely usable. + pub const USABLE: Self = Self(0); + /// The memory region is permanently reserved. + pub const RESERVED: Self = Self(1); + /// The memory region is currently used by ACPI, but can be reclaimed once + /// ACPI structures are no longer needed. + pub const ACPI_RECLAIMABLE: Self = Self(2); + /// The memory region is permanently reserved by ACPI, and must not be used. + pub const ACPI_NVS: Self = Self(3); + /// The memory region is unusable due to physical damage or similar errors. + pub const BAD_MEMORY: Self = Self(4); + /// The memory region is used by the bootloader, but can be reclaimed once + /// all responses have been processed and will no longer be used. + pub const BOOTLOADER_RECLAIMABLE: Self = Self(5); + /// The memory region is used by the kernel and modules, and as such is + /// permanently reserved. + pub const KERNEL_AND_MODULES: Self = Self(6); + /// The memory region is used by the framebuffer, and as such is permanently + /// reserved. + pub const FRAMEBUFFER: Self = Self(7); +} +impl From for EntryType { + fn from(val: u64) -> Self { + Self(val) + } +} + +/// A memory map entry. +#[repr(C)] +pub struct Entry { + /// The base of the memory region, in *physical space*. + pub base: u64, + /// The length of the memory region, in bytes. + pub length: u64, + /// The type of the memory region. See [`EntryType`] for specific values. + pub entry_type: EntryType, +} diff --git a/src/modules.rs b/src/modules.rs new file mode 100644 index 0000000..f254ecb --- /dev/null +++ b/src/modules.rs @@ -0,0 +1,112 @@ +//! Auxiliary types for the [module request](crate::request::ModuleRequest) + +use core::ffi::{c_char, CStr}; + +use bitflags::bitflags; + +bitflags! { + /// Flags for internal modules + #[derive(PartialEq, Eq, Clone, Copy)] + pub struct ModuleFlags: u64 { + /// The module is required. If it is not found, the bootloader will + /// refuse to boot. + const REQUIRED = 1 << 0; + /// The module is GZ-compressed and will be uncompressed by the + /// bootloader. This is only honored on response revision 2 and greater. + const COMPRESSED = 1 << 1; + } +} + +/// Create a NUL-terminated C string from a string literal +#[macro_export] +macro_rules! cstr { + () => { + unsafe { core::ffi::CStr::from_bytes_with_nul_unchecked(b"\0") } + }; + ($s:expr) => { + unsafe { core::ffi::CStr::from_bytes_with_nul_unchecked(concat!($s, "\0").as_bytes()) } + }; +} + +/// An internal module that the kernel requests from the bootloader. Only +/// available with request revision 1 and greater. +#[repr(C)] +pub struct InternalModule { + path: *const c_char, + cmdline: *const c_char, + flags: ModuleFlags, +} +unsafe impl Sync for InternalModule {} +unsafe impl Send for InternalModule {} +impl InternalModule { + /// Create a new internal module with no path, no cmdline and no flags. + pub const fn new() -> Self { + Self { + path: b"\0".as_ptr().cast(), + cmdline: b"\0".as_ptr().cast(), + flags: ModuleFlags::empty(), + } + } + + /// Set the path of the internal module. This function returns the new value. + /// + /// # Parameters + /// - `path`: The new value of the field. + pub const fn with_path(mut self, path: &'static CStr) -> Self { + self.path = path.as_ptr(); + self + } + /// Set the path of the internal module. This function operates in place. + /// + /// # Parameters + /// - `path`: The new value of the field. + pub fn set_path(&mut self, path: &'static CStr) { + self.path = path.as_ptr(); + } + + /// Set the command-line for the module. This function returns the new value. + /// + /// # Parameters + /// - `cmdline`: The new value of the field. + pub const fn with_cmdline(mut self, cmdline: &'static CStr) -> Self { + self.cmdline = cmdline.as_ptr(); + self + } + /// Set the command-line for the module. This function operates in place. + /// + /// # Parameters + /// - `cmdline`: The new value of the field. + pub fn set_cmdline(&mut self, cmdline: &'static CStr) { + self.cmdline = cmdline.as_ptr(); + } + + /// Set the flags for the module. This function returns the new value. + /// + /// # Parameters + /// - `flags`: The new value of the field. + pub const fn with_flags(mut self, flags: ModuleFlags) -> Self { + self.flags = flags; + self + } + /// Set the flags for the module. This function operates in place. + /// + /// # Parameters + /// - `flags`: The new value of the field. + pub fn set_flags(&mut self, flags: ModuleFlags) { + self.flags = flags; + } + + /// Returns the module's path as a byte slice with unspecified encoding. + pub fn path(&self) -> &[u8] { + unsafe { CStr::from_ptr(self.path).to_bytes() } + } + /// Returns the module's command-line as a byte slice with unspecified + /// encoding. + pub fn cmdline(&self) -> &[u8] { + unsafe { CStr::from_ptr(self.cmdline).to_bytes() } + } + /// Returns the module's flags. + pub fn flags(&self) -> ModuleFlags { + self.flags + } +} diff --git a/src/paging.rs b/src/paging.rs new file mode 100644 index 0000000..5dcc13c --- /dev/null +++ b/src/paging.rs @@ -0,0 +1,38 @@ +//! Auxiliary types for the [paging mode +//! request](crate::request::PagingModeRequest). + +use bitflags::bitflags; + +bitflags! { + /// Paging mode flags. None are currently specified. + #[derive(Default, Clone, Copy)] + pub struct Flags: u64 {} +} + +/// A paging mode. +#[repr(transparent)] +#[derive(PartialEq, Eq, Clone, Copy)] +pub struct Mode(u64); +impl From for Mode { + fn from(value: u64) -> Self { + Self(value) + } +} + +#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))] +impl Mode { + /// (x86_64 and aarch64) Four-level paging (i.e. 48-bit virtual addresses on x86_64). + pub const FOUR_LEVEL: Self = Self(0); + /// (x86_64 and aarch64) Five-level paging (i.e. 52-bit virtual addresses on x86_64). + pub const FIVE_LEVEL: Self = Self(1); +} + +#[cfg(target_arch = "riscv64")] +impl Mode { + /// (riscv64 only) SV39, i.e. 39-bit virtual addresses. + pub const SV39: Self = Self(0); + /// (riscv64 only) SV48, i.e. 48-bit virtual addresses. + pub const SV48: Self = Self(1); + /// (riscv64 only) SV57, i.e. 57-bit virtual addresses. + pub const SV57: Self = Self(2); +} diff --git a/src/request.rs b/src/request.rs new file mode 100644 index 0000000..bf6e8d1 --- /dev/null +++ b/src/request.rs @@ -0,0 +1,786 @@ +//! Request structures + +use core::{cell::UnsafeCell, ptr::NonNull}; + +use crate::{modules::InternalModule, paging, response::*, smp}; + +macro_rules! impl_base_fns { + ($latest_revision:expr, $response:ty, $magic:expr, { $($(#[$attr:meta])* $field:ident: $val:expr),* $(,)? }) => { + /// Create a new request with the latest revision. + pub const fn new() -> Self { + Self::with_revision($latest_revision) + } + + /// Create a new request with the given revision. + pub const fn with_revision(revision: u64) -> Self { + Self { + id: $magic, + revision, + response: Response::none(), + $($(#[$attr])* $field: $val),* + } + } + + /// Get the ID of this request. This includes the magic number and the + /// request-specific ID. + pub fn id(&self) -> &[u64; 4] { + &self.id + } + + /// Get the revision of this request. + pub fn revision(&self) -> u64 { + self.revision + } + + /// Get the response to this request, if it has been set. + pub fn get_response(&self) -> Option<&$response> { + self.response.get() + } + /// Get a mutable reference to the response to this request, if it has + /// been set. + /// + /// Note that this method takes a mutable reference, so the request will + /// have to be wrapped in a mutex or similar in order to use it. + pub fn get_response_mut(&mut self) -> Option<&mut $response> { + self.response.get_mut() + } + }; +} + +macro_rules! setter { + ($(#[$attr:meta])* $ty:ty, $setter:ident, $with:ident, $field:ident) => { + $(#[$attr])* + /// This function operates in place. + /// + /// # Parameters + #[doc = concat!("- ", stringify!($field), ": The new value for the field.")] + pub fn $setter(&mut self, $field: impl Into<$ty>) { + self.$field = $field.into(); + } + $(#[$attr])* + /// This function returns the new value. + /// + /// # Parameters + #[doc = concat!("- ", stringify!($field), ": The new value for the field.")] + pub const fn $with(mut self, $field: $ty) -> Self { + self.$field = $field; + self + } + }; +} + +macro_rules! magic { + ($first:expr, $second:expr) => { + [0xc7b1dd30df4c8b88, 0x0a82e883a194f07b, $first, $second] + }; +} + +struct Response { + inner: UnsafeCell>>, +} +unsafe impl Sync for Response {} +unsafe impl Send for Response {} +impl Response { + pub fn get(&self) -> Option<&T> { + Some(unsafe { core::ptr::read_volatile(self.inner.get())?.as_ref() }) + } + pub fn get_mut(&mut self) -> Option<&mut T> { + Some(unsafe { core::ptr::read_volatile(self.inner.get())?.as_mut() }) + } +} +impl Response { + pub const fn none() -> Self { + Self { + inner: UnsafeCell::new(None), + } + } +} + +/// Request the name and version of the loading bootloader. +/// +/// # Usage +/// ```rust +/// # use limine::{request::BootloaderInfoRequest, response::BootloaderInfoResponse, BaseRevision}; +/// static BASE_REVISION: BaseRevision = BaseRevision::new(); +/// +/// // Request the bootloader info +/// static BOOTLOADER_INFO_REQUEST: BootloaderInfoRequest = BootloaderInfoRequest::new(); +/// +/// # fn dummy<'a>() -> Option<&'a BootloaderInfoResponse> { +/// // ...later, in our code +/// BOOTLOADER_INFO_REQUEST.get_response() // ... +/// # } +/// ``` +#[repr(C)] +pub struct BootloaderInfoRequest { + id: [u64; 4], + revision: u64, + response: Response, +} +impl BootloaderInfoRequest { + impl_base_fns!( + 0, + BootloaderInfoResponse, + magic!(0xf55038d8e2a1202f, 0x279426fcf5f59740), + {} + ); +} + +/// Request a differently-sized stack. +/// +/// # Usage +/// ``` +/// # use limine::{request::StackSizeRequest, response::StackSizeResponse, BaseRevision}; +/// static BASE_REVISION: BaseRevision = BaseRevision::new(); +/// +/// // Request a 128 KiB stack +/// static STACK_SIZE_REQUEST: StackSizeRequest = StackSizeRequest::new().with_size(0x32000); +/// +/// # fn dummy<'a>() -> Option<&'a StackSizeResponse> { +/// // ...later, in our code +/// STACK_SIZE_REQUEST.get_response() // ... +/// # } +/// ``` +#[repr(C)] +pub struct StackSizeRequest { + id: [u64; 4], + revision: u64, + response: Response, + size: u64, +} +impl StackSizeRequest { + impl_base_fns!( + 0, + StackSizeResponse, + magic!(0x224ef0460a8e8926, 0xe1cb0fc25f46ea3d), + { + size: 0, + } + ); + + setter!( + /// Set the requested stack size, in bytes. + u64, + set_size, + with_size, + size + ); + + /// Get the requested stack size, in bytes. + pub fn size(&self) -> u64 { + self.size + } +} + +/// Request information about the higher-half direct mapping. +/// +/// # Usage +/// ```rust +/// # use limine::{request::HhdmRequest, response::HhdmResponse, BaseRevision}; +/// static BASE_REVISION: BaseRevision = BaseRevision::new(); +/// +/// // Request the higher-half direct mapping +/// static HHDM_REQUEST: HhdmRequest = HhdmRequest::new(); +/// +/// # fn dummy<'a>() -> Option<&'a HhdmResponse> { +/// // ...later, in our code +/// HHDM_REQUEST.get_response() // ... +/// # } +/// ``` +#[repr(C)] +pub struct HhdmRequest { + id: [u64; 4], + revision: u64, + response: Response, +} +impl HhdmRequest { + impl_base_fns!( + 0, + HhdmResponse, + magic!(0x48dcf1cb8ad2b852, 0x63984e959a98244b), + {} + ); +} + +/// Request a framebuffer for graphics output. +/// +/// # Usage +/// ```rust +/// # use limine::{request::FramebufferRequest, response::FramebufferResponse, BaseRevision}; +/// static BASE_REVISION: BaseRevision = BaseRevision::new(); +/// +/// // Request a framebuffer +/// static FRAMEBUFFER_REQUEST: FramebufferRequest = FramebufferRequest::new(); +/// +/// # fn dummy<'a>() -> Option<&'a FramebufferResponse> { +/// // ...later, in our code +/// FRAMEBUFFER_REQUEST.get_response() // ... +/// # } +#[repr(C)] +pub struct FramebufferRequest { + id: [u64; 4], + revision: u64, + response: Response, +} +impl FramebufferRequest { + impl_base_fns!( + 0, + FramebufferResponse, + magic!(0x9d5827dcd881dd75, 0xa3148604f6fab11b), + {} + ); +} + +/// Request certain platform-dependent paging modes and flags to be set. +/// +/// # Usage +/// ```rust +/// # use limine::{request::PagingModeRequest, response::PagingModeResponse, paging, BaseRevision}; +/// static BASE_REVISION: BaseRevision = BaseRevision::new(); +/// +/// // Request a paging mode +/// #[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))] // x86_64 and AArch64 share the same modes +/// static PAGING_MODE_REQUEST: PagingModeRequest = PagingModeRequest::new().with_mode(paging::Mode::FOUR_LEVEL); +/// +/// #[cfg(target_arch = "riscv64")] // RISC-V has different modes +/// static PAGING_MODE_REQUEST: PagingModeRequest = PagingModeRequest::new().with_mode(paging::Mode::SV48); +/// +/// # fn dummy<'a>() -> Option<&'a PagingModeResponse> { +/// // ...later, in our code +/// PAGING_MODE_REQUEST.get_response() // ... +/// # } +#[repr(C)] +pub struct PagingModeRequest { + id: [u64; 4], + revision: u64, + response: Response, + mode: paging::Mode, + flags: paging::Flags, +} +impl PagingModeRequest { + impl_base_fns!( + 0, + PagingModeResponse, + magic!(0x95c1a0edab0944cb, 0xa4e5cb3842f7488a), + { + #[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))] + mode: paging::Mode::FOUR_LEVEL, + #[cfg(target_arch = "riscv64")] + mode: paging::Mode::SV48, + flags: paging::Flags::empty(), + } + ); + + setter!( + /// Set the requested paging mode. See [`Mode`](paging::Mode) for more + /// information. + paging::Mode, + set_mode, + with_mode, + mode + ); + setter!( + /// Set the requested paging flags. See [`Flags`](paging::Flags) for + /// more information. + paging::Flags, + set_flags, + with_flags, + flags + ); + + /// Get the requested paging mode. See [`Mode`](paging::Mode) for more + /// information. + pub fn mode(&self) -> paging::Mode { + self.mode + } + /// Get the requested paging flags. See [`Flags`](paging::Flags) for more + /// information. + pub fn flags(&self) -> paging::Flags { + self.flags + } +} + +/// **This request is deprecated and was removed from the reference +/// implementation. Use [`PagingModeRequest`] instead.** +/// +/// Request a five-level paging mode. +/// +/// # Usage +/// ```rust +/// # use limine::{request::FiveLevelPagingRequest, response::FiveLevelPagingResponse, BaseRevision}; +/// static BASE_REVISION: BaseRevision = BaseRevision::new(); +/// +/// // Request a five-level paging mode +/// static FIVE_LEVEL_PAGING_REQUEST: FiveLevelPagingRequest = FiveLevelPagingRequest::new(); +/// +/// # fn dummy<'a>() -> Option<&'a FiveLevelPagingResponse> { +/// // ...later, in our code +/// FIVE_LEVEL_PAGING_REQUEST.get_response() // ... +/// # } +/// ``` +#[repr(C)] +#[deprecated(note = "use `PagingModeRequest` instead")] +pub struct FiveLevelPagingRequest { + id: [u64; 4], + revision: u64, + response: Response, +} +#[allow(deprecated)] +impl FiveLevelPagingRequest { + impl_base_fns!( + 0, + FiveLevelPagingResponse, + magic!(0x94469551da9b3192, 0xebe5e86db7382888), + {} + ); +} + +/// Request the start of all other cores on the system, if they exist. Without +/// this request, non-bootstrap cores will be ignored. +/// +/// # Usage +/// ```rust +/// # use limine::{request::SmpRequest, response::SmpResponse, BaseRevision}; +/// static BASE_REVISION: BaseRevision = BaseRevision::new(); +/// +/// // Request that all other cores be started +/// static SMP_REQUEST: SmpRequest = SmpRequest::new(); +/// +/// # fn dummy<'a>() -> Option<&'a SmpResponse> { +/// // ...later, in our code +/// SMP_REQUEST.get_response() // ... +/// # } +/// ``` +#[repr(C)] +pub struct SmpRequest { + id: [u64; 4], + revision: u64, + response: Response, + flags: smp::RequestFlags, +} +impl SmpRequest { + impl_base_fns!( + 0, + SmpResponse, + magic!(0x95a67b819a1b857e, 0xa0b61b723b6a73e0), + { + flags: smp::RequestFlags::empty(), + } + ); + + setter!( + /// Set the SMP request flags. See [`RequestFlags`](smp::RequestFlags) + /// for more information. + smp::RequestFlags, + set_flags, + with_flags, + flags + ); + + /// Get the SMP request flags. See [`RequestFlags`](smp::RequestFlags) for + /// more information. + pub fn flags(&self) -> smp::RequestFlags { + self.flags + } +} + +/// Request limine's memory map. This may or may not be the same as the one +/// given by the BIOS/UEFI firmware. Entries are guaranteed to be in order of +/// their base address. Usable and bootloader-reclaimable memory regions will +/// never overlap, and will always be 4096-byte aligned. Other region types have +/// no such guarantees. Some holes may exist. Memory between 0x0-0x1000 is never +/// marked as usable. +/// +/// # Usage +/// ```rust +/// # use limine::{request::MemoryMapRequest, response::MemoryMapResponse, BaseRevision}; +/// static BASE_REVISION: BaseRevision = BaseRevision::new(); +/// +/// // Request a memory map +/// static MEMORY_MAP_REQUEST: MemoryMapRequest = MemoryMapRequest::new(); +/// +/// # fn dummy<'a>() -> Option<&'a MemoryMapResponse> { +/// // ...later, in our code +/// MEMORY_MAP_REQUEST.get_response() // ... +/// # } +/// ``` +#[repr(C)] +pub struct MemoryMapRequest { + id: [u64; 4], + revision: u64, + response: Response, +} +impl MemoryMapRequest { + impl_base_fns!( + 0, + MemoryMapResponse, + magic!(0x67cf3d9d378a806f, 0xe304acdfc50c3c62), + {} + ); +} + +/// Requests limine to use a specific function as the kernel entry point, +/// instead of the one specified in the ELF. +#[repr(C)] +pub struct EntryPointRequest { + id: [u64; 4], + revision: u64, + response: Response, + entry_point: extern "C" fn() -> !, +} +impl EntryPointRequest { + impl_base_fns!( + 0, + EntryPointResponse, + magic!(0x13d86c035a1cd3e1, 0x2b0caa89d8f3026a), + { + entry_point: { + extern "C" fn dummy() -> ! { + loop { + core::hint::spin_loop(); + } + } + dummy + }, + } + ); + + setter!( + /// Set the entry point function. The function must never return. + extern "C" fn() -> !, + set_entry_point, + with_entry_point, + entry_point + ); + + /// Get the entry point function. The function must never return. + pub fn entry_point(&self) -> extern "C" fn() -> ! { + self.entry_point + } +} + +/// Request information about the loaded kernel file. See [`File`](file::File) +/// for more information. +/// +/// # Usage +/// ```rust +/// # use limine::{request::KernelFileRequest, response::KernelFileResponse, BaseRevision}; +/// static BASE_REVISION: BaseRevision = BaseRevision::new(); +/// +/// // Request information about the kernel file +/// static KERNEL_FILE_REQUEST: KernelFileRequest = KernelFileRequest::new(); +/// +/// # fn dummy<'a>() -> Option<&'a KernelFileResponse> { +/// // ...later, in our code +/// KERNEL_FILE_REQUEST.get_response() // ... +/// # } +/// ``` +#[repr(C)] +pub struct KernelFileRequest { + id: [u64; 4], + revision: u64, + response: Response, +} +impl KernelFileRequest { + impl_base_fns!( + 0, + KernelFileResponse, + magic!(0xad97e90e83f1ed67, 0x31eb5d1c5ff23b69), + {} + ); +} + +/// Request information about the loaded modules. See [`File`](file::File) for +/// more information. +/// +/// Additionally, with revision 1, this request can be used to specify +/// additional modules to be loaded without being specified in the configuration +/// file. +/// +/// # Revisions +/// - Revision 0: Initial revision. +/// - Revision 1: added `internal_modules` +/// +/// # Usage +/// ```rust +/// # use limine::{request::ModuleRequest, response::ModuleResponse, modules::*, BaseRevision}; +/// static BASE_REVISION: BaseRevision = BaseRevision::new(); +/// +/// // Try to load our own module +/// static MODULE_REQUEST: ModuleRequest = +/// ModuleRequest::new().with_internal_modules(&[ +/// &InternalModule::new().with_path(limine::cstr!("/path/to/a/module")) +/// ]); +/// +/// # fn dummy<'a>() -> Option<&'a ModuleResponse> { +/// // ...later, in our code +/// MODULE_REQUEST.get_response() // ... +/// # } +/// ``` +#[repr(C)] +pub struct ModuleRequest { + id: [u64; 4], + revision: u64, + response: Response, + + // Revision 1+ + internal_module_ct: u64, + internal_modules: *const *const InternalModule, +} +unsafe impl Sync for ModuleRequest {} +unsafe impl Send for ModuleRequest {} +impl ModuleRequest { + impl_base_fns!( + 1, + ModuleResponse, + magic!(0x3e7e279702be32af, 0xca1c4f3bd1280cee), + { + internal_module_ct: 0, + internal_modules: core::ptr::null(), + } + ); + + /// Seet the internal modules to be loaded. Only available on revision 1+. + /// This function operates in place. + /// + /// # Parameters + /// - `modules`: The new value of the field. + pub fn set_internal_modules(&mut self, modules: &'static [&'static InternalModule]) { + self.internal_module_ct = modules.len() as u64; + self.internal_modules = modules.as_ptr().cast(); + } + + /// Set the internal modules to be loaded. Only available on revision 1+. + /// This function returns the new value. + /// + /// # Parameters + /// - `modules`: The new value of the field. + pub const fn with_internal_modules( + mut self, + modules: &'static [&'static InternalModule], + ) -> Self { + self.internal_module_ct = modules.len() as u64; + self.internal_modules = modules.as_ptr().cast(); + self + } + + /// Get the internal modules to be loaded. Only available on revision 1. + pub fn internal_modules(&self) -> &[&InternalModule] { + unsafe { + core::slice::from_raw_parts( + self.internal_modules.cast(), + self.internal_module_ct as usize, + ) + } + } +} + +/// Request the RSDP address. +/// +/// # Usage +/// ```rust +/// # use limine::{request::RsdpRequest, response::RsdpResponse, BaseRevision}; +/// static BASE_REVISION: BaseRevision = BaseRevision::new(); +/// +/// // Request the RSDP address +/// static Rsdp_REQUEST: RsdpRequest = RsdpRequest::new(); +/// +/// # fn dummy<'a>() -> Option<&'a RsdpResponse> { +/// // ...later, in our code +/// Rsdp_REQUEST.get_response() // ... +/// # } +/// ``` +#[repr(C)] +pub struct RsdpRequest { + id: [u64; 4], + revision: u64, + response: Response, +} +impl RsdpRequest { + impl_base_fns!( + 0, + RsdpResponse, + magic!(0xc5e77b6b397e7b43, 0x27637845accdcf3c), + {} + ); +} + +/// Request the address of the SMBIOS table. +/// +/// # Usage +/// ```rust +/// # use limine::{request::SmbiosRequest, response::SmbiosResponse, BaseRevision}; +/// static BASE_REVISION: BaseRevision = BaseRevision::new(); +/// +/// // Request the SMBIOS table address +/// static SMBIOS_REQUEST: SmbiosRequest = SmbiosRequest::new(); +/// +/// # fn dummy<'a>() -> Option<&'a SmbiosResponse> { +/// // ...later, in our code +/// SMBIOS_REQUEST.get_response() // ... +/// # } +/// ``` +#[repr(C)] +pub struct SmbiosRequest { + id: [u64; 4], + revision: u64, + response: Response, +} +impl SmbiosRequest { + impl_base_fns!( + 0, + SmbiosResponse, + magic!(0x9e9046f11e095391, 0xaa4a520fefbde5ee), + {} + ); +} + +/// Request the address of the UEFI system table. +/// +/// # Usage +/// ```rust +/// # use limine::{request::EfiSystemTableRequest, response::EfiSystemTableResponse, BaseRevision}; +/// static BASE_REVISION: BaseRevision = BaseRevision::new(); +/// +/// // Request the UEFI system table address +/// static EFI_SYSTEM_TABLE_REQUEST: EfiSystemTableRequest = EfiSystemTableRequest::new(); +/// +/// # fn dummy<'a>() -> Option<&'a EfiSystemTableResponse> { +/// // ...later, in our code +/// EFI_SYSTEM_TABLE_REQUEST.get_response() // ... +/// # } +/// ``` +#[repr(C)] +pub struct EfiSystemTableRequest { + id: [u64; 4], + revision: u64, + response: Response, +} +impl EfiSystemTableRequest { + impl_base_fns!( + 0, + EfiSystemTableResponse, + magic!(0x5ceba5163eaaf6d6, 0x0a6981610cf65fcc), + {} + ); +} + +/// Request the address of the UEFI memory map. +/// +/// # Usage +/// ```rust +/// # use limine::{request::EfiMemoryMapRequest, response::EfiMemoryMapResponse, BaseRevision}; +/// static BASE_REVISION: BaseRevision = BaseRevision::new(); +/// +/// // Request the UEFI memory map address +/// static EFI_MEMORY_MAP_REQUEST: EfiMemoryMapRequest = EfiMemoryMapRequest::new(); +/// +/// # fn dummy<'a>() -> Option<&'a EfiMemoryMapResponse> { +/// // ...later, in our code +/// EFI_MEMORY_MAP_REQUEST.get_response() // ... +/// # } +/// ``` +#[repr(C)] +pub struct EfiMemoryMapRequest { + id: [u64; 4], + revision: u64, + response: Response, +} +impl EfiMemoryMapRequest { + impl_base_fns!( + 0, + EfiMemoryMapResponse, + magic!(0x7df62a431d6872d5, 0xa4fcdfb3e57306c8), + {} + ); +} + +/// Request the boot time in seconds +/// +/// # Usage +/// ```rust +/// # use limine::{request::BootTimeRequest, response::BootTimeResponse, BaseRevision}; +/// static BASE_REVISION: BaseRevision = BaseRevision::new(); +/// +/// // Request the boot time +/// static BOOT_TIME_REQUEST: BootTimeRequest = BootTimeRequest::new(); +/// +/// # fn dummy<'a>() -> Option<&'a BootTimeResponse> { +/// // ...later, in our code +/// BOOT_TIME_REQUEST.get_response() // ... +/// # } +/// ``` +#[repr(C)] +pub struct BootTimeRequest { + id: [u64; 4], + revision: u64, + response: Response, +} +impl BootTimeRequest { + impl_base_fns!( + 0, + BootTimeResponse, + magic!(0x502746e184c088aa, 0xfbc5ec83e6327893), + {} + ); +} + +/// Request the base address of the kernel code, in virtual and physical space. +/// +/// # Usage +/// ```rust +/// # use limine::{request::KernelAddressRequest, response::KernelAddressResponse, BaseRevision}; +/// static BASE_REVISION: BaseRevision = BaseRevision::new(); +/// +/// // Request the kernel address +/// static KERNEL_ADDRESS_REQUEST: KernelAddressRequest = KernelAddressRequest::new(); +/// +/// # fn dummy<'a>() -> Option<&'a KernelAddressResponse> { +/// // ...later, in our code +/// KERNEL_ADDRESS_REQUEST.get_response() // ... +/// # } +/// ``` +#[repr(C)] +pub struct KernelAddressRequest { + id: [u64; 4], + revision: u64, + response: Response, +} +impl KernelAddressRequest { + impl_base_fns!( + 0, + KernelAddressResponse, + magic!(0x71ba76863cc55f63, 0xb2644a48c516a487), + {} + ); +} + +/// Request the address of the device-tree blob, if one is present. +/// +/// # Usage +/// ```rust +/// # use limine::{request::DeviceTreeBlobRequest, response::DeviceTreeBlobResponse, BaseRevision}; +/// static BASE_REVISION: BaseRevision = BaseRevision::new(); +/// +/// // Request the device-tree blob address +/// static DEVICE_TREE_BLOB_REQUEST: DeviceTreeBlobRequest = DeviceTreeBlobRequest::new(); +/// +/// # fn dummy<'a>() -> Option<&'a DeviceTreeBlobResponse> { +/// // ...later, in our code +/// DEVICE_TREE_BLOB_REQUEST.get_response() // ... +/// # } +/// ``` +#[repr(C)] +pub struct DeviceTreeBlobRequest { + id: [u64; 4], + revision: u64, + response: Response, +} +impl DeviceTreeBlobRequest { + impl_base_fns!( + 0, + DeviceTreeBlobResponse, + magic!(0xb40ddb48fb54bac7, 0x545081493f81ffb7), + {} + ); +} diff --git a/src/response.rs b/src/response.rs new file mode 100644 index 0000000..8bb4e69 --- /dev/null +++ b/src/response.rs @@ -0,0 +1,438 @@ +//! Responses to [requests](crate::request). + +use core::{ + ffi::{c_char, c_void, CStr}, + ptr::NonNull, + time::Duration, +}; + +use crate::{ + file, + framebuffer::{Framebuffer, RawFramebuffer}, + memory_map, + paging::{Flags, Mode}, + smp, +}; + +macro_rules! impl_base_fns { + () => { + /// Returns the revision of the response. + pub fn revision(&self) -> u64 { + self.revision + } + }; +} + +/// A response to a [bootloader info +/// request](crate::request::BootloaderInfoRequest). +#[repr(C)] +pub struct BootloaderInfoResponse { + revision: u64, + name: *const c_char, + version: *const c_char, +} +unsafe impl Sync for BootloaderInfoResponse {} +unsafe impl Send for BootloaderInfoResponse {} +impl BootloaderInfoResponse { + impl_base_fns!(); + + /// Returns the name of the loading bootloader. + pub fn name(&self) -> &str { + unsafe { CStr::from_ptr(self.name) }.to_str().unwrap() + } + + /// Returns the version of the loading bootloader. + pub fn version(&self) -> &str { + unsafe { CStr::from_ptr(self.version) }.to_str().unwrap() + } +} + +/// A response to a [stack size request](crate::request::StackSizeRequest). This +/// response has no fields. If it is provided, the bootloader complied with the +/// request. +#[repr(C)] +pub struct StackSizeResponse { + revision: u64, +} +impl StackSizeResponse { + impl_base_fns!(); +} + +/// A response to a [higher-half direct map +/// request](crate::request::HhdmRequest). +#[repr(C)] +pub struct HhdmResponse { + revision: u64, + offset: u64, +} +impl HhdmResponse { + impl_base_fns!(); + + /// Returns the offset of the higher-half direct map. This can be used to + /// convert physical addresses to virtual addresses, and the same in + /// reverse, as long as the bootloader's page tables are still in use. + /// + /// # Examples + /// Convert physical to virtual: + /// ```rust + /// # let offset = 42; + /// # let phys_addr = 42; + /// let virt_addr = phys_addr + offset; + /// ``` + /// Convert virtual† to physical: + /// ```rust + /// # let offset = 42; + /// # let virt_addr = 42; + /// let phys_addr = virt_addr - offset; + /// ``` + /// + /// † Note that this only works if the virtual address is in the higher-half + /// direct map already. This is true of any virtual address returned in + /// any response. However, this is not true of addresses within executable + /// code. To convert an address within executable code, you must use the + /// [kernel address request](crate::request::KernelAddressRequest). + pub fn offset(&self) -> u64 { + self.offset + } +} + +/// A response to a [framebuffer request](crate::request::FramebufferRequest). +#[repr(C)] +pub struct FramebufferResponse { + revision: u64, + framebuffer_ct: u64, + framebuffers: *const *const RawFramebuffer, +} +unsafe impl Sync for FramebufferResponse {} +unsafe impl Send for FramebufferResponse {} +impl FramebufferResponse { + impl_base_fns!(); + + /// Returns an iterator over the found framebuffers. See [`Framebuffer`] for + /// more information. + pub fn framebuffers(&self) -> impl Iterator> { + (unsafe { core::slice::from_raw_parts(self.framebuffers, self.framebuffer_ct as usize) }) + .iter() + .map(|&fb| Framebuffer::new(self.revision, unsafe { &*fb })) + } +} + +/// A response to a [paging mode request](crate::request::PagingModeRequest). +#[repr(C)] +pub struct PagingModeResponse { + revision: u64, + mode: Mode, + flags: Flags, +} +impl PagingModeResponse { + impl_base_fns!(); + + /// Returns mode that was enabled by the bootloader. See [`Mode`] for more + /// information. + pub fn mode(&self) -> Mode { + self.mode + } + /// Returns the flags that were enabled by the bootloader. See [`Flags`] for + /// more information. + pub fn flags(&self) -> Flags { + self.flags + } +} + +/// **This request is deprecated and was removed from the reference +/// implementation. Use [`PagingModeRequest`](crate::request::PagingModeRequest) +/// instead.** +/// +/// A response to a [five-level paging +/// request](crate::request::FiveLevelPagingRequest). This response has no +/// fields. If it is provided, five-level paging is supported and enabled. +#[repr(C)] +pub struct FiveLevelPagingResponse { + revision: u64, +} +impl FiveLevelPagingResponse { + impl_base_fns!(); +} + +/// A response to a [smp request](crate::request::SmpRequest). This response +/// contains information about the boot processor and all other processors. +#[repr(C)] +pub struct SmpResponse { + revision: u64, + flags: smp::ResponseFlags, + #[cfg(target_arch = "x86_64")] + bsp_lapic_id: u32, + #[cfg(target_arch = "aarch64")] + bsp_mpidr: u64, + #[cfg(target_arch = "riscv64")] + bsp_hartid: u64, + cpu_ct: u64, + cpus: *mut *mut smp::Cpu, +} +unsafe impl Sync for SmpResponse {} +unsafe impl Send for SmpResponse {} +impl SmpResponse { + impl_base_fns!(); + + /// Returns the flags that were enabled by the bootloader. See + /// [`ResponseFlags`](smp::ResponseFlags) for more information. + pub fn flags(&self) -> smp::ResponseFlags { + self.flags + } + + /// Returns the local APIC ID of the boot processor. This is only available + /// on x86_64. + #[cfg(target_arch = "x86_64")] + pub fn bsp_lapic_id(&self) -> u32 { + self.bsp_lapic_id + } + + /// Returns the value of the MPIDR on the boot processor. This is only + /// available on aarch64. + #[cfg(target_arch = "aarch64")] + pub fn bsp_mpidr(&self) -> u64 { + self.bsp_mpidr + } + + /// Returns the hart ID of the boot processor. This is only available on + /// riscv64. + #[cfg(target_arch = "riscv64")] + pub fn bsp_hartid(&self) -> u64 { + self.bsp_hartid + } + + /// Returns a slice of found CPUs. See [`Cpu`] for more information. + pub fn cpus(&self) -> &[&smp::Cpu] { + unsafe { core::slice::from_raw_parts(self.cpus.cast(), self.cpu_ct as usize) } + } + + /// Returns a mutable slice of found CPUs. See [`Cpu`] for more information. + /// Note that this function takes `&mut self`, so the response will likely + /// need to be wrapped in a `Mutex` or similar. It is provided so that the + /// `extra` field on each CPU can be set. + pub fn cpus_mut(&mut self) -> &mut [&mut smp::Cpu] { + unsafe { core::slice::from_raw_parts_mut(self.cpus.cast(), self.cpu_ct as usize) } + } +} + +/// A response to a [memory map request](crate::request::MemoryMapRequest). +#[repr(C)] +pub struct MemoryMapResponse { + revision: u64, + entry_ct: u64, + entries: *mut *mut memory_map::Entry, +} +unsafe impl Sync for MemoryMapResponse {} +unsafe impl Send for MemoryMapResponse {} +impl MemoryMapResponse { + impl_base_fns!(); + + /// Returns a slice of found memory map entries. See + /// [`Entry`](memory_map::Entry) for more information. + pub fn entries(&self) -> &[&memory_map::Entry] { + unsafe { core::slice::from_raw_parts(self.entries.cast(), self.entry_ct as usize) } + } + + /// Returns a mutable slice of found memory map entries. See + /// [`Entry`](memory_map::Entry) for more information. Note that this + /// function takes `&mut self`, so the response will likely need to be + /// wrapped in a `Mutex` or similar. + pub fn entries_mut(&mut self) -> &mut [&mut memory_map::Entry] { + unsafe { core::slice::from_raw_parts_mut(self.entries.cast(), self.entry_ct as usize) } + } +} + +/// A response to a [kernel file request](crate::request::KernelFileRequest). +#[repr(C)] +pub struct EntryPointResponse { + revision: u64, +} +impl EntryPointResponse { + impl_base_fns!(); +} + +/// A response to a [kernel file request](crate::request::KernelFileRequest). +#[repr(C)] +pub struct KernelFileResponse { + revision: u64, + file: *const file::File, +} +unsafe impl Sync for KernelFileResponse {} +unsafe impl Send for KernelFileResponse {} +impl KernelFileResponse { + impl_base_fns!(); + + /// Returns the kernel file. See [`File`](file::File) for more information. + pub fn file(&self) -> &file::File { + unsafe { &*self.file } + } +} + +/// A response to a [module request](crate::request::ModuleRequest). +#[repr(C)] +pub struct ModuleResponse { + revision: u64, + module_ct: u64, + modules: *const *const file::File, +} +unsafe impl Sync for ModuleResponse {} +unsafe impl Send for ModuleResponse {} +impl ModuleResponse { + impl_base_fns!(); + + /// Returns a slice of loaded modules. See [`File`](file::File) for more + /// information. + pub fn modules(&self) -> &[&file::File] { + unsafe { core::slice::from_raw_parts(self.modules.cast(), self.module_ct as usize) } + } +} + +/// A response to a [rsdp request](crate::request::RsdpRequest). +#[repr(C)] +pub struct RsdpResponse { + revision: u64, + address: *const c_void, +} +unsafe impl Sync for RsdpResponse {} +unsafe impl Send for RsdpResponse {} +impl RsdpResponse { + impl_base_fns!(); + + /// Returns the address of the RSDP table in the ACPI. + pub fn address(&self) -> *const () { + self.address.cast() + } +} + +/// A response to a [smbios request](crate::request::SmbiosRequest). +#[repr(C)] +pub struct SmbiosResponse { + revision: u64, + entry_32: Option>, + entry_64: Option>, +} +unsafe impl Sync for SmbiosResponse {} +unsafe impl Send for SmbiosResponse {} +impl SmbiosResponse { + impl_base_fns!(); + + /// Returns the address of the SMBIOS 32-bit entry point, if it exists. + pub fn entry_32(&self) -> Option> { + self.entry_32 + } + /// Returns the address of the SMBIOS 64-bit entry point, if it exists. + pub fn entry_64(&self) -> Option> { + self.entry_64 + } +} + +/// A response to a [system table request](crate::request::EfiSystemTableRequest). +#[repr(C)] +pub struct EfiSystemTableResponse { + revision: u64, + address: *const c_void, +} +unsafe impl Sync for EfiSystemTableResponse {} +unsafe impl Send for EfiSystemTableResponse {} +impl EfiSystemTableResponse { + impl_base_fns!(); + + /// Returns the address of the EFI system table. + pub fn address(&self) -> *const () { + self.address.cast() + } +} + +/// A response to a [memory map request](crate::request::EfiMemoryMapRequest). +#[repr(C)] +pub struct EfiMemoryMapResponse { + revision: u64, + memmap: *const c_void, + memmap_size: u64, + desc_size: u64, + desc_version: u32, +} +unsafe impl Sync for EfiMemoryMapResponse {} +unsafe impl Send for EfiMemoryMapResponse {} +impl EfiMemoryMapResponse { + impl_base_fns!(); + + /// Returns the address of the EFI memory map. + pub fn memmap(&self) -> *const () { + self.memmap.cast() + } + /// Returns the size of the EFI memory map. + pub fn memmap_size(&self) -> u64 { + self.memmap_size + } + + /// Returns the size of each EFI memory map entry. + pub fn desc_size(&self) -> u64 { + self.desc_size + } + /// Returns the version of each EFI memory map entry. + pub fn desc_version(&self) -> u32 { + self.desc_version + } +} + +/// A response to a [boot time request](crate::request::BootTimeRequest). +#[repr(C)] +pub struct BootTimeResponse { + revision: u64, + boot_time: i64, +} +impl BootTimeResponse { + impl_base_fns!(); + + /// Returns the boot time in seconds, as read from the system RTC. + pub fn boot_time(&self) -> Duration { + Duration::from_secs(self.boot_time as u64) + } +} + +/// A response to a [kernel address request](crate::request::KernelAddressRequest). +/// +/// This can be used to convert a virtual address within the kernel to a +/// physical address like so: +/// ```rust +/// # let virt_addr = 42; +/// # let virtual_base = 42; +/// # let physical_base = 42; +/// let phys_addr = virt_addr - virtual_base + physical_base; +/// ```` +#[repr(C)] +pub struct KernelAddressResponse { + revision: u64, + physical_base: u64, + virtual_base: u64, +} +impl KernelAddressResponse { + impl_base_fns!(); + + /// Returns the base address of the kernel in physical memory. + pub fn physical_base(&self) -> u64 { + self.physical_base + } + /// Returns the base address of the kernel in virtual memory. + pub fn virtual_base(&self) -> u64 { + self.virtual_base + } +} + +/// A response to a [device tree blob request](crate::request::DeviceTreeBlobRequest). +#[repr(C)] +pub struct DeviceTreeBlobResponse { + revision: u64, + dtb_ptr: *const c_void, +} +unsafe impl Sync for DeviceTreeBlobResponse {} +unsafe impl Send for DeviceTreeBlobResponse {} +impl DeviceTreeBlobResponse { + impl_base_fns!(); + + /// Returns the address of the device tree blob. + pub fn dtb_ptr(&self) -> *const () { + self.dtb_ptr.cast() + } +} diff --git a/src/smp.rs b/src/smp.rs new file mode 100644 index 0000000..45f5f90 --- /dev/null +++ b/src/smp.rs @@ -0,0 +1,101 @@ +//! Auxiliary types for the [SMP request](crate::request::SmpRequest). + +use core::sync::atomic::{AtomicPtr, Ordering}; + +use bitflags::bitflags; + +/// A function pointer that the core will jump to when it is written to. +#[repr(transparent)] +pub struct GotoAddress { + inner: AtomicPtr<()>, +} +impl GotoAddress { + /// Set the goto address pointer. This will cause the core to jump to the + /// given function. This function also synchronizes all writes, so anything + /// written before this function returns is guaranteed to be seen before the + /// core jumps to the given function. + pub fn write(&self, func: unsafe extern "C" fn(&Cpu) -> !) { + self.inner.store(func as *mut (), Ordering::Release); + } +} + +/// A CPU entry in the SMP request. +#[cfg(target_arch = "x86_64")] +#[repr(C)] +pub struct Cpu { + /// The ACPI processor ID, according to the ACPI MADT. + pub id: u32, + /// The APIC ID, according to the ACPI MADT. + pub lapic_id: u32, + _reserved: core::mem::MaybeUninit, + /// The address to jump to. Writing to this field will cause the core to + /// jump to the given function. The function will receive a pointer to this + /// structure, and it will have its own 64KiB (or requested-size) stack. + pub goto_address: GotoAddress, + /// Free for use by the kernel. + pub extra: u64, +} + +/// A CPU entry in the SMP request. +#[cfg(target_arch = "aarch64")] +#[repr(C)] +pub struct Cpu { + /// The ACPI processor ID, according to the ACPI MADT. + pub id: u32, + /// The GIC interface number, according to the ACPI MADT. + pub gic_iface_no: u32, + /// The MPIDR of the CPU, according to the ACPI MADT or the device tree. + pub mpidr: u64, + _reserved: core::mem::MaybeUninit, + /// The address to jump to. Writing to this field will cause the core to + /// jump to the given function. The function will receive a pointer to this + /// structure, and it will have its own 64KiB (or requested-size) stack. + pub goto_address: GotoAddress, + /// Free for use by the kernel. + pub extra: u64, +} + +/// A CPU entry in the SMP request. +#[cfg(target_arch = "riscv64")] +#[repr(C)] +pub struct Cpu { + /// The ACPI processor ID, according to the ACPI MADT. + pub id: u64, + /// The hart ID, according to the ACPI MADT or the device tree. + pub hartid: u64, + _reserved: core::mem::MaybeUninit, + /// The address to jump to. Writing to this field will cause the core to + /// jump to the given function. The function will receive a pointer to this + /// structure, and it will have its own 64KiB (or requested-size) stack. + pub goto_address: GotoAddress, + /// Free for use by the kernel. + pub extra: u64, +} + +bitflags! { + /// Flags for the [SMP request](crate::request::SmpRequest). + #[derive(Default, Clone, Copy)] + pub struct RequestFlags: u64 { + /// Initialize the X2APIC. + #[cfg(target_arch = "x86_64")] + const X2APIC = 1 << 0; + } +} + +#[cfg(target_arch = "x86_64")] +bitflags! { + /// Flags for the [SMP response](crate::response::SmpResponse). + #[derive(Default, Clone, Copy)] + pub struct ResponseFlags: u32 { + /// The X2APIC was initialized. + #[cfg(target_arch = "x86_64")] + const X2APIC = 1 << 0; + } +} + +#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] +bitflags! { + /// Flags for the [SMP response](crate::response::SmpResponse). + #[derive(Default, Clone, Copy)] + pub struct ResponseFlags: u64 {} +}