draw to framebuffer

This commit is contained in:
janis 2026-07-16 16:27:37 +02:00
parent f1570c06b1
commit cffad21a64
Signed by: janis
SSH key fingerprint: SHA256:bB1qbbqmDXZNT0KKD5c2Dfjg53JGhj7B3CFcLIzSqq8
7 changed files with 286 additions and 26 deletions

View file

@ -3,6 +3,7 @@ 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]

6
kernel/build.rs Normal file
View file

@ -0,0 +1,6 @@
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");
}

43
kernel/kernel.lds Normal file
View file

@ -0,0 +1,43 @@
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,5 +1,7 @@
#![no_std]
#![feature(const_trait_impl, const_default)]
pub mod limine;
pub mod x86_64;
/// # Safety

164
kernel/src/limine.rs Normal file
View file

@ -0,0 +1,164 @@
use core::{cell::UnsafeCell, ptr::NonNull};
#[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() }
}
}
#[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, U> Send for Request<T, U> {}
unsafe impl<T, U> 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() })
}
}
#[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 {
pub 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_le_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,
}
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<'limine>(&self) -> &'limine [&'limine 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::<&'limine Framebuffer>(),
response.response.count as usize,
)
})
.unwrap_or(&[])
}
}

View file

@ -8,5 +8,48 @@ fn panic(_info: &core::panic::PanicInfo) -> ! {
#[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 mut bits = ((x * 255 / fb.width) as u32) << 8;
bits ^= (y * 255 / fb.height) as u32;
fb.draw_pixel(
x,
y,
kernel::limine::Color(*bits.to_le_bytes().first_chunk().unwrap()),
);
}
}
kernel::x86_64::halt_loop()
}
mod limine_requests {
use kernel::limine::{
BaseRevision, FramebufferRequest, REQUESTS_END_MARKER, REQUESTS_START_MARKER,
RequestsEndMarker, RequestsStartMarker,
};
#[unsafe(link_section = ".limine_requests_start")]
#[unsafe(no_mangle)]
static LIMINE_REQUESTS_START: RequestsStartMarker = REQUESTS_START_MARKER;
#[unsafe(link_section = ".limine_requests")]
#[unsafe(no_mangle)]
pub static LIMINE_BASE_REVISION: BaseRevision = BaseRevision::from_revision(6);
#[unsafe(link_section = ".limine_requests")]
#[unsafe(no_mangle)]
pub static FRAMEBUFFER_REQUEST: FramebufferRequest = FramebufferRequest::new();
#[unsafe(link_section = ".limine_requests_end")]
#[unsafe(no_mangle)]
static LIMINE_REQUESTS_END: RequestsEndMarker = REQUESTS_END_MARKER;
}

View file

@ -1,28 +1,29 @@
{
"arch": "x86_64",
"code-model": "kernel",
"cpu": "x86-64",
"crt-objects-fallback": "false",
"data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128",
"disable-redzone": true,
"target-endian": "little",
"features": "-mmx,-sse,-sse2,-sse3,-ssse3,-sse4.1,-sse4.2,-avx,-avx2,+soft-float",
"linker": "mold",
"linker-flavor": "gnu",
"llvm-target": "x86_64-unknown-none-elf",
"max-atomic-width": 64,
"panic-strategy": "abort",
"plt-by-default": false,
"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
"arch": "x86_64",
"code-model": "kernel",
"cpu": "x86-64",
"crt-objects-fallback": "false",
"data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128",
"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",
"llvm-target": "x86_64-unknown-none-elf",
"max-atomic-width": 64,
"panic-strategy": "abort",
"plt-by-default": false,
"os": "none",
"executables": true,
"relro-level": "full",
"rustc-abi": "softfloat",
"stack-probes": {
"kind": "inline"
},
"supported-sanitizers": [
"kcfi",
"kernel-address"
],
"target-pointer-width": 64,
"target-c-int-width": 32
}