RustForge

rust-kernel

Rustbelt: a hobby x86_64 kernel written in Rust

authorTyler Fowler <tyler@localhost>2026-10-29 23:32:33 -0700
commit9ebb4aa97444ff9dd043ab0c20797578cb73eee1
treebrowse files at this commit
Minimal Rust x86_64 kernel booting in QEMU (VGA + serial)
A.cargo/config.toml+11 -0
A.gitignore+2 -0
ACargo.lock+128 -0
ACargo.toml+36 -0
AREADME.md+116 -0
Aboot.png+0 -0
Arust-toolchain.toml+4 -0
Ascripts/run-headless.sh+25 -0
Ascripts/screenshot.sh+25 -0
Asrc/main.rs+71 -0
Asrc/serial.rs+36 -0
Asrc/vga_buffer.rs+142 -0
Ax86_64-rust_kernel.json+16 -0

13 files changed, 612 insertions, 0 deletions

@@ -0,0 +1,11 @@+[unstable]+json-target-spec = true                       # allow our custom x86_64-rust_kernel.json+build-std = ["core", "compiler_builtins"]     # compile `core` for that target ourselves+build-std-features = ["compiler-builtins-mem"] # provide memcpy/memset/etc.++[build]+target = "x86_64-rust_kernel.json"++# `cargo run` -> build a bootable image and boot it in QEMU.+[target.'cfg(target_os = "none")']+runner = "bootimage runner"
@@ -0,0 +1,2 @@+target/+*.ppm
@@ -0,0 +1,128 @@+# This file is automatically @generated by Cargo.+# It is not intended for manual editing.+version = 4++[[package]]+name = "bit_field"+version = "0.10.3"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6"++[[package]]+name = "bitflags"+version = "1.3.2"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"++[[package]]+name = "bitflags"+version = "2.13.2"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06"++[[package]]+name = "bootloader"+version = "0.9.35"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "e22d5a0b9e11dd5bee9d24a68885de6dc7ed367f897323b1c1286150fd469374"++[[package]]+name = "const_fn"+version = "0.4.12"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "413d67b29ef1021b4d60f4aa1e925ca031751e213832b4b1d588fae623c05c60"++[[package]]+name = "lazy_static"+version = "1.5.1"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "20870f649af7073d53e38067b2a84312175d56ea15217e1b15bc83506ec50afb"+dependencies = [+ "spin 0.9.9",+]++[[package]]+name = "raw-cpuid"+version = "10.7.0"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "6c297679cb867470fa8c9f67dbba74a78d78e3e98d7cf2b08d6d71540f797332"+dependencies = [+ "bitflags 1.3.2",+]++[[package]]+name = "rust_kernel"+version = "0.1.0"+dependencies = [+ "bootloader",+ "lazy_static",+ "spin 0.5.2",+ "uart_16550",+ "volatile 0.2.7",+ "x86_64",+]++[[package]]+name = "rustversion"+version = "1.0.23"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"++[[package]]+name = "spin"+version = "0.5.2"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"++[[package]]+name = "spin"+version = "0.9.9"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e"++[[package]]+name = "uart_16550"+version = "0.3.2"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "e492212ac378a5e00da953718dafb1340d9fbaf4f27d6f3c5cab03d931d1c049"+dependencies = [+ "bitflags 2.13.2",+ "rustversion",+ "x86",+]++[[package]]+name = "volatile"+version = "0.2.7"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "f6b06ad3ed06fef1713569d547cdbdb439eafed76341820fb0e0344f29a41945"++[[package]]+name = "volatile"+version = "0.4.6"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "442887c63f2c839b346c192d047a7c87e73d0689c9157b00b53dcc27dd5ea793"++[[package]]+name = "x86"+version = "0.52.0"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "2781db97787217ad2a2845c396a5efe286f87467a5810836db6d74926e94a385"+dependencies = [+ "bit_field",+ "bitflags 1.3.2",+ "raw-cpuid",+]++[[package]]+name = "x86_64"+version = "0.15.5"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "be4ec631e1a81d50e46c35a4a00322bd076c47491b64c2fb10a7ffa89002c697"+dependencies = [+ "bit_field",+ "bitflags 2.13.2",+ "const_fn",+ "rustversion",+ "volatile 0.4.6",+]
@@ -0,0 +1,36 @@+[package]+name = "rust_kernel"+version = "0.1.0"+edition = "2024"++[dependencies]+# Bootloader 0.9.x: the version used by the 2nd edition of "Writing an OS in Rust".+# `cargo bootimage` links it with our kernel into a bootable BIOS disk image.+bootloader = "0.9"+volatile = "0.2.6"      # stops the compiler optimising away VGA buffer writes+spin = "0.5.2"          # spinlock Mutex (no OS = no std::sync::Mutex)+x86_64 = "0.15"         # safe wrappers for x86 instructions/ports (hlt, outb, ...)+uart_16550 = "0.3"      # driver for the serial port (COM1)++[dependencies.lazy_static]+version = "1.0"+features = ["spin_no_std"]++[features]+# Build with `--features headless` to make the kernel shut QEMU down after+# printing its greeting (used by scripts/run-headless.sh). Without it the+# kernel just halts forever so you can look at the QEMU window.+headless = []++# No unwinding in a kernel: just abort on panic.+[profile.dev]+panic = "abort"+[profile.release]+panic = "abort"++# Extra QEMU arguments used by `cargo run` (via `bootimage runner`).+[package.metadata.bootimage]+run-args = [+    "-device", "isa-debug-exit,iobase=0xf4,iosize=0x04",  # lets the kernel exit QEMU+    "-serial", "stdio",                                    # serial port -> your terminal+]
@@ -0,0 +1,116 @@+# rust-kernel++A tiny hobby x86_64 kernel written in Rust, following the 2nd edition of+Philipp Oppermann's [Writing an OS in Rust](https://os.phil-opp.com/).+Right now it boots in QEMU, prints a greeting on the VGA text screen and on+the serial port (COM1), and then halts.++![boot screenshot](boot.png)++## What's installed (on this box)++| Tool | Version | Why |+|---|---|---|+| rustup | 1.29.2 | toolchain manager (in `~/.cargo/bin`, ahead of Debian's `/usr/bin/cargo`) |+| Rust nightly | rustc 1.101.0-nightly (2026-10-29) | custom JSON targets + `build-std` are nightly-only |+| `rust-src` component | – | source of `core`, recompiled for our bare-metal target |+| `llvm-tools-preview` component | – | `llvm-objcopy` etc., used by `bootimage` |+| `bootimage` (cargo subcommand) | 0.10.5 | links kernel + bootloader into a bootable disk image |+| QEMU (`qemu-system-x86`) | 10.0.13 | the emulator we boot in |++`rust-toolchain.toml` pins the project to nightly with the two components, so+`cargo` inside this directory picks them up automatically.++Crates: `bootloader` 0.9.35 (BIOS bootloader used by the 2nd edition),+`volatile` 0.2, `spin` 0.5, `x86_64` 0.15, `uart_16550` 0.3, `lazy_static` 1.5.++Fresh setup on another machine:++```sh+curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh   # rustup+rustup toolchain install nightly -c rust-src -c llvm-tools-preview+cargo install bootimage+sudo apt install qemu-system-x86+```++## Build and run++```sh+cargo bootimage                     # build -> target/x86_64-rust_kernel/debug/bootimage-rust_kernel.bin+cargo run                           # build + boot in a QEMU window; serial output also prints in the terminal+                                    # (kernel halts forever, close the window or press Ctrl+C to quit)+```++Headless (no window, good for SSH / scripts):++```sh+./scripts/run-headless.sh           # builds with --features headless, boots with -display none,+                                    # kernel exits QEMU via isa-debug-exit; script exits 0 on success+cargo run --features headless -- -display none   # same thing via cargo+                                    # (QEMU's exit status is 33 = success; cargo just reports it as non-zero)+cargo run -- -display none          # headless but kernel keeps running; Ctrl+C to stop+./scripts/screenshot.sh [out.png]   # boots, saves the VGA screen to boot.png via QEMU monitor `screendump`+```++Expected serial output:++```+Hello from Tyler's Rust kernel! (via serial COM1)+Kernel booted successfully.+```++Extra args after `--` in `cargo run -- ...` are appended to the QEMU command+line, e.g. `cargo run -- -d int -no-reboot` to debug triple faults, or+`cargo run -- -s -S` to wait for `gdb` on port 1234.++## Project layout++```+.cargo/config.toml       build-std + custom target + "bootimage runner" as the cargo runner+rust-toolchain.toml      pin nightly + rust-src + llvm-tools-preview+x86_64-rust_kernel.json  custom target: no OS, no red zone, soft-float, abort on panic, rust-lld+Cargo.toml               deps, `headless` feature, QEMU run-args for `cargo run`+src/main.rs              entry point `_start`, panic handler, QEMU exit helper+src/vga_buffer.rs        VGA text-mode writer + print!/println! macros+src/serial.rs            COM1 UART writer + serial_print!/serial_println! macros+scripts/run-headless.sh  headless boot with timeout + exit-code check+scripts/screenshot.sh    headless boot + PNG screenshot via QEMU monitor+boot.png                 screenshot of the VGA screen+```++## Roadmap++Already done (roughly chapters 1–3, plus the serial/QEMU-exit bits of chapter 4):++- [x] Freestanding binary — [phil-opp](https://os.phil-opp.com/freestanding-rust-binary/)+- [x] Minimal kernel booting in QEMU — [phil-opp](https://os.phil-opp.com/minimal-rust-kernel/), [OSDev: Bare Bones](https://wiki.osdev.org/Bare_Bones)+- [x] VGA text mode printing — [phil-opp](https://os.phil-opp.com/vga-text-mode/), [OSDev: Printing to Screen](https://wiki.osdev.org/Printing_To_Screen)+- [x] Serial output + QEMU exit — [phil-opp: Testing](https://os.phil-opp.com/testing/), [OSDev: Serial Ports](https://wiki.osdev.org/Serial_Ports)++Next stages:++1. **Testing** — custom test framework (`cargo test` in QEMU). [phil-opp: Testing](https://os.phil-opp.com/testing/)+2. **CPU exceptions / IDT** — breakpoint & page fault handlers. [phil-opp: CPU Exceptions](https://os.phil-opp.com/cpu-exceptions/), [OSDev: IDT](https://wiki.osdev.org/Interrupt_Descriptor_Table), [OSDev: Exceptions](https://wiki.osdev.org/Exceptions)+3. **GDT / TSS / double faults** — separate stack so stack overflows don't triple-fault. [phil-opp: Double Faults](https://os.phil-opp.com/double-fault-exceptions/), [OSDev: GDT](https://wiki.osdev.org/Global_Descriptor_Table), [OSDev: TSS](https://wiki.osdev.org/Task_State_Segment)+4. **Hardware interrupts** — 8259 PIC, timer, PS/2 keyboard. [phil-opp: Hardware Interrupts](https://os.phil-opp.com/hardware-interrupts/), [OSDev: 8259 PIC](https://wiki.osdev.org/8259_PIC), [OSDev: PIT](https://wiki.osdev.org/Programmable_Interval_Timer), [OSDev: PS/2 Keyboard](https://wiki.osdev.org/PS/2_Keyboard) (later: [APIC](https://wiki.osdev.org/APIC))+5. **Paging** — 4-level page tables, mapping frames. [phil-opp: Introduction to Paging](https://os.phil-opp.com/paging-introduction/), [phil-opp: Paging Implementation](https://os.phil-opp.com/paging-implementation/), [OSDev: Paging](https://wiki.osdev.org/Paging)+6. **Heap allocation** — `alloc` crate, `Box`/`Vec`, allocator designs. [phil-opp: Heap Allocation](https://os.phil-opp.com/heap-allocation/), [phil-opp: Allocator Designs](https://os.phil-opp.com/allocator-designs/), [OSDev: Memory Allocation](https://wiki.osdev.org/Memory_Allocation)+7. **Multitasking** — cooperative async/await executor first, then preemptive threads. [phil-opp: Async/Await](https://os.phil-opp.com/async-await/), [OSDev: Multitasking Systems](https://wiki.osdev.org/Multitasking_Systems), [OSDev: Brendan's Multi-tasking Tutorial](https://wiki.osdev.org/Brendan%27s_Multi-tasking_Tutorial)+8. **User mode & syscalls** — ring 3, `syscall`/`sysret`, loading ELF programs. (Beyond the phil-opp series.) [OSDev: Getting to Ring 3](https://wiki.osdev.org/Getting_to_Ring_3), [OSDev: System Calls](https://wiki.osdev.org/System_Calls), [OSDev: SYSENTER/SYSCALL](https://wiki.osdev.org/SYSENTER), [OSDev: ELF](https://wiki.osdev.org/ELF)+9. **Filesystem & shell** — VFS, a RAM disk / FAT, a tiny shell reading the keyboard. [OSDev: File Systems](https://wiki.osdev.org/File_Systems), [OSDev: VFS](https://wiki.osdev.org/VFS), [OSDev: FAT](https://wiki.osdev.org/FAT), [OSDev: Initrd](https://wiki.osdev.org/Initrd)++Other good references: [OSDev wiki main page](https://wiki.osdev.org/Expanded_Main_Page),+[Intel SDM](https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdms.html),+the [blog_os repo](https://github.com/phil-opp/blog_os) (one branch per post, e.g. `post-04`),+and for a UEFI/framebuffer setup later, the newer [`bootloader` 0.11](https://github.com/rust-osdev/bootloader) crate.++## Notes / gotchas++- The target JSON changes occasionally with new nightlies (e.g. `target-pointer-width`+  became an integer, `rustc-abi` was added). If a nightly update breaks the build,+  compare with the latest `x86_64-blog_os.json` in the blog_os repo, or pin a date in+  `rust-toolchain.toml` (`channel = "nightly-2026-10-29"`).+- The "packages contain code that will be rejected by a future version of Rust: core"+  warning comes from building `core` with build-std and is harmless.+- `bootimage` prints "`CARGO_MANIFEST_DIR` env variable not set" when run outside+  `cargo run`; also harmless.

Binary file changed.

@@ -0,0 +1,4 @@+# Kernel dev needs nightly features (custom JSON target, build-std).+[toolchain]+channel = "nightly"+components = ["rust-src", "llvm-tools-preview"]
@@ -0,0 +1,25 @@+#!/usr/bin/env bash+# Build the kernel with the `headless` feature and boot it in QEMU with no+# window. Serial output goes to this terminal; the kernel powers QEMU off via+# the isa-debug-exit device when done. A timeout guards against hangs.+set -euo pipefail+cd "$(dirname "$0")/.."++cargo bootimage --features headless+IMG=target/x86_64-rust_kernel/debug/bootimage-rust_kernel.bin++set +e+timeout 30 qemu-system-x86_64 \+    -drive format=raw,file="$IMG" \+    -device isa-debug-exit,iobase=0xf4,iosize=0x04 \+    -serial stdio -display none -no-reboot+code=$?+set -e++# isa-debug-exit: QEMU exit status = (value << 1) | 1, so Success(0x10) -> 33.+case $code in+    33)  echo "[run-headless] kernel exited OK"; exit 0 ;;+    35)  echo "[run-headless] kernel reported FAILURE"; exit 1 ;;+    124) echo "[run-headless] timed out (kernel hung?)"; exit 124 ;;+    *)   echo "[run-headless] QEMU exited with $code"; exit "$code" ;;+esac
@@ -0,0 +1,25 @@+#!/usr/bin/env bash+# Boot the (non-headless) kernel with no window, wait a moment, then ask the+# QEMU monitor to dump the VGA screen to boot.png. Handy on a headless box.+set -euo pipefail+cd "$(dirname "$0")/.."++cargo bootimage+IMG=target/x86_64-rust_kernel/debug/bootimage-rust_kernel.bin+OUT="${1:-$PWD/boot.png}"+SOCK=$(mktemp -u /tmp/qemu-mon.XXXXXX)++qemu-system-x86_64 -drive format=raw,file="$IMG" \+    -serial stdio -display none -monitor unix:"$SOCK",server,nowait &+QPID=$!+sleep 3   # give the BIOS + bootloader + kernel time to draw++# Talk to the QEMU monitor over its UNIX socket (python avoids needing socat).+python3 - "$SOCK" "$OUT" <<'PY'+import socket, sys, time+s = socket.socket(socket.AF_UNIX); s.connect(sys.argv[1])+s.sendall(f"screendump {sys.argv[2]} -f png\n".encode()); time.sleep(1)+s.sendall(b"quit\n"); time.sleep(0.5)+PY+wait $QPID || true+echo "[screenshot] saved $OUT"
@@ -0,0 +1,71 @@+//! A minimal freestanding x86_64 kernel.+//!+//! Boot flow: BIOS -> `bootloader` crate (switches to 64-bit long mode, sets up+//! paging) -> `_start` below. There is no OS underneath us, so no `std`, no+//! `main`, and no heap. Everything we print goes straight to hardware.++#![no_std] // don't link the Rust standard library (it needs an OS)+#![no_main] // no normal `main`: the bootloader jumps to `_start`++mod serial; // COM1 serial port -> shows up in your terminal via `-serial stdio`+mod vga_buffer; // 80x25 VGA text mode screen at physical address 0xb8000++// The bootloader is linked in by `cargo bootimage`, not called from our code;+// this line just tells Rust/cargo the dependency is intentional.+extern crate bootloader;++use core::panic::PanicInfo;++/// Kernel entry point. The bootloader calls this after setting up the CPU.+/// `no_mangle` keeps the symbol name `_start` so the linker can find it.+#[unsafe(no_mangle)]+pub extern "C" fn _start() -> ! {+    println!("Hello from Tyler's Rust kernel!");+    println!("VGA text mode is working. Next stop: interrupts.");++    serial_println!("Hello from Tyler's Rust kernel! (via serial COM1)");+    serial_println!("Kernel booted successfully.");++    // In headless mode, power QEMU off so scripts/CI finish on their own.+    #[cfg(feature = "headless")]+    exit_qemu(QemuExitCode::Success);++    hlt_loop();+}++/// Called on `panic!`. Print the message to both outputs, then stop.+#[panic_handler]+fn panic(info: &PanicInfo) -> ! {+    println!("{}", info);+    serial_println!("KERNEL PANIC: {}", info);+    #[cfg(feature = "headless")]+    exit_qemu(QemuExitCode::Failed);+    hlt_loop();+}++/// Sleep the CPU until the next interrupt, forever (cheaper than `loop {}`).+pub fn hlt_loop() -> ! {+    loop {+        x86_64::instructions::hlt();+    }+}++/// Exit codes for QEMU's `isa-debug-exit` device. QEMU exits with+/// status `(value << 1) | 1`, so Success = 0x10 -> shell exit code 33.+#[allow(dead_code)]+#[derive(Debug, Clone, Copy, PartialEq, Eq)]+#[repr(u32)]+pub enum QemuExitCode {+    Success = 0x10,+    Failed = 0x11,+}++/// Write to I/O port 0xf4 (where we put `isa-debug-exit`) to quit QEMU.+#[allow(dead_code)]+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,36 @@+//! Serial port (UART 16550, COM1 at I/O port 0x3F8).+//! QEMU forwards it to your terminal with `-serial stdio`, which makes it the+//! easiest way to see kernel output without a screen.++use lazy_static::lazy_static;+use spin::Mutex;+use uart_16550::SerialPort;++lazy_static! {+    /// The one global COM1 port, initialised on first use, behind a spinlock.+    pub static ref SERIAL1: Mutex<SerialPort> = {+        let mut port = unsafe { SerialPort::new(0x3F8) };+        port.init();+        Mutex::new(port)+    };+}++#[doc(hidden)]+pub fn _print(args: core::fmt::Arguments) {+    use core::fmt::Write;+    SERIAL1.lock().write_fmt(args).expect("printing to serial failed");+}++/// Like `print!`, but to the serial port.+#[macro_export]+macro_rules! serial_print {+    ($($arg:tt)*) => ($crate::serial::_print(format_args!($($arg)*)));+}++/// Like `println!`, but to the serial port. Uses "\n" (QEMU passes it through).+#[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,142 @@+//! VGA text mode driver.+//!+//! In text mode the screen is a 25x80 grid of 2-byte cells memory-mapped at+//! physical address 0xb8000: byte 0 = ASCII char, byte 1 = colour attribute+//! (low nibble foreground, high nibble background).+//! The bootloader identity-maps that address, so we can write to it directly.++use core::fmt;+use lazy_static::lazy_static;+use spin::Mutex;+use volatile::Volatile;++#[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,+}++/// Foreground + background packed into one attribute byte.+#[derive(Debug, Clone, Copy, PartialEq, Eq)]+#[repr(transparent)]+struct ColorCode(u8);++impl ColorCode {+    fn new(fg: Color, bg: Color) -> ColorCode {+        ColorCode((bg as u8) << 4 | (fg as u8))+    }+}++/// One on-screen cell. `repr(C)` guarantees the char-then-colour layout.+#[derive(Debug, Clone, Copy, PartialEq, Eq)]+#[repr(C)]+struct ScreenChar {+    ascii_character: u8,+    color_code: ColorCode,+}++const BUFFER_HEIGHT: usize = 25;+const BUFFER_WIDTH: usize = 80;++/// The whole screen. `Volatile` tells the compiler these writes have side+/// effects (they change what's on the monitor) so it must not drop them.+#[repr(transparent)]+struct Buffer {+    chars: [[Volatile<ScreenChar>; BUFFER_WIDTH]; BUFFER_HEIGHT],+}++/// Always writes on the bottom row and scrolls everything up on newline.+pub struct Writer {+    column_position: usize,+    color_code: ColorCode,+    buffer: &'static mut Buffer,+}++impl Writer {+    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;+                self.buffer.chars[row][col].write(ScreenChar {+                    ascii_character: byte,+                    color_code: self.color_code,+                });+                self.column_position += 1;+            }+        }+    }++    pub fn write_string(&mut self, s: &str) {+        for byte in s.bytes() {+            match byte {+                // printable ASCII or newline+                0x20..=0x7e | b'\n' => self.write_byte(byte),+                // VGA only knows code page 437; show anything else as a block+                _ => self.write_byte(0xfe),+            }+        }+    }++    fn new_line(&mut self) {+        for row in 1..BUFFER_HEIGHT {+            for col in 0..BUFFER_WIDTH {+                let c = self.buffer.chars[row][col].read();+                self.buffer.chars[row - 1][col].write(c);+            }+        }+        self.clear_row(BUFFER_HEIGHT - 1);+        self.column_position = 0;+    }++    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);+        }+    }+}++/// Lets us use Rust's formatting machinery (`write!`, `{}`) with the Writer.+impl fmt::Write for Writer {+    fn write_str(&mut self, s: &str) -> fmt::Result {+        self.write_string(s);+        Ok(())+    }+}++lazy_static! {+    /// Global writer. The raw pointer cast to 0xb8000 is the one `unsafe`+    /// spot: we promise that address really is the VGA buffer.+    pub static ref WRITER: Mutex<Writer> = Mutex::new(Writer {+        column_position: 0,+        color_code: ColorCode::new(Color::LightGreen, Color::Black),+        buffer: unsafe { &mut *(0xb8000 as *mut Buffer) },+    });+}++#[doc(hidden)]+pub fn _print(args: fmt::Arguments) {+    use core::fmt::Write;+    WRITER.lock().write_fmt(args).unwrap();+}++/// `print!` for the VGA screen.+#[macro_export]+macro_rules! print {+    ($($arg:tt)*) => ($crate::vga_buffer::_print(format_args!($($arg)*)));+}++/// `println!` for the VGA screen.+#[macro_export]+macro_rules! println {+    () => ($crate::print!("\n"));+    ($($arg:tt)*) => ($crate::print!("{}\n", format_args!($($arg)*)));+}
@@ -0,0 +1,16 @@+{+    "llvm-target": "x86_64-unknown-none",+    "data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-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",+    "rustc-abi": "softfloat"+}