mirror of
https://github.com/phil-opp/blog_os.git
synced 2025-12-17 23:07:50 +00:00
Compare commits
10 Commits
2cf0675a2d
...
edition-3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bd3550ea87 | ||
|
|
ecb60ec326 | ||
|
|
8a1267477a | ||
|
|
ce01059620 | ||
|
|
4d0c3ac188 | ||
|
|
d565cd125b | ||
|
|
ca86085360 | ||
|
|
5f3d38884c | ||
|
|
f557d1c698 | ||
|
|
0c248d027e |
@@ -75,7 +75,7 @@ This structure has the following general format:
|
||||
| 446 | partition entry 1 | 16 |
|
||||
| 462 | partition entry 2 | 16 |
|
||||
| 478 | partition entry 3 | 16 |
|
||||
| 444 | partition entry 4 | 16 |
|
||||
| 494 | partition entry 4 | 16 |
|
||||
| 510 | boot signature | 2 |
|
||||
|
||||
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
|
||||
[`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
|
||||
@@ -1011,27 +1011,35 @@ Now we can create our `qemu-uefi` executable at `src/bin/qemu-uefi.rs`:
|
||||
|
||||
```rust ,hl_lines=3-15
|
||||
// src/bin/qemu-uefi.rs
|
||||
|
||||
use std::{
|
||||
env,
|
||||
process::{self, Command},
|
||||
env, process::{self, Command}
|
||||
};
|
||||
|
||||
use ovmf_prebuilt::{Arch, FileType, Prebuilt, Source};
|
||||
|
||||
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");
|
||||
qemu.arg("-drive");
|
||||
qemu.arg(format!("format=raw,file={}", env!("UEFI_IMAGE")));
|
||||
qemu.arg("-bios").arg(ovmf_prebuilt::ovmf_pure_efi());
|
||||
qemu.args([
|
||||
"-drive",
|
||||
&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();
|
||||
process::exit(exit_status.code().unwrap_or(-1));
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
|
||||
### Screen Output
|
||||
|
||||
While we see some screen output from the bootloader, our kernel still does nothing.
|
||||
|
||||
@@ -149,8 +149,6 @@ In the new module, we create basic structs for representing pixel positions and
|
||||
```rust ,hl_lines=3-16
|
||||
// in new kernel/src/framebuffer.rs file
|
||||
|
||||
use bootloader_api::info::FrameBuffer;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Position {
|
||||
pub x: usize,
|
||||
@@ -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
|
||||
// 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) {
|
||||
let info = framebuffer.info();
|
||||
@@ -312,32 +310,45 @@ Fortunately, there is the nice `no_std`-compatible [`embedded-graphics`] crate,
|
||||
|
||||
```rust ,hl_lines=3
|
||||
// in kernel/src/framebuffer.rs
|
||||
use embedded_graphics::pixelcolor::Rgb888;
|
||||
use embedded_graphics::{
|
||||
Pixel,
|
||||
draw_target::DrawTarget,
|
||||
geometry::{OriginDimensions, Size},
|
||||
pixelcolor::{Rgb888, RgbColor},
|
||||
};
|
||||
|
||||
pub struct Display {
|
||||
framebuffer: FrameBuffer,
|
||||
pub struct Display<'f> {
|
||||
framebuffer: &'f mut FrameBuffer,
|
||||
}
|
||||
|
||||
impl Display {
|
||||
pub fn new(framebuffer: FrameBuffer) -> Display {
|
||||
Self { framebuffer }
|
||||
impl<'f> Display<'f> {
|
||||
pub fn new(framebuffer: &'f mut FrameBuffer) -> Display {
|
||||
Display { framebuffer }
|
||||
}
|
||||
|
||||
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 info = self.framebuffer.info();
|
||||
(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()};
|
||||
set_pixel_in(&mut self.framebuffer, Position { x, y }, color);
|
||||
(info.width, info.height)
|
||||
};
|
||||
|
||||
let (x, y) = {
|
||||
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 {
|
||||
|
||||
impl<'f> DrawTarget for Display<'f> {
|
||||
type Color = Rgb888;
|
||||
|
||||
/// Drawing operations can never fail.
|
||||
@@ -354,6 +365,14 @@ impl embedded_graphics::draw_target::DrawTarget for Display {
|
||||
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)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
@@ -363,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.
|
||||
|
||||
@@ -453,7 +472,7 @@ fn kernel_main(boot_info: &'static mut bootloader_api::BootInfo) -> ! {
|
||||
let frame_buffer_struct = frame_buffer_option.unwrap();
|
||||
|
||||
// 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
|
||||
let raw_frame_buffer = frame_buffer_struct.buffer_mut();
|
||||
|
||||
Reference in New Issue
Block a user