initial commit
This commit is contained in:
commit
ccd4631e2b
|
@ -0,0 +1,5 @@
|
||||||
|
[build]
|
||||||
|
target = "x86_64-xe_os.json"
|
||||||
|
|
||||||
|
[target.'cfg(target_os = "none")']
|
||||||
|
runner = "bootimage runner"
|
|
@ -0,0 +1,5 @@
|
||||||
|
.DS_Store
|
||||||
|
.idea
|
||||||
|
*.log
|
||||||
|
tmp/
|
||||||
|
target/
|
|
@ -0,0 +1,104 @@
|
||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
[[package]]
|
||||||
|
name = "array-init"
|
||||||
|
version = "0.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"nodrop 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bit_field"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "1.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bootloader"
|
||||||
|
version = "0.8.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cast"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lazy_static"
|
||||||
|
version = "1.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nodrop"
|
||||||
|
version = "0.1.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "spin"
|
||||||
|
version = "0.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "uart_16550"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"x86_64 0.7.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ux"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "volatile"
|
||||||
|
version = "0.2.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "x86_64"
|
||||||
|
version = "0.7.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"array-init 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"bit_field 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"cast 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"ux 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "xe_os"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"bootloader 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"uart_16550 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"volatile 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"x86_64 0.7.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[metadata]
|
||||||
|
"checksum array-init 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "23589ecb866b460d3a0f1278834750268c607e8e28a1b982c907219f3178cd72"
|
||||||
|
"checksum bit_field 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ed8765909f9009617974ab6b7d332625b320b33c326b1e9321382ef1999b5d56"
|
||||||
|
"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||||
|
"checksum bootloader 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "abfbe6cdea6367860818facc8e4a184f003cb83d7d004acaaf57baebf1949da0"
|
||||||
|
"checksum cast 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "926013f2860c46252efceabb19f4a6b308197505082c609025aa6706c011d427"
|
||||||
|
"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||||
|
"checksum nodrop 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
|
||||||
|
"checksum spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
|
||||||
|
"checksum uart_16550 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "803ea8cb602dbb32c1a657a866d2dd79fe7dbeab0fb2ac667cb4dcc7de12a58b"
|
||||||
|
"checksum ux 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "88dfeb711b61ce620c0cb6fd9f8e3e678622f0c971da2a63c4b3e25e88ed012f"
|
||||||
|
"checksum volatile 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "6af0edf5b4faacc31fc51159244d78d65ec580f021afcef7bd53c04aeabc7f29"
|
||||||
|
"checksum x86_64 0.7.5 (registry+https://github.com/rust-lang/crates.io-index)" = "997837f3913aac8f67164683258756d712376849906c8d609f844084bc03ef84"
|
|
@ -0,0 +1,44 @@
|
||||||
|
[package]
|
||||||
|
name = "xe_os"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Christine Dodrill <me@christine.website>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
bootloader = "0.8.0"
|
||||||
|
volatile = "0.2.6"
|
||||||
|
spin = "0.5.2"
|
||||||
|
x86_64 = "0.7.5"
|
||||||
|
uart_16550 = "0.2.0"
|
||||||
|
|
||||||
|
[dependencies.lazy_static]
|
||||||
|
version = "1.0"
|
||||||
|
features = ["spin_no_std"]
|
||||||
|
|
||||||
|
[profile.dev]
|
||||||
|
panic = "abort"
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
panic = "abort"
|
||||||
|
|
||||||
|
[package.metadata.bootimage]
|
||||||
|
run-args = [
|
||||||
|
"-display", "curses"
|
||||||
|
]
|
||||||
|
test-args = [
|
||||||
|
"-device", "isa-debug-exit,iobase=0xf4,iosize=0x04",
|
||||||
|
"-display", "none",
|
||||||
|
"-serial", "stdio"
|
||||||
|
]
|
||||||
|
test-success-exit-code = 33
|
||||||
|
test-timeout = 300
|
||||||
|
|
||||||
|
[[test]]
|
||||||
|
name = "should_panic"
|
||||||
|
harness = false
|
||||||
|
|
||||||
|
[[test]]
|
||||||
|
name = "stack_overflow"
|
||||||
|
harness = false
|
|
@ -0,0 +1,46 @@
|
||||||
|
use x86_64::VirtAddr;
|
||||||
|
use x86_64::structures::tss::TaskStateSegment;
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use x86_64::structures::gdt::{GlobalDescriptorTable, Descriptor, SegmentSelector};
|
||||||
|
|
||||||
|
pub const DOUBLE_FAULT_IST_INDEX: u16 = 0;
|
||||||
|
|
||||||
|
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;
|
||||||
|
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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init() {
|
||||||
|
use x86_64::instructions::segmentation::set_cs;
|
||||||
|
use x86_64::instructions::tables::load_tss;
|
||||||
|
|
||||||
|
GDT.0.load();
|
||||||
|
unsafe {
|
||||||
|
set_cs(GDT.1.code_selector);
|
||||||
|
load_tss(GDT.1.tss_selector);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref GDT: (GlobalDescriptorTable, Selectors) = {
|
||||||
|
let mut gdt = GlobalDescriptorTable::new();
|
||||||
|
let code_selector = gdt.add_entry(Descriptor::kernel_code_segment());
|
||||||
|
let tss_selector = gdt.add_entry(Descriptor::tss_segment(&TSS));
|
||||||
|
(gdt, Selectors { code_selector, tss_selector })
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Selectors {
|
||||||
|
code_selector: SegmentSelector,
|
||||||
|
tss_selector: SegmentSelector,
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
use x86_64::structures::idt::{InterruptDescriptorTable, InterruptStackFrame};
|
||||||
|
use crate::{gdt, println};
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref IDT: InterruptDescriptorTable = {
|
||||||
|
let mut idt = InterruptDescriptorTable::new();
|
||||||
|
idt.breakpoint.set_handler_fn(breakpoint_handler);
|
||||||
|
unsafe {
|
||||||
|
idt.double_fault.set_handler_fn(double_fault_handler)
|
||||||
|
.set_stack_index(gdt::DOUBLE_FAULT_IST_INDEX);
|
||||||
|
}
|
||||||
|
idt
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init_idt() {
|
||||||
|
IDT.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "x86-interrupt" fn breakpoint_handler(
|
||||||
|
stack_frame: &mut InterruptStackFrame)
|
||||||
|
{
|
||||||
|
println!("EXCEPTION: BREAKPOINT\n{:#?}", stack_frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "x86-interrupt" fn double_fault_handler(
|
||||||
|
stack_frame: &mut InterruptStackFrame, _error_code: u64)
|
||||||
|
{
|
||||||
|
panic!("EXCEPTION: DOUBLE FAULT\n{:#?}", stack_frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
use crate::{serial_print, serial_println};
|
||||||
|
|
||||||
|
#[test_case]
|
||||||
|
fn test_breakpoint_exception() {
|
||||||
|
serial_print!("test_breakpoint_exception...");
|
||||||
|
// invoke a breakpoint exception
|
||||||
|
x86_64::instructions::interrupts::int3();
|
||||||
|
serial_println!("[ok]");
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
#![no_std]
|
||||||
|
#![cfg_attr(test, no_main)]
|
||||||
|
#![feature(abi_x86_interrupt)]
|
||||||
|
#![feature(custom_test_frameworks)]
|
||||||
|
#![test_runner(crate::test_runner)]
|
||||||
|
#![reexport_test_harness_main = "test_main"]
|
||||||
|
|
||||||
|
pub mod gdt;
|
||||||
|
pub mod serial;
|
||||||
|
pub mod interrupts;
|
||||||
|
pub mod vga_buffer;
|
||||||
|
|
||||||
|
use core::panic::PanicInfo;
|
||||||
|
|
||||||
|
pub fn init() {
|
||||||
|
gdt::init();
|
||||||
|
interrupts::init_idt();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn test_runner(tests: &[&dyn Fn()]) {
|
||||||
|
serial_println!("Running {} tests", tests.len());
|
||||||
|
for test in tests {
|
||||||
|
test();
|
||||||
|
}
|
||||||
|
exit_qemu(QemuExitCode::Success);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn test_panic_handler(info: &PanicInfo) -> ! {
|
||||||
|
serial_println!("[failed]\n");
|
||||||
|
serial_println!("Error: {}\n", info);
|
||||||
|
exit_qemu(QemuExitCode::Failed);
|
||||||
|
loop {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Entry point for `cargo xtest`
|
||||||
|
#[cfg(test)]
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn _start() -> ! {
|
||||||
|
init();
|
||||||
|
test_main();
|
||||||
|
loop {}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[panic_handler]
|
||||||
|
fn panic(info: &PanicInfo) -> ! {
|
||||||
|
test_panic_handler(info)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
#[repr(u32)]
|
||||||
|
pub enum QemuExitCode {
|
||||||
|
Success = 0x10,
|
||||||
|
Failed = 0x11,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn exit_qemu(exit_code: QemuExitCode) {
|
||||||
|
use x86_64::instructions::port::Port;
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let mut port = Port::new(0xf4);
|
||||||
|
port.write(exit_code as u32);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
#![no_std]
|
||||||
|
#![no_main]
|
||||||
|
#![feature(custom_test_frameworks)]
|
||||||
|
#![test_runner(xe_os::test_runner)]
|
||||||
|
#![reexport_test_harness_main = "test_main"]
|
||||||
|
|
||||||
|
use core::panic::PanicInfo;
|
||||||
|
use xe_os::println;
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn _start() -> ! {
|
||||||
|
xe_os::init();
|
||||||
|
println!("Hello World{}", "!");
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
test_main();
|
||||||
|
|
||||||
|
println!("It did not crash!");
|
||||||
|
loop {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// our existing panic handler
|
||||||
|
#[cfg(not(test))] // new attribute
|
||||||
|
#[panic_handler]
|
||||||
|
fn panic(info: &PanicInfo) -> ! {
|
||||||
|
println!("{}", info);
|
||||||
|
loop {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// our panic handler in test mode
|
||||||
|
#[cfg(test)]
|
||||||
|
#[panic_handler]
|
||||||
|
fn panic(info: &PanicInfo) -> ! {
|
||||||
|
xe_os::test_panic_handler(info);
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
use uart_16550::SerialPort;
|
||||||
|
use spin::Mutex;
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
pub static ref SERIAL1: Mutex<SerialPort> = {
|
||||||
|
let mut serial_port = unsafe { SerialPort::new(0x3F8) };
|
||||||
|
serial_port.init();
|
||||||
|
Mutex::new(serial_port)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn _print(args: ::core::fmt::Arguments) {
|
||||||
|
use core::fmt::Write;
|
||||||
|
SERIAL1.lock().write_fmt(args).expect("Printing to serial failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prints to the host through the serial interface.
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! serial_print {
|
||||||
|
($($arg:tt)*) => {
|
||||||
|
$crate::serial::_print(format_args!($($arg)*));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prints to the host through the serial interface, appending a newline.
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! serial_println {
|
||||||
|
() => ($crate::serial_print!("\n"));
|
||||||
|
($fmt:expr) => ($crate::serial_print!(concat!($fmt, "\n")));
|
||||||
|
($fmt:expr, $($arg:tt)*) => ($crate::serial_print!(
|
||||||
|
concat!($fmt, "\n"), $($arg)*));
|
||||||
|
}
|
|
@ -0,0 +1,204 @@
|
||||||
|
use core::fmt;
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use spin::Mutex;
|
||||||
|
use volatile::Volatile;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
/// A global `Writer` instance that can be used for printing to the VGA text buffer.
|
||||||
|
///
|
||||||
|
/// Used by the `print!` and `println!` macros.
|
||||||
|
pub static ref WRITER: Mutex<Writer> = Mutex::new(Writer {
|
||||||
|
column_position: 0,
|
||||||
|
color_code: ColorCode::new(Color::Yellow, Color::Black),
|
||||||
|
buffer: unsafe { &mut *(0xb8000 as *mut Buffer) },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The standard color palette in VGA text mode.
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum Color {
|
||||||
|
Black = 0,
|
||||||
|
Blue = 1,
|
||||||
|
Green = 2,
|
||||||
|
Cyan = 3,
|
||||||
|
Red = 4,
|
||||||
|
Magenta = 5,
|
||||||
|
Brown = 6,
|
||||||
|
LightGray = 7,
|
||||||
|
DarkGray = 8,
|
||||||
|
LightBlue = 9,
|
||||||
|
LightGreen = 10,
|
||||||
|
LightCyan = 11,
|
||||||
|
LightRed = 12,
|
||||||
|
Pink = 13,
|
||||||
|
Yellow = 14,
|
||||||
|
White = 15,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A combination of a foreground and a background color.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
#[repr(transparent)]
|
||||||
|
struct ColorCode(u8);
|
||||||
|
|
||||||
|
impl ColorCode {
|
||||||
|
/// Create a new `ColorCode` with the given foreground and background colors.
|
||||||
|
fn new(foreground: Color, background: Color) -> ColorCode {
|
||||||
|
ColorCode((background as u8) << 4 | (foreground as u8))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A screen character in the VGA text buffer, consisting of an ASCII character and a `ColorCode`.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
#[repr(C)]
|
||||||
|
struct ScreenChar {
|
||||||
|
ascii_character: u8,
|
||||||
|
color_code: ColorCode,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The height of the text buffer (normally 25 lines).
|
||||||
|
const BUFFER_HEIGHT: usize = 25;
|
||||||
|
/// The width of the text buffer (normally 80 columns).
|
||||||
|
const BUFFER_WIDTH: usize = 80;
|
||||||
|
|
||||||
|
/// A structure representing the VGA text buffer.
|
||||||
|
#[repr(transparent)]
|
||||||
|
struct Buffer {
|
||||||
|
chars: [[Volatile<ScreenChar>; BUFFER_WIDTH]; BUFFER_HEIGHT],
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A writer type that allows writing ASCII bytes and strings to an underlying `Buffer`.
|
||||||
|
///
|
||||||
|
/// Wraps lines at `BUFFER_WIDTH`. Supports newline characters and implements the
|
||||||
|
/// `core::fmt::Write` trait.
|
||||||
|
pub struct Writer {
|
||||||
|
column_position: usize,
|
||||||
|
color_code: ColorCode,
|
||||||
|
buffer: &'static mut Buffer,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Writer {
|
||||||
|
/// Writes an ASCII byte to the buffer.
|
||||||
|
///
|
||||||
|
/// Wraps lines at `BUFFER_WIDTH`. Supports the `\n` newline character.
|
||||||
|
pub fn write_byte(&mut self, byte: u8) {
|
||||||
|
match byte {
|
||||||
|
b'\n' => self.new_line(),
|
||||||
|
byte => {
|
||||||
|
if self.column_position >= BUFFER_WIDTH {
|
||||||
|
self.new_line();
|
||||||
|
}
|
||||||
|
|
||||||
|
let row = BUFFER_HEIGHT - 1;
|
||||||
|
let col = self.column_position;
|
||||||
|
|
||||||
|
let color_code = self.color_code;
|
||||||
|
self.buffer.chars[row][col].write(ScreenChar {
|
||||||
|
ascii_character: byte,
|
||||||
|
color_code,
|
||||||
|
});
|
||||||
|
self.column_position += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes the given ASCII string to the buffer.
|
||||||
|
///
|
||||||
|
/// Wraps lines at `BUFFER_WIDTH`. Supports the `\n` newline character. Does **not**
|
||||||
|
/// support strings with non-ASCII characters, since they can't be printed in the VGA text
|
||||||
|
/// mode.
|
||||||
|
fn write_string(&mut self, s: &str) {
|
||||||
|
for byte in s.bytes() {
|
||||||
|
match byte {
|
||||||
|
// printable ASCII byte or newline
|
||||||
|
0x20..=0x7e | b'\n' => self.write_byte(byte),
|
||||||
|
// not part of printable ASCII range
|
||||||
|
_ => self.write_byte(0xfe),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Shifts all lines one line up and clears the last row.
|
||||||
|
fn new_line(&mut self) {
|
||||||
|
for row in 1..BUFFER_HEIGHT {
|
||||||
|
for col in 0..BUFFER_WIDTH {
|
||||||
|
let character = self.buffer.chars[row][col].read();
|
||||||
|
self.buffer.chars[row - 1][col].write(character);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.clear_row(BUFFER_HEIGHT - 1);
|
||||||
|
self.column_position = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clears a row by overwriting it with blank characters.
|
||||||
|
fn clear_row(&mut self, row: usize) {
|
||||||
|
let blank = ScreenChar {
|
||||||
|
ascii_character: b' ',
|
||||||
|
color_code: self.color_code,
|
||||||
|
};
|
||||||
|
for col in 0..BUFFER_WIDTH {
|
||||||
|
self.buffer.chars[row][col].write(blank);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Write for Writer {
|
||||||
|
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||||
|
self.write_string(s);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Like the `print!` macro in the standard library, but prints to the VGA text buffer.
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! print {
|
||||||
|
($($arg:tt)*) => ($crate::vga_buffer::_print(format_args!($($arg)*)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Like the `println!` macro in the standard library, but prints to the VGA text buffer.
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! println {
|
||||||
|
() => ($crate::print!("\n"));
|
||||||
|
($($arg:tt)*) => ($crate::print!("{}\n", format_args!($($arg)*)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prints the given formatted string to the VGA text buffer through the global `WRITER` instance.
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn _print(args: fmt::Arguments) {
|
||||||
|
use core::fmt::Write;
|
||||||
|
WRITER.lock().write_fmt(args).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
use crate::{serial_print, serial_println};
|
||||||
|
|
||||||
|
#[test_case]
|
||||||
|
fn test_println_simple() {
|
||||||
|
serial_print!("test_println... ");
|
||||||
|
println!("test_println_simple output");
|
||||||
|
serial_println!("[ok]");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test_case]
|
||||||
|
fn test_println_many() {
|
||||||
|
serial_print!("test_println_many... ");
|
||||||
|
for _ in 0..200 {
|
||||||
|
println!("test_println_many output");
|
||||||
|
}
|
||||||
|
serial_println!("[ok]");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test_case]
|
||||||
|
fn test_println_output() {
|
||||||
|
serial_print!("test_println_output... ");
|
||||||
|
|
||||||
|
let s = "Some test string that fits on a single line";
|
||||||
|
println!("{}", s);
|
||||||
|
for (i, c) in s.chars().enumerate() {
|
||||||
|
let screen_char = WRITER.lock().buffer.chars[BUFFER_HEIGHT - 2][i].read();
|
||||||
|
assert_eq!(char::from(screen_char.ascii_character), c);
|
||||||
|
}
|
||||||
|
|
||||||
|
serial_println!("[ok]");
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
#![no_std]
|
||||||
|
#![no_main]
|
||||||
|
#![feature(custom_test_frameworks)]
|
||||||
|
#![test_runner(xe_os::test_runner)]
|
||||||
|
#![reexport_test_harness_main = "test_main"]
|
||||||
|
|
||||||
|
use core::panic::PanicInfo;
|
||||||
|
use xe_os::{println, serial_print, serial_println};
|
||||||
|
|
||||||
|
#[no_mangle] // don't mangle the name of this function
|
||||||
|
pub extern "C" fn _start() -> ! {
|
||||||
|
test_main();
|
||||||
|
|
||||||
|
loop {}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[panic_handler]
|
||||||
|
fn panic(info: &PanicInfo) -> ! {
|
||||||
|
xe_os::test_panic_handler(info);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test_case]
|
||||||
|
fn test_println() {
|
||||||
|
serial_print!("test_println... ");
|
||||||
|
println!("test_println output");
|
||||||
|
serial_println!("[ok]");
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
#![no_std]
|
||||||
|
#![no_main]
|
||||||
|
|
||||||
|
use core::panic::PanicInfo;
|
||||||
|
use xe_os::{exit_qemu, serial_print, serial_println, QemuExitCode};
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn _start() -> ! {
|
||||||
|
should_fail();
|
||||||
|
serial_println!("[test did not panic]");
|
||||||
|
exit_qemu(QemuExitCode::Failed);
|
||||||
|
loop{}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn should_fail() {
|
||||||
|
serial_print!("should_fail... ");
|
||||||
|
assert_eq!(0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[panic_handler]
|
||||||
|
fn panic(_info: &PanicInfo) -> ! {
|
||||||
|
serial_println!("[ok]");
|
||||||
|
exit_qemu(QemuExitCode::Success);
|
||||||
|
loop {}
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
#![no_std]
|
||||||
|
#![no_main]
|
||||||
|
#![feature(abi_x86_interrupt)]
|
||||||
|
|
||||||
|
use core::panic::PanicInfo;
|
||||||
|
use xe_os::serial_print;
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn _start() -> ! {
|
||||||
|
serial_print!("stack_overflow... ");
|
||||||
|
|
||||||
|
xe_os::gdt::init();
|
||||||
|
init_test_idt();
|
||||||
|
|
||||||
|
stack_overflow();
|
||||||
|
|
||||||
|
panic!("Execution continued after stack overflow");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unconditional_recursion)]
|
||||||
|
fn stack_overflow() {
|
||||||
|
stack_overflow();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[panic_handler]
|
||||||
|
fn panic(info: &PanicInfo) -> ! {
|
||||||
|
xe_os::test_panic_handler(info)
|
||||||
|
}
|
||||||
|
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use x86_64::structures::idt::InterruptDescriptorTable;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref TEST_IDT: InterruptDescriptorTable = {
|
||||||
|
let mut idt = InterruptDescriptorTable::new();
|
||||||
|
unsafe {
|
||||||
|
idt.double_fault
|
||||||
|
.set_handler_fn(test_double_fault_handler)
|
||||||
|
.set_stack_index(xe_os::gdt::DOUBLE_FAULT_IST_INDEX);
|
||||||
|
}
|
||||||
|
|
||||||
|
idt
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
use xe_os::{exit_qemu, QemuExitCode, serial_println};
|
||||||
|
use x86_64::structures::idt::InterruptStackFrame;
|
||||||
|
|
||||||
|
extern "x86-interrupt" fn test_double_fault_handler(
|
||||||
|
_stack_frame: &mut InterruptStackFrame,
|
||||||
|
_error_code: u64,
|
||||||
|
) {
|
||||||
|
serial_println!("[ok]");
|
||||||
|
exit_qemu(QemuExitCode::Success);
|
||||||
|
loop {}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init_test_idt() {
|
||||||
|
TEST_IDT.load();
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"llvm-target": "x86_64-unknown-none",
|
||||||
|
"data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
|
||||||
|
"arch": "x86_64",
|
||||||
|
"target-endian": "little",
|
||||||
|
"target-pointer-width": "64",
|
||||||
|
"target-c-int-width": "32",
|
||||||
|
"os": "none",
|
||||||
|
"executables": true,
|
||||||
|
"linker-flavor": "ld.lld",
|
||||||
|
"linker": "rust-lld",
|
||||||
|
"panic-strategy": "abort",
|
||||||
|
"disable-redzone": true,
|
||||||
|
"features": "-mmx,-sse,+soft-float"
|
||||||
|
}
|
Loading…
Reference in New Issue