Today I read another post by phil-opp about implementing VGA Text Mode. Thanks to the previous post, I created bare-metal Rust binary which can run on QEMU, and prints text via VGA. In this post I learned more about VGA and implemented safe interface and a bunch of macros which allow to print the text via VGA.

Vga

First, VGA is accessible via memory-mapped I/O. It means that we can use memory addresses, but the bytes are not written into RAM, but rather directly to the VGA hardware.

VGA on its own is pretty simple in terms of functionalities. We can write CP-437 set of characters to the buffer, control the foreground and background color and turn blinking of the character on.

Useful crates

In order to deliver safe interface for VGA Text Mode, global mutable static was used. Because mutating global variable is unsafe in Rust, we can't really change it without using unsafe blocks. The reason this is unsafe is that if multiple threads will start modifying the global, then we'll end up with race condition which Rust tries to prevent. The simplest solution to get rid of unsafe is to use Mutex. The problem is that I'm implementing OS from scratch and at this point it doesn't even have the concept of threads so the standard Rust's Mutex won't work. There is, however, a crate called spin which allows using spinlock technique to create Mutex. Spinlock simply uses busy waiting to repeatedly ask if it can already use the mutex.

Another useful library I learned about today is volatile. This crate allows wrapping almost any type with Volatile type. This using methods like write or read. What's that for? In OS development scenario, when I'm writing to memory-mapped I/O like VGA, the compiler has no idea about VGA and it's consequences. From the compiler point of view, I'm just writing to some memory and I never read values back. It may happen that compiler will catch that and will optimize this writes (why do we need writes for if we don't read values back?). To instruct the compiler that this writes are actually important, we can use Volatile which basically tells the compiler that the values in the memory may change in the meantime between two accesses, even if that's not visible in the code — so simply don't do the optimizations here. This technique is most often used during accessing memory-mapped I/O and in multithreading environment.

There is more I learned today by reading this blog post, but putting all this info in TIL is too much. I'll end up writing blog post instead. I encourage you to read phil-opp blog on your own. It's great source of information to the point I supported Phil via GitHub sponsors. The content he creates is worth a lot more than I'm able to give, but it's something :).