mirror of
https://github.com/phil-opp/blog_os.git
synced 2025-12-17 06:47:49 +00:00
Compare commits
26 Commits
post-3.1
...
15535d6217
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
15535d6217 | ||
|
|
521d4b2fd2 | ||
|
|
563c19731f | ||
|
|
79f47fda12 | ||
|
|
00fa82937a | ||
|
|
54b3a26202 | ||
|
|
d56d98988c | ||
|
|
54e41b79a5 | ||
|
|
cf28a5fdf9 | ||
|
|
2a49491fc7 | ||
|
|
ba8b0392b8 | ||
|
|
98da4b2f9a | ||
|
|
34b1eb4741 | ||
|
|
51e0dc1b63 | ||
|
|
a4b2b853b9 | ||
|
|
0a21782583 | ||
|
|
e2dca79d0d | ||
|
|
913d5189c9 | ||
|
|
29c919962f | ||
|
|
0eee1e080b | ||
|
|
ecf8fe826b | ||
|
|
4ea28d0910 | ||
|
|
88329503ad | ||
|
|
7ff6510352 | ||
|
|
eb78b1fb9b | ||
|
|
2dc10d0198 |
2
.cargo/config.toml
Normal file
2
.cargo/config.toml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
[unstable]
|
||||||
|
bindeps = true
|
||||||
12
.github/workflows/code.yml
vendored
12
.github/workflows/code.yml
vendored
@@ -15,8 +15,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- run: rustup target add x86_64-unknown-none
|
- run: cargo check
|
||||||
- run: cargo check --target x86_64-unknown-none
|
|
||||||
|
|
||||||
test:
|
test:
|
||||||
name: Test
|
name: Test
|
||||||
@@ -30,9 +29,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
- run: cargo build
|
||||||
- run: rustup target add x86_64-unknown-none
|
|
||||||
- run: cargo build --target x86_64-unknown-none
|
|
||||||
|
|
||||||
fmt:
|
fmt:
|
||||||
name: Check Formatting
|
name: Check Formatting
|
||||||
@@ -46,7 +43,4 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
- run: cargo clippy
|
||||||
- run: rustup target add x86_64-unknown-none
|
|
||||||
- run: rustup component add clippy
|
|
||||||
- run: cargo clippy --target x86_64-unknown-none
|
|
||||||
|
|||||||
1117
Cargo.lock
generated
1117
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
11
Cargo.toml
11
Cargo.toml
@@ -1,8 +1,15 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "kernel"
|
name = "blog_os"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
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]
|
[dependencies]
|
||||||
|
ovmf-prebuilt = "0.1.0-alpha"
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
kernel = { path = "kernel", artifact = "bin", target = "x86_64-unknown-none" }
|
||||||
|
bootloader = "0.11.3"
|
||||||
|
|||||||
17
README.md
17
README.md
@@ -1,23 +1,16 @@
|
|||||||
# Blog OS (Minimal Kernel)
|
# Blog OS (Booting)
|
||||||
|
|
||||||
[](https://github.com/phil-opp/blog_os/actions?query=workflow%3A%22Code%22+branch%3Apost-3.1)
|
[](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.**
|
**Check out the [master branch](https://github.com/phil-opp/blog_os) for more information.**
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|
||||||
- Install the `x86_64-unknown-none` target using rustup:
|
TODO
|
||||||
```
|
|
||||||
rustup target add x86_64-unknown-none
|
|
||||||
```
|
|
||||||
- Build by running:
|
|
||||||
```
|
|
||||||
cargo build --target x86_64-unknown-none
|
|
||||||
```
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
21
build.rs
Normal file
21
build.rs
Normal 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
13
kernel/Cargo.toml
Normal 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
138
kernel/src/framebuffer.rs
Normal 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
50
kernel/src/main.rs
Normal 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
5
rust-toolchain.toml
Normal 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
12
src/bin/qemu-bios.rs
Normal 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
13
src/bin/qemu-uefi.rs
Normal 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));
|
||||||
|
}
|
||||||
22
src/main.rs
22
src/main.rs
@@ -1,15 +1,13 @@
|
|||||||
#![no_std]
|
use std::{env, fs};
|
||||||
#![no_main]
|
|
||||||
|
|
||||||
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]
|
fs::copy(env!("UEFI_IMAGE"), &uefi_target).unwrap();
|
||||||
pub extern "C" fn _start() -> ! {
|
fs::copy(env!("BIOS_IMAGE"), &bios_target).unwrap();
|
||||||
loop {}
|
|
||||||
}
|
println!("UEFI disk image at {}", uefi_target.display());
|
||||||
|
println!("BIOS disk image at {}", bios_target.display());
|
||||||
/// This function is called on panic.
|
|
||||||
#[panic_handler]
|
|
||||||
fn panic(_info: &PanicInfo) -> ! {
|
|
||||||
loop {}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user