Compare commits

...

14 Commits

Author SHA1 Message Date
Philipp Oppermann
bd3550ea87 Merge pull request #1410 from v4zha/edition-3
loading UEFI using ovmf_prebuilt=0.2.3 with ovmf_code and ovmf_vars
2025-05-09 15:51:53 +02:00
V4zha
ecb60ec326 Update index.md
change to_str().unwrap() to display() in format! args
2025-04-24 18:48:52 +05:30
v4zha
8a1267477a loading UEFI using ovmf_prebuilt=0.2.3 with ovmf_code and ovmf_vars 2025-04-24 16:58:59 +05:30
Philipp Oppermann
ce01059620 Fix typos
Fixes #1339
2024-08-26 07:56:55 +02:00
Philipp Oppermann
4d0c3ac188 Merge pull request #1333 from proudmuslim-dev/patch-5
Move import to sensible location in chapter 3
2024-07-25 22:57:03 +02:00
proudmuslim-dev
d565cd125b Move import to sensible location in chapter 3
It served no purpose in the previous code block and would only confuse the user
2024-07-04 19:15:21 +00:00
Philipp Oppermann
ca86085360 Merge pull request #1299 from spocino/patch-1
fix typo'd variable name in post 3 (doesn't compile)
2024-02-27 07:52:00 +01:00
Samuel Pocino
5f3d38884c fix typo'd variable name in post 3 (doesn't compile) 2024-02-24 20:32:28 -05:00
Philipp Oppermann
f557d1c698 Merge pull request #1276 from proudmuslim-dev/patch-4
Fix `embedded_graphics` code + typo in chapter 3
2024-02-04 17:09:00 +01:00
proudmuslim-dev
0c248d027e Fix embedded_graphics code + correct typo in chapter 3
Compiles now
2024-01-31 12:55:20 -08:00
Philipp Oppermann
2cf0675a2d Merge pull request #1269 from proudmuslim-dev/patch-3
Fix typos in code for `embedded_graphics` crate in chapter 3
2024-01-28 12:18:43 +01:00
Philipp Oppermann
916ad36e78 Merge pull request #1270 from lachsdachs/patch-1
fix a lil typo
2024-01-28 12:09:11 +01:00
lachsdachs
3c2e91fa4e fix a lil typo
sturcts -> structs
2024-01-27 21:23:35 +01:00
proudmuslim-dev
c9683a2cd9 Fix typos in code for embedded_graphics crate in chapter 3
This still won't compile on account of the fact that the `Point` type apparently doesn't implement `Into<(usize, usize)>`. Attempting to change the type just results in more issues
2024-01-27 00:21:18 +00:00
2 changed files with 60 additions and 30 deletions

View File

@@ -75,7 +75,7 @@ This structure has the following general format:
| 446 | partition entry 1 | 16 | | 446 | partition entry 1 | 16 |
| 462 | partition entry 2 | 16 | | 462 | partition entry 2 | 16 |
| 478 | partition entry 3 | 16 | | 478 | partition entry 3 | 16 |
| 444 | partition entry 4 | 16 | | 494 | partition entry 4 | 16 |
| 510 | boot signature | 2 | | 510 | boot signature | 2 |
The bootstrap code is commonly called the _bootloader_ and responsible for loading and starting the operating system kernel. The bootstrap code is commonly called the _bootloader_ and responsible for loading and starting the operating system kernel.
@@ -706,7 +706,7 @@ We then use the the `create_uefi_image` and `create_bios_image` methods to creat
[requires build scripts]: https://doc.rust-lang.org/cargo/reference/build-scripts.html#outputs-of-the-build-script [requires build scripts]: https://doc.rust-lang.org/cargo/reference/build-scripts.html#outputs-of-the-build-script
[`join`]: https://doc.rust-lang.org/std/path/struct.PathBuf.html#method.join [`join`]: https://doc.rust-lang.org/std/path/struct.PathBuf.html#method.join
We can now use use a simple `cargo build` to cross-compile our kernel, build the bootloader, and combine them to create a bootable disk image: We can now use a simple `cargo build` to cross-compile our kernel, build the bootloader, and combine them to create a bootable disk image:
``` ```
cargo build cargo build
@@ -1011,27 +1011,35 @@ Now we can create our `qemu-uefi` executable at `src/bin/qemu-uefi.rs`:
```rust ,hl_lines=3-15 ```rust ,hl_lines=3-15
// src/bin/qemu-uefi.rs // src/bin/qemu-uefi.rs
use std::{ use std::{
env, env, process::{self, Command}
process::{self, Command},
}; };
use ovmf_prebuilt::{Arch, FileType, Prebuilt, Source};
fn main() { fn main() {
let prebuilt =
Prebuilt::fetch(Source::LATEST, "target/ovmf").unwrap();
let ovmf_code = prebuilt.get_file(Arch::X64, FileType::Code);
let ovmf_vars = prebuilt.get_file(Arch::X64, FileType::Vars);
let mut qemu = Command::new("qemu-system-x86_64"); let mut qemu = Command::new("qemu-system-x86_64");
qemu.arg("-drive"); qemu.args([
qemu.arg(format!("format=raw,file={}", env!("UEFI_IMAGE"))); "-drive",
qemu.arg("-bios").arg(ovmf_prebuilt::ovmf_pure_efi()); &format!("format=raw,if=pflash,readonly=on,file={}", ovmf_code.display()),
"-drive",
&format!("format=raw,if=pflash,file={}", ovmf_vars.display()),
"-drive",
&format!("format=raw,file={}", env!("UEFI_IMAGE")),
]);
let exit_status = qemu.status().unwrap(); let exit_status = qemu.status().unwrap();
process::exit(exit_status.code().unwrap_or(-1)); process::exit(exit_status.code().unwrap_or(-1));
} }
``` ```
It's very similar to our `qemu-bios` executable. It's very similar to our `qemu-bios` executable.
The only two differences are that it passes an additional `-bios` argument and that it uses the `UEFI_IMAGE` instead of the `BIOS_IMAGE`. The only two differences are that it passes two additional `-drive if=pflash,..` arguments to load UEFI firmware (`OVMF_CODE.fd`) and writable NVRAM (`OVMF_VARS.fd`), and that it uses the `UEFI_IMAGE` instead of the `BIOS_IMAGE`.
Using a quick `cargo run --bin qemu-uefi`, we can confirm that it works as intended. Using a quick `cargo run --bin qemu-uefi`, we can confirm that it works as intended.
### Screen Output ### Screen Output
While we see some screen output from the bootloader, our kernel still does nothing. While we see some screen output from the bootloader, our kernel still does nothing.

View File

@@ -149,8 +149,6 @@ In the new module, we create basic structs for representing pixel positions and
```rust ,hl_lines=3-16 ```rust ,hl_lines=3-16
// in new kernel/src/framebuffer.rs file // in new kernel/src/framebuffer.rs file
use bootloader_api::info::FrameBuffer;
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Position { pub struct Position {
pub x: usize, pub x: usize,
@@ -165,7 +163,7 @@ pub struct Color {
} }
``` ```
By marking the sturcts and their fields as `pub`, we make them accessible from the parent `kernel` module. By marking the structs and their fields as `pub`, we make them accessible from the parent `kernel` module.
We use the `#[derive]` attribute to implement the [`Debug`], [`Clone`], [`Copy`], [`PartialEq`], and [`Eq`] traits of Rust's core library. We use the `#[derive]` attribute to implement the [`Debug`], [`Clone`], [`Copy`], [`PartialEq`], and [`Eq`] traits of Rust's core library.
These traits allow us to duplicate, compare, and print the structs. These traits allow us to duplicate, compare, and print the structs.
@@ -180,7 +178,7 @@ Next, we create a function for setting a specific pixel in the framebuffer to a
```rust ,hl_lines=3 5-39 ```rust ,hl_lines=3 5-39
// in new kernel/src/framebuffer.rs file // in new kernel/src/framebuffer.rs file
use bootloader_api::info::PixelFormat; use bootloader_api::info::{FrameBuffer, PixelFormat};
pub fn set_pixel_in(framebuffer: &mut FrameBuffer, position: Position, color: Color) { pub fn set_pixel_in(framebuffer: &mut FrameBuffer, position: Position, color: Color) {
let info = framebuffer.info(); let info = framebuffer.info();
@@ -312,31 +310,46 @@ Fortunately, there is the nice `no_std`-compatible [`embedded-graphics`] crate,
```rust ,hl_lines=3 ```rust ,hl_lines=3
// in kernel/src/framebuffer.rs // in kernel/src/framebuffer.rs
use embedded_graphics::{
Pixel,
draw_target::DrawTarget,
geometry::{OriginDimensions, Size},
pixelcolor::{Rgb888, RgbColor},
};
pub struct Display { pub struct Display<'f> {
framebuffer: Framebuffer, framebuffer: &'f mut FrameBuffer,
} }
impl Display { impl<'f> Display<'f> {
pub fn new(framebuffer: Framebuffer) -> Display { pub fn new(framebuffer: &'f mut FrameBuffer) -> Display {
Self { framebuffer } Display { framebuffer }
} }
fn draw_pixel(&mut self, pixel: Pixel) { fn draw_pixel(&mut self, Pixel(coordinates, color): Pixel<Rgb888>) {
// ignore any pixels that are out of bounds. // ignore any out of bounds pixels
let (width, height) = { let (width, height) = {
let info = self.framebuffer.info(); let info = self.framebuffer.info();
(info.width, info.height) (info.width, info.height)
} };
if let Ok((x @ 0..width, y @ 0..height)) = coordinates.try_into() {
let color = Color { red: color.r(), green: color.g(), blue: color.b()}; let (x, y) = {
set_pixel_in(&mut self.framebuffer, Position { x, y }, color); let c: (i32, i32) = coordinates.into();
(c.0 as usize, c.1 as usize)
};
if (0..width).contains(&x) && (0..height).contains(&y) {
let color = Color { red: color.r(), green: color.g(), blue: color.b() };
set_pixel_in(self.framebuffer, Position { x, y }, color);
} }
} }
} }
impl embedded_graphics::draw_target::DrawTarget for Display {
type Color = embedded_graphics::pixelcolor::Rgb888; impl<'f> DrawTarget for Display<'f> {
type Color = Rgb888;
/// Drawing operations can never fail. /// Drawing operations can never fail.
type Error = core::convert::Infallible; type Error = core::convert::Infallible;
@@ -345,12 +358,21 @@ impl embedded_graphics::draw_target::DrawTarget for Display {
where where
I: IntoIterator<Item = Pixel<Self::Color>>, I: IntoIterator<Item = Pixel<Self::Color>>,
{ {
for Pixel(coordinates, color) in pixels.into_iter() { for pixel in pixels.into_iter() {
self.draw_pixel(pixel); self.draw_pixel(pixel);
} }
Ok(()) Ok(())
} }
} }
impl<'f> OriginDimensions for Display<'f> {
fn size(&self) -> Size {
let info = self.framebuffer.info();
Size::new(info.width as u32, info.height as u32)
}
}
``` ```
--- ---
@@ -360,7 +382,7 @@ impl embedded_graphics::draw_target::DrawTarget for Display {
draw shapes and pixels directly onto the framebuffer. That's fine and all, but how is one able to go from that to displaying text on the screen? Understanding this requires taking a deep dive into how characters are rendered behind the scenes. So far, we have drawn shapes and pixels directly onto the framebuffer. That's fine and all, but how is one able to go from that to displaying text on the screen? Understanding this requires taking a deep dive into how characters are rendered behind the scenes.
When a key is pressed on the keyboard, it sends a character code to the CPU. It's the CPU's job at that point to then interpret the character code and match it with an image to draw on the screen. The image is then sent to either the GPU or the framebuffer (the latter in our case) to be drawn on the screen, and the user sees that image as a letter, number, CJK character, emoji, or whatever else he or she wanted to have displayed by pressing that key. When a key is pressed on the keyboard, it sends a character code to the CPU. It's the CPU's job at that point to then interpret the character code and match it with an image to draw on the screen. The image is then sent to either the GPU or the framebuffer (the latter in our case) to be drawn on the screen, and the user sees that image as a letter, number, CJK character, emoji, or whatever else he or she wanted to have displayed by pressing that key.
@@ -450,7 +472,7 @@ fn kernel_main(boot_info: &'static mut bootloader_api::BootInfo) -> ! {
let frame_buffer_struct = frame_buffer_option.unwrap(); let frame_buffer_struct = frame_buffer_option.unwrap();
// extract the framebuffer info and, to satisfy the borrow checker, clone it // extract the framebuffer info and, to satisfy the borrow checker, clone it
let frame_buffer_info = frame_buffer.info().clone(); let frame_buffer_info = frame_buffer_struct.info().clone();
// get the framebuffer's mutable raw byte slice // get the framebuffer's mutable raw byte slice
let raw_frame_buffer = frame_buffer_struct.buffer_mut(); let raw_frame_buffer = frame_buffer_struct.buffer_mut();