Compare commits

..

No commits in common. "ea5ad2ced09a95ac8d5d8bb0b2cb85162598aad4" and "83d1ce9b9806fd0ef17def6e436138381472ee87" have entirely different histories.

14 changed files with 55 additions and 2093 deletions

View file

@ -117,25 +117,24 @@ As is, attempting to build this code will throw linker errors, because cargo wil
"disable-redzone": true,
"target-endian": "little",
"features": "-mmx,-sse,-sse2,-sse3,-ssse3,-sse4.1,-sse4.2,-avx,-avx2,+soft-float",
"linker": "rust-lld",
"linker-flavor": "ld.lld",
"linker": "mold",
"linker-flavor": "gnu",
"llvm-target": "x86_64-unknown-none-elf",
"max-atomic-width": 64,
"panic-strategy": "abort",
"plt-by-default": false,
"os": "none",
"executables": true,
"position-independent-executables": true,
"relro-level": "full",
"rustc-abi": "softfloat",
"stack-probes": {
"kind": "inline"
},
"static-position-independent-executables": true,
"supported-sanitizers": [
"kcfi",
"kernel-address"
],
"target-pointer-width": 64,
"target-c-int-width": 32
"target-pointer-width": 64
}
#+end_src
@ -149,8 +148,6 @@ More options available in the current rustc can be found by running the followin
rustc -Zunstable-options --print target-spec-json-schema
#+end_src
Notably, I have not copied the =*position-independent-executables= fields from the =x86_64-unknown-none-elf= target, because we will later use a linker script to place our kernel in the higher-half of the address space and modify the layout of our sections manually, and position independent executables complicate this.
If we now attempt to build our kernel, cargo will now complain that it cannot find the =core= crate since it is not available for our custom target:
#+begin_src bash
cargo -Zjson-target-spec build --target x86_64-unknown-kernel.json
@ -162,12 +159,9 @@ cargo -Zjson-target-spec build --target x86_64-unknown-kernel.json
#+begin_src toml
[unstable]
json-target-spec = true # lets us specify a custom target specification file
build-std-features = ["compiler-builtins-mem"]
build-std = ["core", "compiler_builtins"]
#+end_src
Because we plan on using builtin functions like =memcpy=, we tell cargo to build the =compiler_builtins= crate with the =mem= feature enabled.
Rust permits the user to abort the program at any time by calling the =panic!= macro, or a function that “panics” internally. Typically, the mechanism by which this happens, as well as the more complex behaviour of unwinding the stack and catching unwinds, is provided by the =std=. Since we’ve opted out of using =std=, we will need to provide a panic handler for rust to call in case of a panic. Unwinding is already disabled by the =panic-strategy= field in our target specification file.
For the time being, we will again simply spin in an infinite loop whenever a panic occurs:
@ -351,372 +345,4 @@ If we now run =cargo run=, cargo will build our kernel and automatically start q
Unfortunately, since our kernel currently does nothing, this is hard to discern.
Therefore, our next goal will be to get some output from our kernel, which we will do by making use of limine’s framebuffer.
* Interlude: Welcome to kernel-land
If you have an aggressive LSP client, or you ran the =cargo clippy= command on our current crate, you may have noticed a warning telling you that an empty loop wastes CPU cycles.
This is true, and is in fact the very reason why we use it!
However, now that we have arrived in kernel-land, we can actually do a little better: the =hlt= instruction which, according to the Intel manual, stops execution of a logical processor until further notice.
Further notice here may be an interrupt, which means we will want to run the =hlt= instruction in a loop.
=hlt= is one of a handful of privileged instructions that when attempted to be executed in user-space will cause a general protection fault and terminate the program.
We start by creating a =lib.rs= file in the =src= directory, which will contain our kernel library code.
Just line in our =main.rs=, we will need to tell the compiler that we want to opt out of the standard library:
#+begin_src rust
#![no_std]
#+end_src
Since the =hlt= instruction is specific to the x86 architecture and not available in the =core::arch::x86_64= module of the core library, we will need to write our own wrapper around some inline assembly to execute the instruction.
We create a new =x86_64= module in our new library to house this and future x86-64 specific code:
#+begin_src rust
#![no_std]
pbu mod x86_64;
#+end_src
#+begin_src rust
#![cfg(target_arch = "x86_64")]
#[inline]
pub fn hlt() {
unsafe {
core::arch::asm!("hlt", options(nomem, nostack, preserves_flags));
}
}
pub fn halt_loop() -> ! {
loop {
hlt();
}
}
#+end_src
For a detailed explanation of the =asm!= macro, see the [[https://doc.rust-lang.org/nightly/reference/inline-assembly.html][Rust reference]].
In our =main.rs=, we can now replace the infinite loop with a call to our new =halt_loop= function:
#+begin_src rust
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
kernel::x86_64::halt_loop()
}
#[unsafe(no_mangle)]
fn _start() -> ! {
kernel::x86_64::halt_loop()
}
#+end_src
* Getting the Framebuffer
https://wiki.osdev.org/Limine_Bare_Bones#Compiling_the_kernel
https://github.com/Limine-Bootloader/limine-protocol/blob/trunk/PROTOCOL.md
The limine protocol, which we’ve chosen to use for our bootloader, allows our kernel to make requests at compile time, which the bootloader will then fulfil at boot time.
One of the requests we can make is for the Framebuffer with which we can draw pixels to the screen.
Limine requests generally have the following structure:
#+begin_src rust
#[repr(C)]
pub struct Request<T, U = ()> {
magic: [u64; 2],
id: [u64; 2],
revision: u64,
response: UnsafeCell<Option<NonNull<Response<T>>>>,
request: U,
}
impl<T, U> Request<T, U> {
pub const MAGIC: [u64; 2] = [0xc7b1dd30df4c8b88, 0x0a82e883a194f07b];
pub const fn new_raw(id: [u64; 2], revision: u64, request: U) -> Self {
Self {
magic: Self::MAGIC,
id,
revision,
response: UnsafeCell::new(None),
request,
}
}
pub fn response(&self) -> Option<&Response<T>> {
unsafe {
self.response
.get()
.cast::<Option<NonNull<Response<T>>>>()
.read_volatile()
}
.map(|ptr| unsafe { ptr.as_ref() })
}
}
#+end_src
A magic 128-bit number, which is the same for all requests, is followed by a 128-bit request ID which is unique to each request type. For the Framebuffer, the request ID, expressed as two 64-bit integers, is =[0x9d5827dcd881dd75, 0xa3148604f6fab11b]=.
What follows is a 64-bit revision number for the request, which allows single requests to change shape without having to change the revision of the entire protocol.
For the Framebuffer, there are currently two revisions, 0 and 1, but we are interested only in the payload of revision 1.
In the case that there are Framebuffers available, limine will write a pointer to the response structure into the =response= field of the request.
Some requests may require additional data to be read by the bootloader, which are appended to the end of the request structure, here represented by the generic type parameter =U=.
The response structure is very simple, containing only the revision number of the response the bootloader provided, followed by the request-dependent payload, which in the case of the Framebuffer is a count, and a pointer to an array of pointers to the available Framebuffers:
#+begin_src rust
#[repr(C)]
pub struct Response<T> {
revision: u64,
response: T,
}
#[repr(C)]
pub struct FramebufferResponse {
count: u64,
framebuffers: *const *const Framebuffer,
}
unsafe impl Send for FramebufferResponse {}
unsafe impl Sync for FramebufferResponse {}
impl FramebufferRequest {
pub const ID: [u64; 2] = [0x9d5827dcd881dd75, 0xa3148604f6fab11b];
pub const fn new() -> Self {
Self::new_raw(Self::ID, 0, ())
}
pub fn framebuffers<'a>(&self) -> &'a [&'a Framebuffer] {
// SAFETY: limine responses are guaranteed to be valid for the lifetime of the memory mapping.
self.response()
.map(|response| unsafe {
core::slice::from_raw_parts(
response
.response
.framebuffers
.cast::<&'a Framebuffer>(),
response.response.count as usize,
)
})
.unwrap_or(&[])
}
}
#+end_src
Limine requests, for any revision versions of the protocol greater than 1, must be placed inbetween two special magic markers in the mapping of the kernel binary.
This way, Limine will know where to find the requests and hopefully not accidentally mistake some other data in the kernel binary for a request.
#+begin_src rust
#[repr(transparent)]
pub struct RequestsStartMarker([u64; 4]);
pub const REQUESTS_START_MARKER: RequestsStartMarker = RequestsStartMarker([
0xf6b8f4b39de7d1ae,
0xfab91a6940fcb9cf,
0x785c6ed015d3e316,
0x181e920a7852b9d9,
]);
#[repr(transparent)]
pub struct RequestsEndMarker([u64; 2]);
pub const REQUESTS_END_MARKER: RequestsEndMarker =
RequestsEndMarker([0xadc0e0531bb10d03, 0x9572709f31764c62]);
#+end_src
Typically, items in Rust are placed into sections (e.g. =.text=, =.data=, =.rodata=, =.bss=) according to their type and usage.
Functions are placed in the =.text= section, which is usually marked as executable and read-only, meaning that, just like attempting to write to a =const= variable, attempting to write to a function will cause a fault.
Sometimes, one might still want to write to a function however, and then Rust offers the =link_section= attribute to place items into arbitrary sections.
A linker or other binary manipulation tools (found in the =binutils= collection of tools) can be used to inform the loader to mark the memory of that section as, for example, writable.
#+begin_src rust
#[used]
#[unsafe(link_section = ".limine_requests_start")]
static LIMINE_REQUESTS_START: RequestsStartMarker = REQUESTS_START_MARKER;
#[used]
#[unsafe(link_section = ".limine_requests")]
pub static FRAMEBUFFER_REQUEST: FramebufferRequest = FramebufferRequest::new();
#[used]
#[unsafe(link_section = ".limine_requests_end")]
static LIMINE_REQUESTS_END: RequestsEndMarker = REQUESTS_END_MARKER;
#+end_src
Since we don’t reference the =LIMINE_REQUESTS_START= and =LIMINE_REQUESTS_END= markers anywhere in our code, we tell the compiler and clippy that they are nevertheless used with the =used= attribute.
Rust already knows to place our requests into read-write memory, however the order of items within a section cannot be controlled from within Rust, and so we will use a linker script to ensure that all requests are placed between the start and end markers.
The following script is mostly taken from the =osdev.org= wiki entry on the [[https://wiki.osdev.org/Limine_Bare_Bones#Compiling_the_kernel][Limine Bare Bones example]].
#+begin_src linkerscript
OUTPUT_FORMAT(elf64-x86-64)
ENTRY(_start)
PHDRS {
limine_requests PT_LOAD;
text PT_LOAD;
rodata PT_LOAD;
data PT_LOAD;
}
SECTIONS {
. = 0xffffffff80000000;
.limine_requests : {
KEEP(*(.limine_requests_start))
KEEP(*(.limine_requests))
KEEP(*(.limine_requests_end))
} :limine_requests
. = ALIGN(CONSTANT(MAXPAGESIZE));
.text : {
*(.text .text.*)
} :text
. = ALIGN(CONSTANT(MAXPAGESIZE));
.rodata : {
*(.rodata .rodata.*)
} :rodata
. = ALIGN(CONSTANT(MAXPAGESIZE));
.data : {
*(.data .data.*)
} :data
.bss : {
*(.bss .bss.*)
*(COMMON)
} :data
/DISCARD/ : {
*(.eh_frame*)
*(.note .note.*)
}
}
#+end_src
We take the opportunity to place our kernel explicitly into the higher-half of the address space, starting at =0xffffffff80000000=.
The most important bit with respect to the limine requests is the ordering of the =KEEP= directives in the =.limine_requests= section.
After that, we need to make sure that none of the sections with different permissions (read, write, execute) are placed in the same page.
In addition to the Framebuffer, another request we will make use of is the Base Revision request, which tells us whether the bootloader supports the revision of the protocol we are using.
#+begin_src rust
#[repr(C)]
pub struct BaseRevision(UnsafeCell<[u64; 3]>);
unsafe impl Send for BaseRevision {}
unsafe impl Sync for BaseRevision {}
impl BaseRevision {
pub const fn from_revision(revision: u64) -> Self {
Self(UnsafeCell::new([
0xf9562b2d5c95a6c8,
0x6a7b384944536bdc,
revision,
]))
}
pub fn is_supported(&self) -> bool {
unsafe { self.0.get().cast::<u64>().add(2).read_volatile() == 0 }
}
pub fn base_revision(&self) -> u64 {
unsafe { self.0.get().cast::<u64>().add(2).read_volatile() }
}
}
#+end_src
Even though we don’t ever require mutable access to the internal array, we use an =UnsafeCell= to tell the compiler that the value of the array may have changed at any time and are not what it might expect them to be.
This makes the reads of the array, which require unsafe code to access the contents of the interior mutabily cell, perfectly safe as we don’t violate Rust’s aliasing rules.
Rust also assumes by default that anything containing an =UnsafeCell= is not safe to share between threads, and while our program is currently single-threaded, since we only ever read from the cell anyway, we can safely tell Rust our type is =Sync=.
This is actually required for the =BaseRevision= request to be used in a static variable, as Rust has no way of enforcing that all code that accesses the variable is running on the same thread.
The same is true also for the =Request= type, except that we have to bound the impl of =Sync= on =T= and =U= being =Sync= as well.
** Using the Framebuffer
http://www.petesqbsite.com/sections/tutorials/tuts/vbe3.pdf
https://www.ctyme.com/intr/rb-0274.htm#Table82
https://github.com/Limine-Bootloader/Limine/blob/v12.x/common/drivers/vbe.c
https://github.com/Limine-Bootloader/Limine/blob/v12.x/common/drivers/gop.c
Framebuffers have the following layout:
#+begin_src rust
#[repr(C)]
pub struct Framebuffer {
pub addr: *mut u8,
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: *mut u8,
}
#+end_src
The limine documentation on this type is somewhat sparse, but the fields are mostly self-explanatory.
Relevant to us are the =addr= field, and the fields describing how the (sub)pixels =addr= points to are laid out in memory.
=width= and =height= describe the dimensions of the framebuffer in pixels, =pitch= describes how many bytes are used for each row of pixels, and =bpp= describes how many bits are used for each pixel.
=bpp= can be assumed to be a multiple of 8. We will additionally assume that =bpp= is always at most 64.
=memory_model= is currently always =1=, meaning =LIMINE_FRAMEBUFFER_RGB=, but might in the future allow for other memory models in order to support [[https://www.ctyme.com/intr/rb-0274.htm#Table82][VESA SVGA memory model]] values other than Direct Color.
The =mask_size= and =mask_shift= fields describe how the respective color channels are to be organised with a =bpp / 8= byte memory region.
To draw an rgb-colored pixel at a given position =(x, y)= then, we will define the following function:
#+begin_src rust
pub struct Color(pub [u8; 3]);
impl Framebuffer {
/// # Safety
/// the caller must ensure they have exclusive access to the region of the
/// framebuffer they are writing to.
pub unsafe fn draw_pixel(&self, x: u64, y: u64, color: Color) {
assert!(self.bpp <= 64);
let bytes_per_pixel = self.bpp.div_ceil(8) as usize;
let [r, g, b] = color.0;
let mask_color = |c: u8, bits: u8, shift: u8| {
let mask = (1 << bits) - 1;
(c as u64 & mask) << shift
};
let mut pixel = 0u64;
pixel |= mask_color(r, self.red_mask_size, self.red_mask_shift);
pixel |= mask_color(g, self.green_mask_size, self.green_mask_shift);
pixel |= mask_color(b, self.blue_mask_size, self.blue_mask_shift);
let pixel = pixel.to_ne_bytes();
let dst = unsafe {
self.addr
.add((y * self.pitch + x * bytes_per_pixel as u64) as usize)
};
unsafe { super::volatile_copy(pixel.as_ptr(), dst, bytes_per_pixel) }
}
}
#+end_src
The method is =unsafe=, since we cannot guarantee that the call site is the only code currently attempting to write to the framebuffer yet.
=volatile_copy= is a simple utility function mirroring =core::ptr::copy=, but performing a volatile write to the destination; this is necessary because =addr= might be device mapped memory.
Inside of our =_start= function, we can now bring all of this together to draw a pretty pattern to the screen:
#+begin_src rust
#[unsafe(no_mangle)]
fn _start() -> ! {
assert!(limine_requests::LIMINE_BASE_REVISION.is_supported());
let fb = limine_requests::FRAMEBUFFER_REQUEST
.framebuffers()
.first()
.expect("No framebuffer found");
for y in 0..fb.height {
for x in 0..fb.width {
let r = (x * 255 / fb.width) as u8;
let b = (y * 255 / fb.height) as u8;
// SAFETY: We have exclusive access to the framebuffer.
unsafe {
fb.draw_pixel(x, y, kernel::limine::Color([r, 0, b]));
}
}
}
kernel::x86_64::halt_loop()
}
#+end_src

View file

@ -3,7 +3,6 @@ codegen-backend = "llvm"
[unstable]
json-target-spec = true # lets us specify a custom target specification file
build-std-features = ["compiler-builtins-mem"]
build-std = ["core", "compiler_builtins"]
[build]

View file

@ -1 +0,0 @@
((nil . ((eglot-workspace-configuration . (:rust-analyzer (:cargo (:extraEnv ((CARGO_UNSTABLE_JSON_TARGET_SPEC . "true")))))))))

9
kernel/Cargo.lock generated
View file

@ -2,15 +2,6 @@
# It is not intended for manual editing.
version = 4
[[package]]
name = "bitflags"
version = "2.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
[[package]]
name = "kernel"
version = "0.1.0"
dependencies = [
"bitflags",
]

View file

@ -3,15 +3,4 @@ name = "kernel"
version = "0.1.0"
edition = "2024"
[[bin]]
name = "kernel"
path = "src/main.rs"
test = false
bench = false
[lib]
test = false
bench = false
[dependencies]
bitflags = "2.13.1"

View file

@ -1,6 +0,0 @@
fn main() {
println!("cargo::rerun-if-changed=build.rs");
println!("cargo::rustc-link-arg-bin=kernel=-Tkernel.lds");
println!("cargo::rerun-if-changed=kernel.lds");
}

View file

@ -1,43 +0,0 @@
OUTPUT_FORMAT(elf64-x86-64)
ENTRY(_start)
PHDRS {
limine_requests PT_LOAD;
text PT_LOAD;
rodata PT_LOAD;
data PT_LOAD;
}
SECTIONS {
. = 0xffffffff80000000;
.limine_requests : {
KEEP(*(.limine_requests_start))
KEEP(*(.limine_requests))
KEEP(*(.limine_requests_end))
} :limine_requests
. = ALIGN(CONSTANT(MAXPAGESIZE));
.text : {
*(.text .text.*)
} :text
. = ALIGN(CONSTANT(MAXPAGESIZE));
.rodata : {
*(.rodata .rodata.*)
} :rodata
. = ALIGN(CONSTANT(MAXPAGESIZE));
.data : {
*(.data .data.*)
} :data
.bss : {
*(.bss .bss.*)
*(COMMON)
} :data
/DISCARD/ : {
*(.eh_frame*)
*(.note .note.*)
}
}

View file

@ -1,48 +0,0 @@
#![no_std]
#![feature(const_trait_impl, const_default)]
pub mod limine;
pub mod serial;
pub mod sync;
pub mod x86_64;
/// # Safety
pub unsafe fn volatile_copy<T: Sized>(src: *const T, dst: *mut T, count: usize) {
unsafe {
for i in 0..count {
let src_ptr = src.add(i);
let dst_ptr = dst.add(i);
let val = src_ptr.read();
dst_ptr.write_volatile(val);
}
}
}
#[macro_export]
macro_rules! drop_guard {
($($stmts:stmt)*) => {
{
struct __DropGuard<F: FnOnce()>(::core::mem::ManuallyDrop<F>);
impl<F: FnOnce()> __DropGuard<F> {
#[allow(dead_code)]
fn forget(self) {
let mut this = ::core::mem::ManuallyDrop::new(self);
unsafe {
ManuallyDrop::drop(&mut this.0);
}
}
}
impl<F: FnOnce()> Drop for __DropGuard<F> {
fn drop(&mut self) {
unsafe { ::core::ptr::read(&*self.0)() }
}
}
__DropGuard(::core::mem::ManuallyDrop::new(|| {
$($stmts)*
}))
}
};
}

View file

@ -1,167 +0,0 @@
use core::{cell::UnsafeCell, ptr::NonNull};
#[repr(C)]
pub struct BaseRevision(UnsafeCell<[u64; 3]>);
unsafe impl Sync for BaseRevision {}
impl BaseRevision {
pub const fn from_revision(revision: u64) -> Self {
Self(UnsafeCell::new([
0xf9562b2d5c95a6c8,
0x6a7b384944536bdc,
revision,
]))
}
pub fn is_supported(&self) -> bool {
unsafe { self.0.get().cast::<u64>().add(2).read_volatile() == 0 }
}
pub fn base_revision(&self) -> u64 {
unsafe { self.0.get().cast::<u64>().add(2).read_volatile() }
}
}
#[repr(transparent)]
pub struct RequestsStartMarker([u64; 4]);
pub const REQUESTS_START_MARKER: RequestsStartMarker = RequestsStartMarker([
0xf6b8f4b39de7d1ae,
0xfab91a6940fcb9cf,
0x785c6ed015d3e316,
0x181e920a7852b9d9,
]);
#[repr(transparent)]
pub struct RequestsEndMarker([u64; 2]);
pub const REQUESTS_END_MARKER: RequestsEndMarker =
RequestsEndMarker([0xadc0e0531bb10d03, 0x9572709f31764c62]);
#[repr(C)]
pub struct Request<T, U = ()> {
magic: [u64; 2],
id: [u64; 2],
revision: u64,
response: UnsafeCell<Option<NonNull<Response<T>>>>,
request: U,
}
unsafe impl<T: Sync, U: Sync> Sync for Request<T, U>{}
#[repr(C)]
pub struct Response<T> {
revision: u64,
response: T,
}
impl<T, U> Request<T, U> {
pub const MAGIC: [u64; 2] = [0xc7b1dd30df4c8b88, 0x0a82e883a194f07b];
pub const fn new_raw(id: [u64; 2], revision: u64, request: U) -> Self {
Self {
magic: Self::MAGIC,
id,
revision,
response: UnsafeCell::new(None),
request,
}
}
pub fn response(&self) -> Option<&Response<T>> {
unsafe {
self.response
.get()
.cast::<Option<NonNull<Response<T>>>>()
.read_volatile()
}
.map(|ptr| unsafe { ptr.as_ref() })
}
}
#[derive(Debug)]
#[repr(C)]
pub struct Framebuffer {
pub addr: *mut u8,
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: *mut u8,
}
pub struct Color(pub [u8; 3]);
impl Framebuffer {
/// # Safety
/// the caller must ensure they have exclusive access to the region of the
/// framebuffer they are writing to.
pub unsafe fn draw_pixel(&self, x: u64, y: u64, color: Color) {
assert!(self.bpp <= 64);
let bytes_per_pixel = self.bpp.div_ceil(8) as usize;
let [r, g, b] = color.0;
let mask_color = |c: u8, bits: u8, shift: u8| {
let mask = (1 << bits) - 1;
(c as u64 & mask) << shift
};
let mut pixel = 0u64;
pixel |= mask_color(r, self.red_mask_size, self.red_mask_shift);
pixel |= mask_color(g, self.green_mask_size, self.green_mask_shift);
pixel |= mask_color(b, self.blue_mask_size, self.blue_mask_shift);
let pixel = pixel.to_ne_bytes();
let dst = unsafe {
self.addr
.add((y * self.pitch + x * bytes_per_pixel as u64) as usize)
};
unsafe { super::volatile_copy(pixel.as_ptr(), dst, bytes_per_pixel) }
}
}
#[repr(C)]
pub struct FramebufferResponse {
count: u64,
framebuffers: *const *const Framebuffer,
}
// SAFETY: We only ever read from the framebuffer array.
unsafe impl Send for FramebufferResponse {}
unsafe impl Sync for FramebufferResponse {}
pub type FramebufferRequest = Request<FramebufferResponse>;
const impl Default for FramebufferRequest {
fn default() -> Self {
Self::new()
}
}
impl FramebufferRequest {
pub const ID: [u64; 2] = [0x9d5827dcd881dd75, 0xa3148604f6fab11b];
pub const fn new() -> Self {
Self::new_raw(Self::ID, 0, ())
}
pub fn framebuffers<'a>(&self) -> &'a [&'a Framebuffer] {
// SAFETY: limine responses are guaranteed to be valid for the lifetime of the memory mapping.
self.response()
.map(|response| unsafe {
core::slice::from_raw_parts(
response.response.framebuffers.cast::<&'a Framebuffer>(),
response.response.count as usize,
)
})
.unwrap_or(&[])
}
}

View file

@ -3,56 +3,10 @@
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
kernel::serial_println!("[PANIC] {}", _info);
kernel::x86_64::halt_loop()
loop {}
}
/// Entry point for the kernel
#[unsafe(no_mangle)]
extern "C" fn _start() -> ! {
kernel::serial_println!("Hello, world!");
assert!(limine_requests::LIMINE_BASE_REVISION.is_supported());
let fb = limine_requests::FRAMEBUFFER_REQUEST
.framebuffers()
.first()
.expect("No framebuffer found");
kernel::serial_println!("Framebuffer: {fb:#?}");
for y in 0..fb.height {
for x in 0..fb.width {
let r = (x * 255 / fb.width) as u8;
let b = (y * 255 / fb.height) as u8;
// SAFETY: We have exclusive access to the framebuffer.
unsafe {
fb.draw_pixel(x, y, kernel::limine::Color([r, 0, b]));
}
}
}
kernel::x86_64::halt_loop()
}
mod limine_requests {
use kernel::limine::{
BaseRevision, FramebufferRequest, REQUESTS_END_MARKER, REQUESTS_START_MARKER,
RequestsEndMarker, RequestsStartMarker,
};
#[used]
#[unsafe(link_section = ".limine_requests_start")]
static LIMINE_REQUESTS_START: RequestsStartMarker = REQUESTS_START_MARKER;
#[used]
#[unsafe(link_section = ".limine_requests")]
pub static LIMINE_BASE_REVISION: BaseRevision = BaseRevision::from_revision(6);
#[used]
#[unsafe(link_section = ".limine_requests")]
pub static FRAMEBUFFER_REQUEST: FramebufferRequest = FramebufferRequest::new();
#[used]
#[unsafe(link_section = ".limine_requests_end")]
static LIMINE_REQUESTS_END: RequestsEndMarker = REQUESTS_END_MARKER;
fn _start() -> ! {
loop {}
}

View file

@ -1,812 +0,0 @@
pub mod uart_16550 {
#![cfg(target_arch = "x86_64")]
use crate::x86_64::PortU16;
use core::mem::MaybeUninit;
pub struct Uart16550 {
pub base_port: PortU16,
pub baud_rate: BaudRate,
pub parity: Parity,
pub stop_bits: StopBits,
pub data_bits: DataBits,
pub fifo_trigger_level: Option<FifoTriggerLevel>,
pub interrupt_enable: InterruptEnable,
}
impl Uart16550 {
/// Transmitter/Receiver Holding Register offset
pub const DATA: u16 = 0x0;
/// Interrupt Enable Register offset
pub const IER: u16 = 0x1;
/// Interrupt Status Register offset
pub const ISR: u16 = 0x2;
/// FIFO Control Register offset
pub const FCR: u16 = 0x2;
/// Line Control Register offset
pub const LCR: u16 = 0x3;
/// Modem Control Register offset
pub const MCR: u16 = 0x4;
/// Line Status Register offset
pub const LSR: u16 = 0x5;
/// Modem Status Register offset
pub const MSR: u16 = 0x6;
/// Scratch Pad Register offset
pub const SPR: u16 = 0x7;
/// Divisor Latch Low offset
pub const DLL: u16 = 0x0;
/// Divisor Latch High offset
pub const DLM: u16 = 0x1;
/// # Safety
/// This function creates a new instance of Uart16550 with the given base port.
pub unsafe fn with_port(base_port: u16) -> Self {
Self {
base_port: PortU16(base_port),
baud_rate: BaudRate::default(),
parity: Parity::default(),
stop_bits: StopBits::default(),
data_bits: DataBits::default(),
fifo_trigger_level: Some(FifoTriggerLevel::default()),
interrupt_enable: InterruptEnable::default(),
}
}
pub fn init(&mut self) {
unsafe {
self.base_port.offset(Self::IER).write(0);
self.base_port.offset(Self::LCR).write(0);
}
self.set_baud_rate(self.baud_rate);
let lcr = LineControl::new(false, false, self.parity, self.stop_bits, self.data_bits);
unsafe {
self.write_lcr(lcr);
let mut fcr = FifoControl::empty();
if let Some(level) = self.fifo_trigger_level {
fcr.set_fifo_enable(true);
fcr.set_fifo_trigger_level(level);
}
fcr.set_clear_receive_fifo();
fcr.set_clear_transmit_fifo();
self.write_fcr(fcr);
let mut mcr = ModemControl::empty();
mcr.set_data_terminal_ready(true);
mcr.set_request_to_send(true);
mcr.set_auxiliary_output_2(true);
self.write_mcr(mcr);
self.write_ier(self.interrupt_enable);
loop {
let lsr = self.read_lsr();
if lsr.transmitter_holding_register_empty() {
break;
}
core::hint::spin_loop();
}
}
}
pub fn ready_to_receive(&self) -> bool {
let lsr = self.read_lsr();
lsr.data_ready()
}
pub fn ready_to_send(&self) -> bool {
let lsr = self.read_lsr();
let msr = self.read_msr();
let mcr = self.read_mcr();
if !lsr.transmitter_holding_register_empty() {
return false;
}
if !mcr.loopback_mode() && !msr.clear_to_send() {
return false;
}
true
}
pub fn try_receive_byte(&self) -> Option<u8> {
if self.ready_to_receive() {
let data_port = self.base_port.offset(Self::DATA);
let value = unsafe { data_port.read() } as u8;
Some(value)
} else {
None
}
}
pub fn try_send_bytes(&self, bytes: &[u8]) -> Option<usize> {
if bytes.is_empty() {
return Some(0);
}
if !self.ready_to_send() {
return None;
}
let bytes = match self.fifo_trigger_level {
None => &bytes[..1],
Some(level) => {
let end = core::cmp::min(bytes.len(), level.count());
&bytes[..end]
}
};
for &b in bytes {
unsafe {
self.base_port.offset(Self::DATA).write(b as u16);
}
}
Some(bytes.len())
}
pub fn try_send_byte(&self, byte: u8) -> Option<()> {
if self.ready_to_send() {
let data_port = self.base_port.offset(Self::DATA);
unsafe { data_port.write(byte as u16) };
Some(())
} else {
None
}
}
pub fn try_receive_bytes<'a>(
&self,
buffer: &'a mut [MaybeUninit<u8>],
) -> (&'a mut [u8], &'a mut [MaybeUninit<u8>]) {
let len = buffer
.iter_mut()
.map_while(|b: &mut MaybeUninit<u8>| {
self.try_receive_byte().map(|val| b.write(val))
})
.count();
// SAFETY: We have just initialized the first `len` elements of the
// buffer, so it is safe to transmute them to `&mut [u8]`.
unsafe {
let (init, uninit) = buffer.split_at_mut(len);
let init = core::mem::transmute::<&mut [MaybeUninit<u8>], &mut [u8]>(init);
(init, uninit)
}
}
pub fn receive_bytes_exact<'a>(&self, buffer: &'a mut [MaybeUninit<u8>]) -> &'a mut [u8] {
let (ptr, len) = (buffer.as_mut_ptr(), buffer.len());
let mut rest = buffer;
loop {
rest = self.try_receive_bytes(rest).1;
if rest.is_empty() {
break;
}
}
unsafe {
// SAFETY: We have just initialized the entire buffer, so it is safe to transmute it to `&mut [u8]`.
core::slice::from_raw_parts_mut(ptr as *mut u8, len)
}
}
pub fn send_bytes_exact(&self, bytes: &[u8]) {
let mut rest = bytes;
loop {
let sent = self.try_send_bytes(rest).unwrap_or(0);
rest = &rest[sent..];
if rest.is_empty() {
break;
}
}
}
pub fn read_lcr(&self) -> LineControl {
let lcr_port = self.base_port.offset(3);
let value = unsafe { lcr_port.read() } as u8;
LineControl(value)
}
/// # Safety
/// This function writes to the Line Control Register (LCR) of the UART.
pub unsafe fn write_lcr(&self, lcr: LineControl) {
let lcr_port = self.base_port.offset(Self::LCR);
unsafe { lcr_port.write(lcr.0 as u16) };
}
pub fn read_msr(&self) -> ModemStatus {
let msr_port = self.base_port.offset(Self::MSR);
let value = unsafe { msr_port.read() } as u8;
ModemStatus(value)
}
pub fn read_lsr(&self) -> LineStatus {
let lsr_port = self.base_port.offset(Self::LSR);
let value = unsafe { lsr_port.read() } as u8;
LineStatus(value)
}
pub fn read_ier(&self) -> InterruptEnable {
let ier_port = self.base_port.offset(Self::IER);
let value = unsafe { ier_port.read() } as u8;
InterruptEnable(value)
}
/// # Safety
/// This function writes to the Interrupt Enable Register (IER) of the UART.
pub unsafe fn write_ier(&self, ier: InterruptEnable) {
let ier_port = self.base_port.offset(Self::IER);
unsafe { ier_port.write(ier.0 as u16) };
}
pub fn read_isr(&self) -> InterruptStatus {
let isr_port = self.base_port.offset(Self::ISR);
let value = unsafe { isr_port.read() } as u8;
InterruptStatus(value)
}
pub fn read_fcr(&self) -> FifoControl {
let fcr_port = self.base_port.offset(Self::FCR);
let value = unsafe { fcr_port.read() } as u8;
FifoControl(value)
}
pub fn read_mcr(&self) -> ModemControl {
let mcr_port = self.base_port.offset(Self::MCR);
let value = unsafe { mcr_port.read() } as u8;
ModemControl(value)
}
/// # Safety
/// This function writes to the Modem Control Register (MCR) of the UART.
pub unsafe fn write_mcr(&self, mcr: ModemControl) {
let mcr_port = self.base_port.offset(Self::MCR);
unsafe { mcr_port.write(mcr.0 as u16) };
}
/// # Safety
/// This function writes to the FIFO Control Register (FCR) of the UART.
pub unsafe fn write_fcr(&self, fcr: FifoControl) {
let fcr_port = self.base_port.offset(Self::FCR);
unsafe { fcr_port.write(fcr.0 as u16) };
}
pub fn set_baud_rate(&self, baud_rate: BaudRate) {
let divisor = baud_rate.divisor();
let mut lcr = self.read_lcr();
lcr.set_dlab(true);
let [low_byte, high_byte] = divisor.to_le_bytes();
unsafe {
self.write_lcr(lcr);
self.base_port.offset(Self::DLL).write(low_byte as u16);
self.base_port.offset(Self::DLM).write(high_byte as u16);
lcr.set_dlab(false);
self.write_lcr(lcr);
}
}
}
const BASE_RATE: u32 = 115200;
#[derive(Debug, Clone, Copy, Default)]
pub enum BaudRate {
Baud115200,
Baud57600,
Baud38400,
Baud19200,
#[default]
Baud9600,
}
impl BaudRate {
pub fn divisor(&self) -> u16 {
let rate = match self {
BaudRate::Baud115200 => 115200,
BaudRate::Baud57600 => 57600,
BaudRate::Baud38400 => 38400,
BaudRate::Baud19200 => 19200,
BaudRate::Baud9600 => 9600,
};
assert!(BASE_RATE.is_multiple_of(rate), "Invalid baud rate");
(BASE_RATE / rate) as u16
}
}
#[derive(Debug, Clone, Copy)]
#[repr(transparent)]
pub struct LineControl(pub u8);
#[derive(Debug, Clone, Copy, Default)]
#[repr(u8)]
pub enum DataBits {
Five = 0b00,
Six = 0b01,
Seven = 0b10,
#[default]
Eight = 0b11,
}
#[derive(Debug, Clone, Copy, Default)]
#[repr(u8)]
pub enum StopBits {
#[default]
One = 0b0,
Two = 0b1,
}
#[derive(Debug, Clone, Copy, Default)]
#[repr(u8)]
pub enum Parity {
#[default]
None = 0b00,
Odd = 0b01,
Even = 0b11,
Mark = 0b101,
Space = 0b111,
}
impl LineControl {
pub const fn empty() -> Self {
Self(0)
}
pub fn dlab(&self) -> bool {
(self.0 & (1 << 7)) != 0
}
pub fn set_dlab(&mut self, dlab: bool) {
if dlab {
self.0 |= 1 << 7;
} else {
self.0 &= !(1 << 7);
}
}
pub fn set_break_signal(&mut self, break_signal: bool) {
if break_signal {
self.0 |= 1 << 6;
} else {
self.0 &= !(1 << 6);
}
}
pub fn break_signal(&self) -> bool {
(self.0 & (1 << 6)) != 0
}
pub fn set_parity(&mut self, parity: Parity) {
self.0 &= !(0b111 << 3);
self.0 |= (parity as u8) << 3;
}
pub fn parity(&self) -> Parity {
match (self.0 >> 3) & 0b111 {
0b000 => Parity::None,
0b001 => Parity::Odd,
0b011 => Parity::Even,
0b101 => Parity::Mark,
0b111 => Parity::Space,
_ => unreachable!(),
}
}
pub fn set_stop_bits(&mut self, stop_bits: StopBits) {
self.0 &= !(1 << 2);
self.0 |= (stop_bits as u8) << 2;
}
pub fn stop_bits(&self) -> StopBits {
match (self.0 >> 2) & 0b1 {
0b0 => StopBits::One,
0b1 => StopBits::Two,
_ => unreachable!(),
}
}
pub fn set_data_bits(&mut self, data_bits: DataBits) {
self.0 &= !(0b11);
self.0 |= data_bits as u8;
}
pub fn data_bits(&self) -> DataBits {
match self.0 & 0b11 {
0b00 => DataBits::Five,
0b01 => DataBits::Six,
0b10 => DataBits::Seven,
0b11 => DataBits::Eight,
_ => unreachable!(),
}
}
pub const fn new(
dlab: bool,
break_signal: bool,
parity: Parity,
stop_bits: StopBits,
data_bits: DataBits,
) -> Self {
let mut value = 0u8;
if dlab {
value |= 1 << 7;
}
if break_signal {
value |= 1 << 6;
}
value |= (parity as u8) << 3;
value |= (stop_bits as u8) << 2;
value |= data_bits as u8;
Self(value)
}
}
#[derive(Debug, Clone, Copy)]
#[repr(transparent)]
pub struct InterruptEnable(pub u8);
impl Default for InterruptEnable {
fn default() -> Self {
let mut empty = Self(0);
empty.set_received_data_available(true);
empty
}
}
impl InterruptEnable {
pub const fn empty() -> Self {
Self(0)
}
pub fn set_received_data_available(&mut self, enable: bool) {
if enable {
self.0 |= 1 << 0;
} else {
self.0 &= !(1 << 0);
}
}
pub fn received_data_available(&self) -> bool {
(self.0 & (1 << 0)) != 0
}
pub fn set_transmitter_holding_register_empty(&mut self, enable: bool) {
if enable {
self.0 |= 1 << 1;
} else {
self.0 &= !(1 << 1);
}
}
pub fn transmitter_holding_register_empty(&self) -> bool {
(self.0 & (1 << 1)) != 0
}
pub fn set_receiver_line_status(&mut self, enable: bool) {
if enable {
self.0 |= 1 << 2;
} else {
self.0 &= !(1 << 2);
}
}
pub fn receiver_line_status(&self) -> bool {
(self.0 & (1 << 2)) != 0
}
pub fn set_modem_status(&mut self, enable: bool) {
if enable {
self.0 |= 1 << 3;
} else {
self.0 &= !(1 << 3);
}
}
pub fn modem_status(&self) -> bool {
(self.0 & (1 << 3)) != 0
}
}
pub struct FifoControl(pub u8);
impl FifoControl {
pub const fn empty() -> Self {
Self(0)
}
pub fn set_fifo_enable(&mut self, enable: bool) {
if enable {
self.0 |= 1 << 0;
} else {
self.0 &= !(1 << 0);
}
}
pub fn fifo_enable(&self) -> bool {
(self.0 & (1 << 0)) != 0
}
pub fn set_fifo_trigger_level(&mut self, level: FifoTriggerLevel) {
self.0 &= !(0b11 << 6);
self.0 |= (level as u8) << 6;
}
pub fn fifo_trigger_level(&self) -> FifoTriggerLevel {
match (self.0 >> 6) & 0b11 {
0b00 => FifoTriggerLevel::Level1Byte,
0b01 => FifoTriggerLevel::Level4Bytes,
0b10 => FifoTriggerLevel::Level8Bytes,
0b11 => FifoTriggerLevel::Level14Bytes,
_ => unreachable!(),
}
}
pub fn set_clear_receive_fifo(&mut self) {
self.0 |= 1 << 1;
}
pub fn set_clear_transmit_fifo(&mut self) {
self.0 |= 1 << 2;
}
pub fn set_dma_mode(&mut self, enable: bool) {
if enable {
self.0 |= 1 << 3;
} else {
self.0 &= !(1 << 3);
}
}
}
#[repr(u8)]
pub enum InterruptState {
ModemStatus = 0,
TransmitterHoldingRegisterEmpty = 1,
ReceivedDataAvailable = 2,
ReceiverLineStatus = 3,
}
#[repr(u8)]
pub enum FIFOBufferState {
Disabled = 0,
Unusable = 1,
Enabled = 2,
}
pub struct InterruptStatus(pub u8);
impl InterruptStatus {
pub fn interrupt_pending(&self) -> bool {
(self.0 & (1 << 0)) == 0
}
pub fn timeout_interrupt_pending(&self) -> bool {
(self.0 & (1 << 4)) != 0
}
pub fn interrupt_state(&self) -> InterruptState {
match (self.0 >> 1) & 0b111 {
0b000 => InterruptState::ModemStatus,
0b001 => InterruptState::TransmitterHoldingRegisterEmpty,
0b010 => InterruptState::ReceivedDataAvailable,
0b011 => InterruptState::ReceiverLineStatus,
_ => unreachable!(),
}
}
pub fn fifo_buffer_state(&self) -> FIFOBufferState {
match (self.0 >> 6) & 0b11 {
0b00 => FIFOBufferState::Disabled,
0b01 => FIFOBufferState::Unusable,
0b10 => FIFOBufferState::Enabled,
_ => unreachable!(),
}
}
}
pub struct ModemControl(pub u8);
impl ModemControl {
pub const fn empty() -> Self {
Self(0)
}
pub fn set_data_terminal_ready(&mut self, enable: bool) {
if enable {
self.0 |= 1 << 0;
} else {
self.0 &= !(1 << 0);
}
}
pub fn set_request_to_send(&mut self, enable: bool) {
if enable {
self.0 |= 1 << 1;
} else {
self.0 &= !(1 << 1);
}
}
pub fn set_auxiliary_output_1(&mut self, enable: bool) {
if enable {
self.0 |= 1 << 2;
} else {
self.0 &= !(1 << 2);
}
}
pub fn set_auxiliary_output_2(&mut self, enable: bool) {
if enable {
self.0 |= 1 << 3;
} else {
self.0 &= !(1 << 3);
}
}
pub fn set_loopback_mode(&mut self, enable: bool) {
if enable {
self.0 |= 1 << 4;
} else {
self.0 &= !(1 << 4);
}
}
pub fn loopback_mode(&self) -> bool {
(self.0 & (1 << 4)) != 0
}
}
pub struct LineStatus(pub u8);
impl LineStatus {
pub fn data_ready(&self) -> bool {
(self.0 & (1 << 0)) != 0
}
pub fn overrun_error(&self) -> bool {
(self.0 & (1 << 1)) != 0
}
pub fn parity_error(&self) -> bool {
(self.0 & (1 << 2)) != 0
}
pub fn framing_error(&self) -> bool {
(self.0 & (1 << 3)) != 0
}
pub fn break_interrupt(&self) -> bool {
(self.0 & (1 << 4)) != 0
}
pub fn transmitter_holding_register_empty(&self) -> bool {
(self.0 & (1 << 5)) != 0
}
pub fn transmitter_empty(&self) -> bool {
(self.0 & (1 << 6)) != 0
}
pub fn impending_error(&self) -> bool {
(self.0 & (1 << 7)) != 0
}
}
pub struct ModemStatus(pub u8);
impl ModemStatus {
pub fn delta_clear_to_send(&self) -> bool {
(self.0 & (1 << 0)) != 0
}
pub fn delta_data_set_ready(&self) -> bool {
(self.0 & (1 << 1)) != 0
}
pub fn trailing_edge_ring_indicator(&self) -> bool {
(self.0 & (1 << 2)) != 0
}
pub fn delta_data_carrier_detect(&self) -> bool {
(self.0 & (1 << 3)) != 0
}
pub fn clear_to_send(&self) -> bool {
(self.0 & (1 << 4)) != 0
}
pub fn data_set_ready(&self) -> bool {
(self.0 & (1 << 5)) != 0
}
pub fn ring_indicator(&self) -> bool {
(self.0 & (1 << 6)) != 0
}
pub fn data_carrier_detect(&self) -> bool {
(self.0 & (1 << 7)) != 0
}
}
#[derive(Debug, Clone, Copy, Default)]
#[repr(u8)]
pub enum FifoTriggerLevel {
Level1Byte = 0b00,
Level4Bytes = 0b01,
Level8Bytes = 0b10,
#[default]
Level14Bytes = 0b11,
}
impl FifoTriggerLevel {
pub fn count(&self) -> usize {
match self {
FifoTriggerLevel::Level1Byte => 1,
FifoTriggerLevel::Level4Bytes => 4,
FifoTriggerLevel::Level8Bytes => 8,
FifoTriggerLevel::Level14Bytes => 14,
}
}
}
pub struct Uart16550Writer {
uart: Uart16550,
}
impl Uart16550Writer {
pub fn from_port(base_port: u16) -> Self {
let mut uart = unsafe { Uart16550::with_port(base_port) };
uart.init();
Self { uart }
}
}
impl core::fmt::Write for Uart16550Writer {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
let bytes = s.as_bytes();
const SPECIAL_BYTES: &[u8] = &[b'\n', 8, 0x7f];
bytes
.split_inclusive(|b| SPECIAL_BYTES.contains(b))
.for_each(|chunk| {
if let Some((special, rest)) = chunk.split_last() {
self.uart.send_bytes_exact(rest);
match special {
b'\n' => {
self.uart.send_bytes_exact(b"\r\n");
}
8 | 0x7f => {
// Backspace or Delete: send backspace, space,
// backspace to erase the character on the
// terminal
self.uart.send_bytes_exact(&[8, b' ', 8]);
}
_ => {
self.uart.send_bytes_exact(&[*special]);
}
}
}
});
Ok(())
}
}
pub static SERIAL1: crate::sync::LazyLock<crate::sync::SpinMutex<Uart16550Writer>> =
crate::sync::LazyLock::new(|| {
crate::sync::SpinMutex::new(Uart16550Writer::from_port(0x3f8))
});
}
#[doc(hidden)]
pub fn _print(args: core::fmt::Arguments, nl: bool) {
#[cfg(target_arch = "x86_64")]
{
use core::fmt::Write;
struct Writer<'a>(&'a crate::sync::SpinMutex<uart_16550::Uart16550Writer>);
impl core::fmt::Write for Writer<'_> {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
let mut writer = self.0.lock();
writer.write_str(s)
}
}
let mut writer = Writer(&uart_16550::SERIAL1);
writer.write_fmt(args).unwrap();
if nl {
writer.write_str("\n").unwrap();
}
}
}
#[macro_export]
macro_rules! serial_print {
($($arg:tt)*) => {
$crate::serial::_print(format_args!($($arg)*), false);
};
}
#[macro_export]
macro_rules! serial_println {
() => {
$crate::serial_print!("\n");
};
($($arg:tt)*) => {
$crate::serial::_print(format_args!($($arg)*), true);
};
}

View file

@ -1,470 +0,0 @@
mod spin_mutex {
use core::{
cell::UnsafeCell,
sync::atomic::{AtomicBool, Ordering},
};
pub struct SpinMutex<T: ?Sized> {
lock: AtomicBool,
data: UnsafeCell<T>,
}
pub struct SpinMutexGuard<'a, T: ?Sized + 'a> {
mutex: &'a SpinMutex<T>,
}
impl<T: ?Sized> core::ops::Deref for SpinMutexGuard<'_, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
unsafe { &*self.mutex.data.get() }
}
}
impl<T: ?Sized> core::ops::DerefMut for SpinMutexGuard<'_, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { &mut *self.mutex.data.get() }
}
}
impl<T: ?Sized> Drop for SpinMutexGuard<'_, T> {
fn drop(&mut self) {
unsafe { self.mutex.unlock() }
}
}
unsafe impl<T: ?Sized + Send> Send for SpinMutex<T> {}
unsafe impl<T: ?Sized + Send> Sync for SpinMutex<T> {}
unsafe impl<T: ?Sized> Send for SpinMutexGuard<'_, T> where for<'a> &'a mut T: Send {}
unsafe impl<T: ?Sized> Sync for SpinMutexGuard<'_, T> where for<'a> &'a mut T: Sync {}
impl<T> SpinMutex<T> {
pub const fn new(data: T) -> Self {
Self {
lock: AtomicBool::new(false),
data: UnsafeCell::new(data),
}
}
pub fn into_inner(self) -> T {
self.data.into_inner()
}
pub fn as_mut_ptr(&self) -> *mut T {
self.data.get()
}
}
impl<T: ?Sized> SpinMutex<T> {
pub fn try_lock(&self) -> bool {
self.lock
.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
.is_ok()
}
pub fn try_lock_weak(&self) -> bool {
self.lock
.compare_exchange_weak(false, true, Ordering::Acquire, Ordering::Relaxed)
.is_ok()
}
pub fn lock(&self) -> SpinMutexGuard<'_, T> {
while !self.try_lock() {
core::hint::spin_loop();
}
SpinMutexGuard { mutex: self }
}
/// # Safety
/// The caller must be the logical owner of the locked mutex.
pub unsafe fn unlock(&self) {
self.lock.store(false, Ordering::Release);
}
}
}
pub use spin_mutex::SpinMutex;
mod once {
use core::{
cell::{Cell, UnsafeCell},
mem::{ManuallyDrop, MaybeUninit},
sync::atomic::{AtomicU8, Ordering},
};
const UNINITIALIZED: u8 = 0;
const INITIALIZING: u8 = 1;
const INITIALIZED: u8 = 2;
const PANICKED: u8 = 3;
pub struct OnceState {
poisoned: bool,
state_to_set: Cell<u8>,
}
impl OnceState {
pub fn is_poisoned(&self) -> bool {
self.poisoned
}
pub fn poison(&self) {
self.state_to_set.set(PANICKED);
}
}
pub struct Once {
state: AtomicU8,
}
const impl Default for Once {
fn default() -> Self {
Self {
state: AtomicU8::new(UNINITIALIZED),
}
}
}
impl Once {
pub const fn new() -> Self {
Self {
state: AtomicU8::new(UNINITIALIZED),
}
}
pub fn state(&self) -> u8 {
self.state.load(Ordering::Acquire)
}
pub fn set_state(&self, state: u8) {
self.state.store(state, Ordering::Release);
}
pub fn is_completed(&self) -> bool {
self.state.load(Ordering::Acquire) == INITIALIZED
}
pub fn call_once<F>(&self, f: F)
where
F: FnOnce(&OnceState),
{
if self.is_completed() {
return;
}
self.call_once_slow(false, f);
}
pub fn call_once_force<F>(&self, f: F)
where
F: FnOnce(&OnceState),
{
if self.is_completed() {
return;
}
self.call_once_slow(true, f);
}
#[cold]
pub fn call_once_slow<F>(&self, ignore_poison: bool, f: F)
where
F: FnOnce(&OnceState),
{
let mut state = self.state.load(Ordering::Acquire);
loop {
match state {
INITIALIZED => return,
PANICKED if !ignore_poison => {
panic!("Once instance has previously been poisoned")
}
PANICKED | UNINITIALIZED => {
match self.state.compare_exchange(
state,
INITIALIZING,
Ordering::Acquire,
// if we get `Err(INITIALIZED)`, we want to have
// acquired what the lock is protecting.
Ordering::Acquire,
) {
Err(new) => {
state = new;
continue;
}
Ok(_) => {
// even though we don't have unwinding, for
// completeness sake we'll poison the lock if
// the closure panics.
let guard = crate::drop_guard! {
self.state.store(PANICKED, Ordering::Release)
};
let state = OnceState {
poisoned: state == PANICKED,
state_to_set: Cell::new(INITIALIZED),
};
f(&state);
guard.forget();
self.state
.store(state.state_to_set.take(), Ordering::Release);
return;
}
}
}
_ => {
assert_eq!(state, INITIALIZING);
loop {
state = self.state.load(Ordering::Acquire);
if state != INITIALIZING {
break;
}
core::hint::spin_loop();
}
}
}
}
}
}
pub struct OnceLock<T> {
once: Once,
data: UnsafeCell<MaybeUninit<T>>,
}
unsafe impl<T: Send> Send for OnceLock<T> {}
unsafe impl<T: Send + Sync> Sync for OnceLock<T> {}
const impl<T> Default for OnceLock<T> {
fn default() -> Self {
Self {
once: Once::new(),
data: UnsafeCell::new(MaybeUninit::uninit()),
}
}
}
impl<T> OnceLock<T> {
pub const fn new() -> Self {
Self {
once: Once::new(),
data: UnsafeCell::new(MaybeUninit::uninit()),
}
}
pub const fn from(t: T) -> Self {
Self {
once: Once::new(),
data: UnsafeCell::new(MaybeUninit::new(t)),
}
}
/// # Safety
/// The caller must ensure that the `OnceLock` has been initialized before calling this method.
pub unsafe fn get_unchecked(&self) -> &T {
unsafe { self.data.get().as_ref_unchecked().assume_init_ref() }
}
/// # Safety
/// The caller must ensure that the `OnceLock` has been initialized before calling this method.
pub unsafe fn get_mut_unchecked(&mut self) -> &mut T {
unsafe { self.data.get().as_mut_unchecked().assume_init_mut() }
}
pub fn get(&self) -> Option<&T> {
if self.is_completed() {
Some(unsafe { self.get_unchecked() })
} else {
None
}
}
pub fn get_mut(&mut self) -> Option<&mut T> {
if self.is_completed_mut() {
Some(unsafe { self.get_mut_unchecked() })
} else {
None
}
}
pub fn is_completed(&self) -> bool {
self.once.is_completed()
}
pub fn is_completed_mut(&mut self) -> bool {
// avoid atomic load when we have mutable access to self
*self.once.state.get_mut() == INITIALIZED
}
pub fn get_spinning(&self) -> Option<&T> {
loop {
match self.once.state() {
INITIALIZED => return Some(unsafe { self.get_unchecked() }),
INITIALIZING => core::hint::spin_loop(),
UNINITIALIZED | PANICKED => return None,
_ => unreachable!(),
}
}
}
pub fn initialize<F, E>(&self, f: F) -> Result<(), E>
where
F: FnOnce() -> Result<T, E>,
{
let mut res: Result<(), E> = Ok(());
let cell = unsafe { &mut *self.data.get() };
self.once.call_once_force(|state| match f() {
Ok(value) => {
unsafe { cell.as_mut_ptr().write(value) };
}
Err(e) => {
state.poison();
res = Err(e);
}
});
res
}
pub fn get_or_init<F>(&self, f: F) -> &T
where
F: FnOnce() -> T,
{
match self.get_or_try_init(|| Ok::<T, core::convert::Infallible>(f())) {
Ok(value) => value,
Err(_) => unreachable!(),
}
}
pub fn get_mut_or_init<F>(&mut self, f: F) -> &mut T
where
F: FnOnce() -> T,
{
match self.get_mut_or_try_init(|| Ok::<T, core::convert::Infallible>(f())) {
Ok(value) => value,
Err(_) => unreachable!(),
}
}
pub fn get_or_try_init<F, E>(&self, f: F) -> Result<&T, E>
where
F: FnOnce() -> Result<T, E>,
{
if !self.is_completed() {
self.initialize(f)?;
}
unsafe { Ok(self.get_unchecked()) }
}
pub fn get_mut_or_try_init<F, E>(&mut self, f: F) -> Result<&mut T, E>
where
F: FnOnce() -> Result<T, E>,
{
if self.get_mut().is_none() {
self.initialize(f)?;
}
Ok(unsafe { self.get_mut_unchecked() })
}
pub fn try_insert(&self, value: T) -> Result<&T, (&T, T)> {
let mut val = Some(value);
let res = self.get_or_init(|| unsafe { val.take().unwrap_unchecked() });
match val {
Some(value) => Err((res, value)),
None => Ok(res),
}
}
}
impl<T> Drop for OnceLock<T> {
fn drop(&mut self) {
if self.is_completed() {
unsafe { self.data.get().as_mut_unchecked().assume_init_drop() }
}
}
}
pub struct LazyLock<T, F = fn() -> T> {
once: Once,
f: UnsafeCell<ManuallyDrop<F>>,
t: UnsafeCell<MaybeUninit<T>>,
}
unsafe impl<T: Sync + Send, F: Send> Sync for LazyLock<T, F> {}
impl<T, F> LazyLock<T, F>
where
F: FnOnce() -> T,
{
pub const fn new(f: F) -> Self {
Self {
once: Once::new(),
f: UnsafeCell::new(ManuallyDrop::new(f)),
t: UnsafeCell::new(MaybeUninit::uninit()),
}
}
pub fn get(&self) -> Option<&T> {
if self.once.is_completed() {
Some(unsafe { (&*self.t.get()).assume_init_ref() })
} else {
None
}
}
fn force(this: &Self) -> &T {
this.once.call_once(|_| {
// SAFETY: because `call_once` will panic if poisoned, this
// closure will only be called once.
let f = unsafe { ManuallyDrop::take(&mut *this.f.get()) };
let val = f();
unsafe { (&mut *this.t.get()).write(val) };
});
unsafe { (&*this.t.get()).assume_init_ref() }
}
fn force_mut(this: &mut Self) -> &mut T {
this.once.call_once(|_| {
// SAFETY: because `call_once` will panic if poisoned, this
// closure will only be called once.
let f = unsafe { ManuallyDrop::take(&mut *this.f.get()) };
let val = f();
unsafe { (&mut *this.t.get()).write(val) };
});
unsafe { (&mut *this.t.get()).assume_init_mut() }
}
}
impl<T, F: FnOnce() -> T> core::ops::Deref for LazyLock<T, F> {
type Target = T;
fn deref(&self) -> &Self::Target {
LazyLock::force(self)
}
}
impl<T, F: FnOnce() -> T> core::ops::DerefMut for LazyLock<T, F> {
fn deref_mut(&mut self) -> &mut Self::Target {
LazyLock::force_mut(self)
}
}
impl<T, F> Drop for LazyLock<T, F> {
fn drop(&mut self) {
match self.once.state() {
UNINITIALIZED => unsafe {
ManuallyDrop::drop(self.f.get_mut());
},
INITIALIZED => unsafe {
(&mut *self.t.get()).assume_init_drop();
},
_ => {}
}
}
}
}
pub use once::{LazyLock, Once, OnceLock};

View file

@ -1,49 +0,0 @@
#![cfg(target_arch = "x86_64")]
#[inline]
pub fn hlt() {
unsafe {
core::arch::asm!("hlt", options(nomem, nostack, preserves_flags));
}
}
pub fn halt_loop() -> ! {
loop {
hlt();
}
}
#[repr(transparent)]
pub struct PortU16(pub u16);
impl PortU16 {
pub fn offset(&self, offset: u16) -> Self {
Self(self.0 + offset)
}
#[inline]
pub unsafe fn read(&self) -> u16 {
let value: u16;
unsafe {
core::arch::asm!(
"in ax, dx",
in("dx") self.0,
out("ax") value,
options(nomem, nostack, preserves_flags)
);
}
value
}
#[inline]
pub unsafe fn write(&self, value: u16) {
unsafe {
core::arch::asm!(
"out dx, ax",
in("dx") self.0,
in("ax") value,
options(nomem, nostack, preserves_flags)
);
}
}
}

View file

@ -7,23 +7,22 @@
"disable-redzone": true,
"target-endian": "little",
"features": "-mmx,-sse,-sse2,-sse3,-ssse3,-sse4.1,-sse4.2,-avx,-avx2,+soft-float",
"linker": "rust-lld",
"linker-flavor": "ld.lld",
"linker": "mold",
"linker-flavor": "gnu",
"llvm-target": "x86_64-unknown-none-elf",
"max-atomic-width": 64,
"panic-strategy": "abort",
"plt-by-default": false,
"os": "none",
"executables": true,
"position-independent-executables": true,
"relro-level": "full",
"rustc-abi": "softfloat",
"stack-probes": {
"kind": "inline"
},
"static-position-independent-executables": true,
"supported-sanitizers": [
"kcfi",
"kernel-address"
],
"target-pointer-width": 64,
"target-c-int-width": 32
"target-pointer-width": 64
}