Compare commits

...

26 Commits

Author SHA1 Message Date
Philipp Oppermann
15535d6217 Allow splitting of framebuffer 2023-12-29 20:36:54 +01:00
Philipp Oppermann
521d4b2fd2 Rearrange circle and text 2023-12-29 20:23:52 +01:00
Philipp Oppermann
563c19731f Simplify framebuffer code 2023-12-29 20:20:42 +01:00
Philipp Oppermann
79f47fda12 Use embedded-graphics crate to draw shapes and text 2023-12-29 19:27:56 +01:00
Philipp Oppermann
00fa82937a Write blue rectangle to the screen 2023-12-29 18:42:32 +01:00
Philipp Oppermann
54b3a26202 Run cargo update 2023-12-29 16:54:52 +01:00
Philipp Oppermann
d56d98988c Run cargo update 2023-07-09 11:30:35 +02:00
Philipp Oppermann
54e41b79a5 Remove striped output example again 2023-07-09 11:29:33 +02:00
Philipp Oppermann
cf28a5fdf9 Print something to the screen 2023-05-01 15:05:34 +02:00
Philipp Oppermann
2a49491fc7 Add a qemu-uefi executable to start the UEFI disk image in QEMU 2023-05-01 14:34:05 +02:00
Philipp Oppermann
ba8b0392b8 Add a qemu-bios executable to start the BIOS disk image in QEMU 2023-05-01 14:20:01 +02:00
Philipp Oppermann
98da4b2f9a Fix rust-analyzer errors 2023-05-01 14:19:34 +02:00
Philipp Oppermann
34b1eb4741 Create the disk image in a build script 2023-05-01 13:23:37 +02:00
Philipp Oppermann
51e0dc1b63 Set default rustup profile 2023-04-30 17:16:23 +02:00
Philipp Oppermann
a4b2b853b9 Add rust-src component 2023-04-30 17:14:08 +02:00
Philipp Oppermann
0a21782583 Update CI script for second post 2023-04-30 17:10:47 +02:00
Philipp Oppermann
e2dca79d0d Use bootloader crate to create bootable disk images 2023-04-30 16:52:24 +02:00
Philipp Oppermann
913d5189c9 Silence unused variable warning 2023-04-30 16:52:03 +02:00
Philipp Oppermann
29c919962f Add an artifact dependency on the kernel 2023-04-30 15:40:47 +02:00
Philipp Oppermann
0eee1e080b Use nightly Rust 2023-04-30 15:08:43 +02:00
Philipp Oppermann
ecf8fe826b Create a cargo workspace with a new blog_os crate at the root 2023-04-30 14:53:36 +02:00
Philipp Oppermann
4ea28d0910 Merge branch 'post-3.1' into post-3.2 2023-04-30 14:00:52 +02:00
Philipp Oppermann
88329503ad Merge branch 'post-3.1' into post-3.2 2023-03-25 20:31:48 +01:00
Philipp Oppermann
7ff6510352 Use bootloader_api::entry_point macro 2023-03-25 20:12:41 +01:00
Philipp Oppermann
eb78b1fb9b Add bootloader_api dependency 2023-03-25 19:49:46 +01:00
Philipp Oppermann
2dc10d0198 Update README for second post 2023-03-25 19:19:58 +01:00
13 changed files with 1398 additions and 35 deletions

2
.cargo/config.toml Normal file
View File

@@ -0,0 +1,2 @@
[unstable]
bindeps = true

View File

@@ -15,8 +15,7 @@ jobs:
steps:
- uses: actions/checkout@v3
- run: rustup target add x86_64-unknown-none
- run: cargo check --target x86_64-unknown-none
- run: cargo check
test:
name: Test
@@ -30,9 +29,7 @@ jobs:
steps:
- uses: actions/checkout@v3
- run: rustup target add x86_64-unknown-none
- run: cargo build --target x86_64-unknown-none
- run: cargo build
fmt:
name: Check Formatting
@@ -46,7 +43,4 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: rustup target add x86_64-unknown-none
- run: rustup component add clippy
- run: cargo clippy --target x86_64-unknown-none
- run: cargo clippy

1117
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,15 @@
[package]
name = "kernel"
name = "blog_os"
version = "0.1.0"
edition = "2021"
default-run = "blog_os"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[workspace]
members = ["kernel"]
[dependencies]
ovmf-prebuilt = "0.1.0-alpha"
[build-dependencies]
kernel = { path = "kernel", artifact = "bin", target = "x86_64-unknown-none" }
bootloader = "0.11.3"

View File

@@ -1,23 +1,16 @@
# Blog OS (Minimal Kernel)
# Blog OS (Booting)
[![Build Status](https://github.com/phil-opp/blog_os/workflows/Code/badge.svg?branch=post-3.1)](https://github.com/phil-opp/blog_os/actions?query=workflow%3A%22Code%22+branch%3Apost-3.1)
[![Build Status](https://github.com/phil-opp/blog_os/workflows/Code/badge.svg?branch=post-3.2)](https://github.com/phil-opp/blog_os/actions?query=workflow%3A%22Code%22+branch%3Apost-3.2)
This repository contains the source code for the [Minimal Kernel][post] post of the [Writing an OS in Rust](https://os.phil-opp.com) series.
This repository contains the source code for the [Booting][post] post of the [Writing an OS in Rust](https://os.phil-opp.com) series.
[post]: https://os.phil-opp.com/minimal-kernel
[post]: https://os.phil-opp.com/booting
**Check out the [master branch](https://github.com/phil-opp/blog_os) for more information.**
## Building
- Install the `x86_64-unknown-none` target using rustup:
```
rustup target add x86_64-unknown-none
```
- Build by running:
```
cargo build --target x86_64-unknown-none
```
TODO
## License

21
build.rs Normal file
View File

@@ -0,0 +1,21 @@
use bootloader::DiskImageBuilder;
use std::{env, path::PathBuf};
fn main() {
// set by cargo for the kernel artifact dependency
let kernel_path = env::var("CARGO_BIN_FILE_KERNEL").unwrap();
let disk_builder = DiskImageBuilder::new(PathBuf::from(kernel_path));
// specify output paths
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
let uefi_path = out_dir.join("blog_os-uefi.img");
let bios_path = out_dir.join("blog_os-bios.img");
// create the disk images
disk_builder.create_uefi_image(&uefi_path).unwrap();
disk_builder.create_bios_image(&bios_path).unwrap();
// pass the disk image paths via environment variables
println!("cargo:rustc-env=UEFI_IMAGE={}", uefi_path.display());
println!("cargo:rustc-env=BIOS_IMAGE={}", bios_path.display());
}

13
kernel/Cargo.toml Normal file
View File

@@ -0,0 +1,13 @@
[package]
name = "kernel"
version = "0.1.0"
edition = "2021"
[[bin]]
name = "kernel"
test = false
bench = false
[dependencies]
bootloader_api = "0.11.0"
embedded-graphics = "0.8.1"

138
kernel/src/framebuffer.rs Normal file
View File

@@ -0,0 +1,138 @@
use bootloader_api::info::{FrameBuffer, FrameBufferInfo, PixelFormat};
use embedded_graphics::{
draw_target::DrawTarget,
geometry::{self, Point},
pixelcolor::{Rgb888, RgbColor},
Pixel,
};
pub struct Display {
framebuffer: &'static mut [u8],
info: FrameBufferInfo,
}
impl Display {
pub fn new(framebuffer: &'static mut FrameBuffer) -> Display {
Self {
info: framebuffer.info(),
framebuffer: framebuffer.buffer_mut(),
}
}
fn draw_pixel(&mut self, coordinates: Point, color: Rgb888) {
// ignore any pixels that are out of bounds.
let position = match (coordinates.x.try_into(), coordinates.y.try_into()) {
(Ok(x), Ok(y)) if x < self.info.width && y < self.info.height => Position { x, y },
_ => return, // ignore out-of-bounds pixel
};
let color = Color {
red: color.r(),
green: color.g(),
blue: color.b(),
};
set_pixel_in(self.framebuffer, self.info, position, color);
}
pub fn split_at_line(self, line_index: usize) -> (Self, Self) {
assert!(line_index < self.info.height);
let byte_offset = line_index * self.info.stride * self.info.bytes_per_pixel;
let (first_buffer, second_buffer) = self.framebuffer.split_at_mut(byte_offset);
let first = Self {
framebuffer: first_buffer,
info: FrameBufferInfo {
byte_len: byte_offset,
height: line_index,
..self.info
},
};
let second = Self {
framebuffer: second_buffer,
info: FrameBufferInfo {
byte_len: self.info.byte_len - byte_offset,
height: self.info.height - line_index,
..self.info
},
};
(first, second)
}
}
impl DrawTarget for Display {
type Color = Rgb888;
/// Drawing operations can never fail.
type Error = core::convert::Infallible;
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = Pixel<Self::Color>>,
{
for Pixel(coordinates, color) in pixels.into_iter() {
self.draw_pixel(coordinates, color);
}
Ok(())
}
}
impl geometry::OriginDimensions for Display {
fn size(&self) -> geometry::Size {
geometry::Size::new(
self.info.width.try_into().unwrap(),
self.info.height.try_into().unwrap(),
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Position {
pub x: usize,
pub y: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Color {
pub red: u8,
pub green: u8,
pub blue: u8,
}
pub fn set_pixel_in(
framebuffer: &mut [u8],
info: FrameBufferInfo,
position: Position,
color: Color,
) {
// calculate offset to first byte of pixel
let byte_offset = {
// use stride to calculate pixel offset of target line
let line_offset = position.y * info.stride;
// add x position to get the absolute pixel offset in buffer
let pixel_offset = line_offset + position.x;
// convert to byte offset
pixel_offset * info.bytes_per_pixel
};
// set pixel based on color format
let pixel_bytes = &mut framebuffer[byte_offset..];
match info.pixel_format {
PixelFormat::Rgb => {
pixel_bytes[0] = color.red;
pixel_bytes[1] = color.green;
pixel_bytes[2] = color.blue;
}
PixelFormat::Bgr => {
pixel_bytes[0] = color.blue;
pixel_bytes[1] = color.green;
pixel_bytes[2] = color.red;
}
PixelFormat::U8 => {
// use a simple average-based grayscale transform
let gray = color.red / 3 + color.green / 3 + color.blue / 3;
pixel_bytes[0] = gray;
}
other => panic!("unknown pixel format {other:?}"),
}
}

50
kernel/src/main.rs Normal file
View File

@@ -0,0 +1,50 @@
#![no_std]
#![no_main]
use core::{convert::Infallible, panic::PanicInfo};
use bootloader_api::BootInfo;
use embedded_graphics::{
draw_target::DrawTarget,
geometry::Point,
mono_font::{ascii::FONT_10X20, MonoTextStyle},
pixelcolor::{Rgb888, RgbColor},
primitives::{Circle, PrimitiveStyle, StyledDrawable},
text::Text,
Drawable,
};
mod framebuffer;
bootloader_api::entry_point!(kernel_main);
fn kernel_main(boot_info: &'static mut BootInfo) -> ! {
if let Some(framebuffer) = boot_info.framebuffer.as_mut() {
let height = framebuffer.info().height;
let display = framebuffer::Display::new(framebuffer);
let (mut upper, mut lower) = display.split_at_line(height / 2);
upper.clear(Rgb888::RED).unwrap_or_else(infallible);
lower.clear(Rgb888::BLUE).unwrap_or_else(infallible);
let style = PrimitiveStyle::with_fill(Rgb888::YELLOW);
Circle::new(Point::new(50, 50), 300)
.draw_styled(&style, &mut upper)
.unwrap_or_else(infallible);
let character_style = MonoTextStyle::new(&FONT_10X20, Rgb888::BLUE);
let text = Text::new("Hello, world!", Point::new(140, 210), character_style);
text.draw(&mut upper).unwrap_or_else(infallible);
}
loop {}
}
/// This function is called on panic.
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
loop {}
}
fn infallible<T>(v: Infallible) -> T {
match v {}
}

5
rust-toolchain.toml Normal file
View File

@@ -0,0 +1,5 @@
[toolchain]
channel = "nightly"
profile = "default"
targets = ["x86_64-unknown-none"]
components = ["rust-src", "llvm-tools-preview"]

12
src/bin/qemu-bios.rs Normal file
View File

@@ -0,0 +1,12 @@
use std::{
env,
process::{self, Command},
};
fn main() {
let mut qemu = Command::new("qemu-system-x86_64");
qemu.arg("-drive");
qemu.arg(format!("format=raw,file={}", env!("BIOS_IMAGE")));
let exit_status = qemu.status().unwrap();
process::exit(exit_status.code().unwrap_or(-1));
}

13
src/bin/qemu-uefi.rs Normal file
View File

@@ -0,0 +1,13 @@
use std::{
env,
process::{self, Command},
};
fn main() {
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());
let exit_status = qemu.status().unwrap();
process::exit(exit_status.code().unwrap_or(-1));
}

View File

@@ -1,15 +1,13 @@
#![no_std]
#![no_main]
use std::{env, fs};
use core::panic::PanicInfo;
fn main() {
let current_exe = env::current_exe().unwrap();
let uefi_target = current_exe.with_file_name("uefi.img");
let bios_target = current_exe.with_file_name("bios.img");
#[no_mangle]
pub extern "C" fn _start() -> ! {
loop {}
}
/// This function is called on panic.
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
loop {}
fs::copy(env!("UEFI_IMAGE"), &uefi_target).unwrap();
fs::copy(env!("BIOS_IMAGE"), &bios_target).unwrap();
println!("UEFI disk image at {}", uefi_target.display());
println!("BIOS disk image at {}", bios_target.display());
}