x86_64 Poweroff
This driver works only on x86_64.
We will build a driver that powers off QEMU.
Power Off
QEMU provides a virtual peripheral called ISA Debug Exit that maps an IO port. Writing a numeric value to this port will power off QEMU and return the value as an error code.
Attaching this device to QEMU is done by adding -device isa-debug-exit,iobase=0x10f4,iosize=0x04 to the command line.
In this example, the debug device is mapped on port 0x10f4 and is 4 bytes long.
Simple Power Off Driver
The simplest driver that we can write is one that power off QEMU when it is loaded. We need to:
- reserve the port
- write to it using x86 assembly
Reserving the port region
The Kernel Rust API does not have (yet) and safe abstraction to reserve a port. This means that we have to use the C binding directly,
unsafe { bindings::request_region(start, len, name.as_char_ptr()) }
startis the port numberlenis the gthe length of the portnameis a name that will be displayed in/proc/ioportsand is a C stringchar*
The function return NULL if the port mapping fails. In Rust, we can use the .is_null() function
to check if the port reservation worked.
Writing to the port
Writing to the IO port is done using inline assembly. There are two possible syntaxes:
Kernel AT&T Syntax
// kernel API
use kernel::asm;
unsafe {
asm!("
outb %al, %dx
";
in("dx") port as u16,
in("al") value as u8,
options(noreturn)
);
}
Intel Syntax
// core API
use core::arch::asm;
unsafe {
asm!("
out dx, al
",
in("dx") port as u16,
in("al") value as u8,
options(intel_syntax, noreturn)
);
}
The kernel does not forbid writing to a port without reserving it, but this is bad practice,
as we might override another driver's ports. if we cannot reserve a port, we just back off
and fail to load the driver returning a EBUSY error.
Releasing the port region
If we have reserved a port region, we must make sure to release it when we do not need it anymore or when we unload the driver.
unsafe { bindings::release_region(start, len); }
Exercises
- Write an empty module called
PowerOff - Reserve the
0x10f4port with 4 bytes long in theModule::initfunction.
- Print the contents of
/proc/ioportsto see if your port has been reserved - Try reserving a port that already exists and fail to initialize the module
- Release the
0x10f4port with 4 bytes long in theDrop::dropfunction.