From 028d31cc5bd01d0006f80da5eaaf189b495604a6 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 22 Apr 2019 22:03:53 +0200 Subject: [PATCH] Make exit_qemu safe --- .../second-edition/posts/04-testing/index.md | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/blog/content/second-edition/posts/04-testing/index.md b/blog/content/second-edition/posts/04-testing/index.md index 80d9ff43..3d5eb457 100644 --- a/blog/content/second-edition/posts/04-testing/index.md +++ b/blog/content/second-edition/posts/04-testing/index.md @@ -185,15 +185,17 @@ pub enum QemuExitCode { Failed = 0x11, } -pub unsafe fn exit_qemu(exit_code: QemuExitCode) { +pub fn exit_qemu(exit_code: QemuExitCode) { use x86_64::instructions::port::Port; - let mut port = Port::new(0xf4); - port.write(exit_code as u32); + unsafe { + let mut port = Port::new(0xf4); + port.write(exit_code as u32); + } } ``` -We mark the function as `unsafe` because it relies on the fact that a special QEMU device is attached to the I/O port with address `0xf4`. The function creates a new [`Port`] at `0xf4`, which is the `iobase` of the `isa-debug-exit` device. Then it writes the the passed exit code to the port. We use `u32` because we specified the `iosize` of the `isa-debug-exit` device as 4 bytes. +The function creates a new [`Port`] at `0xf4`, which is the `iobase` of the `isa-debug-exit` device. Then it writes the the passed exit code to the port. We use `u32` because we specified the `iosize` of the `isa-debug-exit` device as 4 bytes. Both operations are unsafe, because writing to an I/O port can generally result in arbitrary behavior. For specifying the exit status, we create a `QemuExitCode` enum. The idea is to exit with the success exit code if all tests succeeded and with the failure exit code otherwise. The enum is marked as `#[repr(u32)]` to represent each variant by an `u32` integer. We use exit code `0x10` for success and `0x11` for failure. The actual exit codes do not matter much, as long as they don't clash with the default exit codes of QEMU. For example, using exit code `0` for success is not a good idea because it becomes `(0 << 1) | 1 = 1` after the transformation, which is the default exit code when QEMU failed to run. So we could not differentiate a QEMU error from a successfull test run. @@ -206,7 +208,7 @@ fn test_runner(tests: &[&dyn Fn()]) { test(); } /// new - unsafe { exit_qemu(QemuExitCode::Success) }; + exit_qemu(QemuExitCode::Success); } ``` @@ -294,7 +296,7 @@ lazy_static! { Like with the [VGA text buffer][vga lazy-static], we use `lazy_static` and a spinlock to create a `static` writer instance. By using `lazy_static` we can ensure that the `init` method is called exactly once on its first use. -Like the `isa-debug-exit` device, the UART is programmed using port I/O. Since the UART is more complex, it uses multiple I/O ports for programming different device registers. The `SerialPort::new` function expects the address of the first I/O port of the UART as argument, from which it can calculate the addresses of all needed ports. We're passing the port address `0x3F8`, which is the standard port number for the first serial interface. +Like the `isa-debug-exit` device, the UART is programmed using port I/O. Since the UART is more complex, it uses multiple I/O ports for programming different device registers. The unsafe `SerialPort::new` function expects the address of the first I/O port of the UART as argument, from which it can calculate the addresses of all needed ports. We're passing the port address `0x3F8`, which is the standard port number for the first serial interface. [vga lazy-static]: ./second-edition/posts/03-vga-text-buffer/index.md#lazy-statics @@ -408,7 +410,7 @@ fn panic(info: &PanicInfo) -> ! { fn panic(info: &PanicInfo) -> ! { serial_println!("[failed]\n"); serial_println!("Error: {}\n", info); - unsafe { exit_qemu(QemuExitCode::Failed); } + exit_qemu(QemuExitCode::Failed); loop {} } ``` @@ -594,15 +596,13 @@ pub fn test_runner(tests: &[&dyn Fn()]) { for test in tests { test(); } - unsafe { exit_qemu(QemuExitCode::Success) }; + exit_qemu(QemuExitCode::Success); } pub fn test_panic_handler(info: &PanicInfo) -> ! { serial_println!("[failed]\n"); serial_println!("Error: {}\n", info); - unsafe { - exit_qemu(QemuExitCode::Failed); - } + exit_qemu(QemuExitCode::Failed); loop {} } @@ -639,11 +639,13 @@ pub enum QemuExitCode { Failed = 0x11, } -pub unsafe fn exit_qemu(exit_code: QemuExitCode) { +pub fn exit_qemu(exit_code: QemuExitCode) { use x86_64::instructions::port::Port; - let mut port = Port::new(0xf4); - port.write(exit_code as u32); + unsafe { + let mut port = Port::new(0xf4); + port.write(exit_code as u32); + } } ```