Make exit_qemu safe

This commit is contained in:
Philipp Oppermann
2019-04-22 22:03:53 +02:00
parent 6257baaea5
commit 028d31cc5b

View File

@@ -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;
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);
}
loop {}
}
@@ -639,12 +639,14 @@ 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;
unsafe {
let mut port = Port::new(0xf4);
port.write(exit_code as u32);
}
}
```
Now executables and integration tests can import these functions from the library and don't need to define their own implementations. To also make `println` and `serial_println` available, we move the module declarations too: