Going Further

The last part of the exercise is to identify a limitation in the current prototypes of the emulated device and its driver, or to develop an entirely new functionality. This is really up to you, but you can find below a few suggestions:

ChatGPT, GitHub Copilot, and other generative AI tools. Using such tools for that exercise is encouraged. In fact, they were very helpful in preparing it!

Once you are done make sure to submit your exercise.

Building the Driver as a Proper Kernel Module

A kernel module is a piece of kernel code that is not automatically loaded at boot time, but that can rather be dynamically loaded and unloaded at runtime. In addition to that added flexibility, a module can also be compiled in a separate source folder outside of the kernel's source tree, which is practical for e.g. version control: you don't need to have an entire clone of the kernel's sources and your module can live in its own git repository. Compilation is also faster, as you don't have to link the module with the rest of the kernel.

You can find a good guide on how to write a "hello world" kernel module here. See if you can take inspiration from it and translate the driver into a kernel module.

Automatic Base Address Discovery

Currently, the base address of the device is hardcoded as a constant into the driver's code:

#define DEVICE_BASE_PHYS_ADDR 0xfebf1000

This is not good because the device's base address in physical memory can change when rebooting the machine (e.g. when some hardware is added/removed). A possible solution would be to compile the driver as a loadable kernel module and pass the base address (that can be at load time discovered with lspci -v) as a module parameter.

This solution is not great still because it forces the user to call lspci and fill in the parameter each time the driver is loaded. Another solution would be to have the driver enumerate PCI devices itself, and automatically find the base address, based on the vendor and device IDs we defined when implementing the virtual device in Qemu. You can see with lspci that for our virtual random number generator the vendor ID is 0x1234 and the device ID is 0xcafe.

Measuring and Improving the performance of the Device/Driver

Should you write a user land application trying to generate through the device as many random numbers as possible, you will find that the throughput is not very high. This is due to the latency of security domain crossings: going from user to kernel space and back when the application calls the driver in the guest is costly in terms of CPU cycles, so is going from the guest to the host and back when the virtual device is accessed by the driver.

To address that issue, a first solution would be to increase the size of the random numbers produced by the device. Switching from 32 to 64 bits should be relatively straightforward, and would hopefully increase the random data generation throughput.

Another possibility would be to consider generating a much larger amount of random data for each call to the device, and implement the transfer of that data to the driver via DMA. This is no easy task, but to achieve it you could take inspiration from the Qemu educational device presents in Qemu sources in hw/misc/edu.c

Implementing Other Random Number Generators

Within the implementation of the RNG virtual device, we currently use the C standard library functions rand() and srand() to generate random numbers and seed the generator. This is perfectly fine but, for the sake of the exercise, one could switch this implementation with something else: Rather than relying on the standard C library, you could implement manually a RNG algorithms. Some are very simple, and others are more complicated, all with their pros and cons. More information here.