Creating the Driver
Creating a kernel-mode driver in Ethereal is extremely easy.
Only C is supported for driver development at the moment.
All drivers for Ethereal are placed under the drivers
directory. Subdirectories are supported if they have a make.config file in them.
Driver Structure
A generic driver has the following folder structure:
driver/
├─ driver.conf
├─ main.c
├─ Makefile
driver.conf
: Contains configuration information about the drivermain.c
: Contains the driver's code (you can have multiple C files)Makefile
: You do not make this - copy it from a nearby driver (the same Makefile will work for any driver)
Creating your first driver
Let's call our driver the example driver, and have its folder be called example
.
We will place its files under drivers/example/
- you can view the reference implementation of this driver in the kernel at the main build tree
First, the config file.
FILENAME = "example_driver.sys"
ENVIRONMENT = ANY
PRIORITY = IGNORE
ARCH = ANY
FILENAME
: This dictates the filename of your driver to place in/boot/drivers/
ENVIRONMENT
: Mainly legacy code at this point.ENVIRONMENT
was a planned expansion but was never finished.PRIORITY
: Options includeIGNORE
(if the driver fails to load it will be ignored),WARN
(warn the user if the driver fails to load), andCRITICAL
(crash on load failure)ARCH
: Supported architectures for your driver. You can separate architectures byOR
, soX86_64 OR I386
is perfectly valid.
Awesome, now copy the Makefile from any other driver into drivers/example/Makefile
and the driver structure is finished.
The actual C code
Let's make a hello world driver!
/**
* @brief Example driver
*/
#include <kernel/loader/driver.h>
#include <kernel/debug.h>
/**
* @brief The main driver init method
* @param argc The argument count
* @param argv The argument list
*
* @note @c argc and @c argv only apply when loaded from userspace with @c driverctl
* Support for these two are very limited!
*/
int driver_init(int argc, char *argv[]) {
dprintf(DEBUG, "Hello, driver world!\n");
return DRIVER_STATUS_SUCCESS;
}
/**
* @brief The main driver deinit method
* Called on driver shutdown
*/
int driver_deinit() {
return DRIVER_STATUS_SUCCESS;
}
struct driver_metadata driver_metadata = {
.name = "Example Driver",
.author = "Your Name Here",
.init = driver_init,
.deinit = driver_deinit,
};
Breakdown of the C code
Let's break some of this down.
driver_init
This is the main method of the driver. It doesn't necessarily have to be called driver_init
, just has to match what's in the metadata.
It is given a list of arguments. This list is pretty unimportant, as it only applies with drivers loaded by userspace (so for debugging only) - it's just a standard argc/argv like in any C program.
The return value, DRIVER_STATUS_SUCCESS
, currently has four possible values:
DRIVER_STATUS_SUCCESS
: No error. Your driver is left loaded in memory.DRIVER_STATUS_UNSUPPORTED
: Unsupported components detected. This is an error.DRIVER_STATUS_NO_DEVICE
: Your driver loaded, but a device was not found. This is a noncritical error and your driver is unloaded from memory.DRIVER_STATUS_ERROR
: Generic driver error.
dprintf
This is the kernel's debug print macro. DEBUG
is the log level, and after the status it acts as a normal printf.
The recommended function for logging in drivers is dprintf_module
, which has the following signature:
dprintf_module(DEBUG, "DRIVER:EXAMPLE", "Hello, world!\n");
A common practice in Ethereal is to define a LOG
macro like so:
#define LOG(status, ...) dprintf_module(status, "DRIVER:EXAMPLE", __VA_ARGS__)
LOG(DEBUG, "Hello!\n");
DEBUG
, ERR
, INFO
, WARN
, and NOHEADER
are all available log levels.
driver_deinit
Called whenever the system begins to power down and is unloading you. Cleanup and free your resources!
The return value of this isn't checked
driver_metadata
The name of this MUST be driver_metadata
The kernel uses this to get more metadata on your driver. It has the driver's name, its author, init method, and deinit method.
In the future, this structure will be upgraded. Details are not known right now, but backwards compatibility with other drivers will be maintained.
Testing your driver
Assuming you placed your driver in drivers/
in the Ethereal repo, you will be able to just rebuild Ethereal and it will automatically build your driver.
On running Ethereal you should see this in the serial output:
[1.000000] [CPU0] [INFO] [DRIVER] Loading driver "/device/initrd/boot/drivers/example_driver.sys" with priority IGNORE...
[1.000001] [CPU0] [DBG ] Hello, driver world!
Congrats! You have designed your first driver.
Continue onto scanning the PCI bus.