Skip to main content

Creating the Driver

Creating a kernel-mode driver in Ethereal is extremely easy.

info

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 driver
  • main.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.

drivers/example/driver.conf
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 include IGNORE (if the driver fails to load it will be ignored), WARN (warn the user if the driver fails to load), and CRITICAL (crash on load failure)
  • ARCH: Supported architectures for your driver. You can separate architectures by OR, so X86_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!

drivers/example/main.c
/**
* @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.

tip

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");
info

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!

note

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.

note

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.