initial commit

This commit is contained in:
Bryan McShea
2024-01-16 00:15:13 -05:00
commit 42b47201fe
18 changed files with 1922 additions and 0 deletions

5
.cargo/config.toml Normal file
View File

@@ -0,0 +1,5 @@
# [build]
# rustflags = ["-g"]
[unstable]
bindeps = true

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/target

1356
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

19
Cargo.toml Normal file
View File

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

20
build.rs Normal file
View File

@@ -0,0 +1,20 @@
use bootloader::DiskImageBuilder;
use std::{env, path::PathBuf};
fn main() {
let kernel_path = env::var("CARGO_BIN_FILE_KERNEL").unwrap();
println!("{kernel_path}");
let disk_builder = DiskImageBuilder::new(PathBuf::from(kernel_path.clone()));
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
let name = env::var("CARGO_PKG_NAME").unwrap();
let uefi_path = out_dir.join(name.clone() + "-uefi.img");
let bios_path = out_dir.join(name + "-bios.img");
disk_builder.create_uefi_image(&uefi_path).unwrap();
disk_builder.create_bios_image(&bios_path).unwrap();
println!("cargo:rustc-env=UEFI_IMAGE={}", uefi_path.display());
println!("cargo:rustc-env=BIOS_IMAGE={}", bios_path.display());
println!("cargo:rustc-env=KERNEL_BIN={}", kernel_path);
}

1
kernel/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/target

23
kernel/Cargo.toml Normal file
View File

@@ -0,0 +1,23 @@
[package]
name = "kernel"
version = "0.1.0"
edition = "2021"
[[bin]]
name = "kernel"
test = false
bench = false
[dependencies]
bootloader_api = "0.11.5"
embedded-graphics = "0.8.1"
uart_16550 = "0.3.0"
x86_64 = "0.14.11"
spin = "0.9.8"
pic8259 = "0.10.1"
pc-keyboard = "0.5.0"
[dependencies.lazy_static]
version = "1.4.0"
features = ["spin_no_std"]

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

@@ -0,0 +1,172 @@
use bootloader_api::info::FrameBuffer;
use embedded_graphics::{
draw_target::DrawTarget,
geometry::{Dimensions, OriginDimensions, Point, Size},
mono_font::{ascii::FONT_6X10, MonoTextStyle},
pixelcolor::{Rgb888, RgbColor},
primitives::{
Circle, Primitive, PrimitiveStyle, PrimitiveStyleBuilder, Rectangle, StrokeAlignment,
Triangle,
},
text::{Alignment, Text},
Drawable, Pixel,
};
#[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 FrameBuffer, position: Position, color: Color) {
let info = framebuffer.info();
let byte_offset = {
let line_offset = position.y * info.stride;
let pixel_offset = line_offset + position.x;
pixel_offset * info.bytes_per_pixel
};
let pixel_buffer = &mut framebuffer.buffer_mut()[byte_offset..];
match info.pixel_format {
bootloader_api::info::PixelFormat::Rgb => {
pixel_buffer[0] = color.red;
pixel_buffer[1] = color.green;
pixel_buffer[2] = color.blue;
}
bootloader_api::info::PixelFormat::Bgr => {
pixel_buffer[0] = color.blue;
pixel_buffer[1] = color.green;
pixel_buffer[2] = color.red;
}
bootloader_api::info::PixelFormat::U8 => {
let gray = color.red / 3 + color.green / 3 + color.blue / 3;
pixel_buffer[0] = gray;
}
other => panic!("unknown pixel format {other:?}"),
}
}
pub struct Display<'a> {
framebuffer: &'a mut FrameBuffer,
}
impl<'a> Display<'a> {
pub fn new(framebuffer: &'a mut FrameBuffer) -> Self {
Self { framebuffer }
}
fn draw_pixel(&mut self, pos: Position, color: Color) {
let (width, height) = {
let info = self.framebuffer.info();
(info.width, info.height)
};
if pos.x >= width || pos.y >= height {
return;
}
set_pixel_in(&mut self.framebuffer, pos, color);
}
}
impl OriginDimensions for Display<'_> {
fn size(&self) -> embedded_graphics::prelude::Size {
let info = self.framebuffer.info();
Size {
width: info.width as u32,
height: info.height as u32,
}
}
}
impl DrawTarget for Display<'_> {
type Color = Rgb888;
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(
Position {
x: coordinates.x as usize,
y: coordinates.y as usize,
},
Color {
red: color.r(),
green: color.g(),
blue: color.b(),
},
);
}
Ok(())
}
}
pub fn draw_test(framebuffer: &mut FrameBuffer) {
for b in framebuffer.buffer_mut() {
*b = 0;
}
let mut display = Display::new(framebuffer);
// Create styles used by the drawing operations.
let thin_stroke = PrimitiveStyle::with_stroke(Rgb888::new(0, 255, 255), 1);
let thick_stroke = PrimitiveStyle::with_stroke(Rgb888::new(0, 255, 255), 3);
let border_stroke = PrimitiveStyleBuilder::new()
.stroke_color(Rgb888::new(0, 255, 255))
.stroke_width(3)
.stroke_alignment(StrokeAlignment::Inside)
.build();
let fill = PrimitiveStyle::with_fill(Rgb888::new(0, 255, 255));
let character_style = MonoTextStyle::new(&FONT_6X10, Rgb888::new(0, 255, 255));
let yoffset = 10;
// Draw a 3px wide outline around the display.
display
.bounding_box()
.into_styled(border_stroke)
.draw(&mut display)
.unwrap();
Rectangle::new(Point::new(52, yoffset), Size::new(16, 16))
.into_styled(fill)
.draw(&mut display)
.unwrap();
// Draw a triangle.
Triangle::new(
Point::new(16, 16 + yoffset),
Point::new(16 + 16, 16 + yoffset),
Point::new(16 + 8, yoffset),
)
.into_styled(thin_stroke)
.draw(&mut display)
.unwrap();
// Draw a filled square
Rectangle::new(Point::new(52, yoffset), Size::new(16, 16))
.into_styled(fill)
.draw(&mut display)
.unwrap();
// Draw a circle with a 3px wide stroke.
Circle::new(Point::new(88, yoffset), 17)
.into_styled(thick_stroke)
.draw(&mut display)
.unwrap();
// Draw centered text.
let text = "embedded-graphics";
Text::with_alignment(
text,
display.bounding_box().center() + Point::new(0, 15),
character_style,
Alignment::Center,
)
.draw(&mut display)
.unwrap();
}

49
kernel/src/gdt.rs Normal file
View File

@@ -0,0 +1,49 @@
use lazy_static::lazy_static;
use x86_64::{
structures::{
gdt::{Descriptor, GlobalDescriptorTable, SegmentSelector},
tss::TaskStateSegment,
},
VirtAddr, registers::segmentation::{CS, Segment}, instructions::tables::load_tss,
};
pub const DOUBLE_FAULT_IST_INDEX: u16 = 0;
pub fn init() {
GDT.0.load();
unsafe {
CS::set_reg(GDT.1.code);
load_tss(GDT.1.tss);
// TODO: properly handle this
x86_64::registers::segmentation::SS::set_reg(SegmentSelector::NULL);
}
}
struct Selectors {
code: SegmentSelector,
tss: SegmentSelector,
}
lazy_static! {
static ref GDT: (GlobalDescriptorTable, Selectors) = {
let mut gdt = GlobalDescriptorTable::new();
let code = gdt.add_entry(Descriptor::kernel_code_segment());
let tss = gdt.add_entry(Descriptor::tss_segment(&TSS));
(gdt, Selectors { code, tss })
};
}
lazy_static! {
static ref TSS: TaskStateSegment = {
let mut tss = TaskStateSegment::new();
tss.interrupt_stack_table[DOUBLE_FAULT_IST_INDEX as usize] = {
const STACK_SIZE: usize = 4096 * 5;
static mut STACK: [u8; STACK_SIZE] = [0; STACK_SIZE];
let stack_start = VirtAddr::from_ptr(unsafe { &STACK });
let stack_end = stack_start + STACK_SIZE;
stack_end
};
tss
};
}

94
kernel/src/interrupts.rs Normal file
View File

@@ -0,0 +1,94 @@
use crate::{gdt, print, println};
use lazy_static::lazy_static;
use pic8259::ChainedPics;
use x86_64::structures::idt::{InterruptDescriptorTable, InterruptStackFrame};
#[derive(Debug, Clone, Copy)]
#[repr(u8)]
pub enum InterruptIndex {
Timer = PIC_1_OFFSET,
Keyboard = PIC_1_OFFSET + 1,
}
impl InterruptIndex {
fn as_u8(self) -> u8 {
self as u8
}
fn as_usize(self) -> usize {
self as usize
}
}
pub fn init() {
IDT.load();
unsafe { PICS.lock().initialize() }
x86_64::instructions::interrupts::enable();
}
lazy_static! {
static ref IDT: InterruptDescriptorTable = {
let mut idt = InterruptDescriptorTable::new();
unsafe {
idt.double_fault
.set_handler_fn(double_fault)
.set_stack_index(gdt::DOUBLE_FAULT_IST_INDEX);
}
idt.breakpoint.set_handler_fn(breakpoint);
idt[InterruptIndex::Timer.as_usize()].set_handler_fn(timer);
idt[InterruptIndex::Keyboard.as_usize()].set_handler_fn(keyboard);
idt
};
}
extern "x86-interrupt" fn timer(_stack_frame: InterruptStackFrame) {
// print!(".");
unsafe {
PICS.lock()
.notify_end_of_interrupt(InterruptIndex::Timer as u8)
}
}
extern "x86-interrupt" fn breakpoint(_stack_frame: InterruptStackFrame) {
println!("POGGERS");
}
extern "x86-interrupt" fn double_fault(stack_frame: InterruptStackFrame, _error_code: u64) -> ! {
panic!("double fault exception: {:#?}", stack_frame);
}
extern "x86-interrupt" fn keyboard(_stack_frame: InterruptStackFrame) {
use pc_keyboard::{layouts, DecodedKey, HandleControl, Keyboard, ScancodeSet1};
use spin::Mutex;
use x86_64::instructions::port::Port;
lazy_static! {
static ref KEYBOARD: Mutex<Keyboard<layouts::Us104Key, ScancodeSet1>> =
Mutex::new(Keyboard::new(layouts::Us104Key, ScancodeSet1,
HandleControl::Ignore)
);
}
let mut keyboard = KEYBOARD.lock();
let mut port = Port::new(0x60);
let scancode: u8 = unsafe { port.read() };
if let Ok(Some(key_event)) = keyboard.add_byte(scancode) {
if let Some(key) = keyboard.process_keyevent(key_event) {
match key {
DecodedKey::Unicode(character) => print!("{}", character),
DecodedKey::RawKey(key) => print!("{:?}", key),
}
}
}
unsafe {
PICS.lock()
.notify_end_of_interrupt(InterruptIndex::Keyboard.as_u8());
}
}
pub const PIC_1_OFFSET: u8 = 32;
pub const PIC_2_OFFSET: u8 = PIC_1_OFFSET + 8;
pub static PICS: spin::Mutex<ChainedPics> =
spin::Mutex::new(unsafe { ChainedPics::new(PIC_1_OFFSET, PIC_2_OFFSET) });

25
kernel/src/lib.rs Normal file
View File

@@ -0,0 +1,25 @@
#![no_std]
#![feature(abi_x86_interrupt)]
pub mod framebuffer;
pub mod gdt;
pub mod interrupts;
pub mod qemu;
pub mod log;
pub fn init() {
gdt::init();
interrupts::init();
}
pub fn exit() -> ! {
qemu::exit();
hlt_loop()
}
pub fn hlt_loop() -> ! {
loop {
x86_64::instructions::hlt();
}
}

16
kernel/src/log.rs Normal file
View File

@@ -0,0 +1,16 @@
use core::fmt::Arguments;
#[doc(hidden)]
pub fn _log(args: Arguments<'_>) {
use core::fmt::Write;
interrupts::without_interrupts(|| {
UART.lock().write_fmt(args).unwrap();
})
}
#[macro_export]
macro_rules! log {
($($arg:tt)*) => ($crate::_log(format_args!($($arg)*)));
}

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

@@ -0,0 +1,20 @@
#![no_std]
#![no_main]
use kernel::{framebuffer, init, exit, println, hlt_loop};
bootloader_api::entry_point!(kernel_main);
fn kernel_main(boot_info: &'static mut bootloader_api::BootInfo) -> ! {
init();
if let Some(framebuffer) = boot_info.framebuffer.as_mut() {
framebuffer::draw_test(framebuffer);
}
for _ in 0..20000000 {}
hlt_loop();
}
#[panic_handler]
fn panic(info: &core::panic::PanicInfo) -> ! {
println!("{}", info);
exit()
}

33
kernel/src/qemu.rs Normal file
View File

@@ -0,0 +1,33 @@
use core::fmt::Arguments;
use spin::Mutex;
use uart_16550::SerialPort;
use x86_64::instructions::{interrupts, port::Port};
pub static UART: Mutex<SerialPort> = Mutex::new(unsafe { SerialPort::new(0x3F8) });
pub fn exit() {
unsafe {
let mut port = Port::new(0xf4);
port.write(0x10u32);
}
}
#[doc(hidden)]
pub fn _print(args: Arguments<'_>) {
use core::fmt::Write;
interrupts::without_interrupts(|| {
UART.lock().write_fmt(args).unwrap();
})
}
#[macro_export]
macro_rules! print {
($($arg:tt)*) => ($crate::qemu::_print(format_args!($($arg)*)));
}
#[macro_export]
macro_rules! println {
() => ($crate::print!("\n"));
($($arg:tt)*) => ($crate::print!("{}\n", format_args!($($arg)*)));
}

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"]

10
src/bin/copy-target.rs Normal file
View File

@@ -0,0 +1,10 @@
use std::{env, fs};
fn main() {
let current_exe = env::current_exe().unwrap();
let uefi = current_exe.with_file_name("uefi.img");
let bios = current_exe.with_file_name("bios.img");
fs::copy(env!("UEFI_IMAGE"), &uefi).unwrap();
fs::copy(env!("BIOS_IMAGE"), &bios).unwrap();
}

55
src/bin/qemu.rs Normal file
View File

@@ -0,0 +1,55 @@
use std::{
env,
path::PathBuf,
process::{self, Command, Stdio},
};
use clap::{Args, Parser, ValueEnum};
use os::Bootloader;
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Cli {
/// whether to use BIOS or UEFI
#[arg(long, short, id = "type", default_value = "bios")]
bootloader: Bootloader,
/// whether to use gdb
#[arg(long, short, id = "port")]
gdb: Option<Option<u16>>,
}
fn main() {
let args = Cli::parse();
let mut qemu = Command::new("qemu-system-x86_64");
qemu.args(["-device", "isa-debug-exit,iobase=0xf4,iosize=0x04"]);
qemu.args(["-serial", "stdio"]);
qemu.arg("-drive");
qemu.arg(format!("format=raw,file={}", args.bootloader.img_path()));
if let Bootloader::UEFI = args.bootloader {
qemu.arg("-bios").arg(ovmf_prebuilt::ovmf_pure_efi());
}
if let Some(port) = args.gdb {
let port = port.unwrap_or(1234);
qemu.arg("-S");
qemu.args(["-gdb", &format!("tcp::{}", port)]);
let mut gdb = Command::new("rust-gdb");
gdb.arg("-q");
gdb.args(["-ex", "target remote :1234"]);
gdb.args([
"-ex",
&format!("symbol-file {} -o 0x8000000000", env!("KERNEL_BIN")),
]);
gdb.args(["-ex", "b kernel::kernel_main"]);
gdb.args(["-ex", "c"]);
let handle = std::thread::spawn(move || {
qemu.stdin(Stdio::null());
qemu.stdout(Stdio::null());
let exit_status = qemu.status().unwrap();
});
gdb.status().unwrap();
handle.join().unwrap();
} else {
let exit_status = qemu.status().unwrap();
process::exit(exit_status.code().unwrap_or(-1));
}
}

18
src/lib.rs Normal file
View File

@@ -0,0 +1,18 @@
use std::env;
use clap::ValueEnum;
#[derive(Copy, Clone, ValueEnum)]
pub enum Bootloader {
UEFI,
BIOS,
}
impl Bootloader {
pub fn img_path(&self) -> &'static str {
match self {
Bootloader::UEFI => env!("UEFI_IMAGE"),
Bootloader::BIOS => env!("BIOS_IMAGE"),
}
}
}