rust-kernel
Rustbelt: a hobby x86_64 kernel written in Rust
| author | Tyler Fowler <tyler@localhost> | 2026-10-29 23:32:33 -0700 |
|---|---|---|
| commit | 9ebb4aa97444ff9dd043ab0c20797578cb73eee1 | |
| tree | browse files at this commit | |
Minimal Rust x86_64 kernel booting in QEMU (VGA + serial)
| A | .cargo/config.toml | +11 -0 |
| A | .gitignore | +2 -0 |
| A | Cargo.lock | +128 -0 |
| A | Cargo.toml | +36 -0 |
| A | README.md | +116 -0 |
| A | boot.png | +0 -0 |
| A | rust-toolchain.toml | +4 -0 |
| A | scripts/run-headless.sh | +25 -0 |
| A | scripts/screenshot.sh | +25 -0 |
| A | src/main.rs | +71 -0 |
| A | src/serial.rs | +36 -0 |
| A | src/vga_buffer.rs | +142 -0 |
| A | x86_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.++++## 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"+}