Dynamic Kernels: Discovery
The last issues Kernel Korner introduced problems related to loading and unloading a custom module, but didn't uncover the code to actually perform these tasks. This time, we are going to look at some finer details of module-writing, in order to begin showing the actual code for our character device driver.
Although a smart driver should be able to autodetect the hardware it looks for, autodetection is not always the most sensible implementation, because it may be tricky to design. It is wise to provide a way to specify as many details as possible at load time, in order to test your driver before scratching your head and deciding to implement autodetection. Moreover, autodetection may fail if “similar” hardware is installed in the computer. A minor project can simply avoid autodetection altogether.
To configure the driver at load time, we'll exploit insmod's capability to assign integer values to arbitrary variables in the driver. We'll thus declare a (public) integer variable for each configurable item, and we'll make sure that the default value will trigger autodetection.
Configuring multiple boards at load time is left as an exercise to the reader (after reading the manpage for insmod); this implementation allows specification of a single board: for the sake of simplicity additional boards are only reachable through autodetection.
The kernel is a complex application, and it is vital to keep its namespace as tidy as possible. This means both to use private symbols wherever it is possible, and to use a common prefix for all the symbols you define.
A production environment will only declare init_module() and cleanup_module(), which are used to load and unload the driver, and any load-time configuration variables. Nothing else needs to be public, because the module is accessed through pointers, not by name.
However, when you are developing and testing your code, you'll need your functions and data structures in the public symbol table in order to access them with your favorite debugging tool.
The easiest way to accomplish this dual need is to always use your own prefix in names, declare all of your symbols Static (note the capital `S'), and include the following five lines at the top of your driver:
#ifdef DEBUG_modulename # define Static /* nothing */ #else # define Static static #endif
Real static symbols (such as persistent local variables) may thus be declared static, while debuggable symbols are declared Static
In this page, the whole code for the initialization function is uncovered. This is skeletal code, as the skel name suggests: a real-world device usually has slightly more than two I/O ports.
The most important thing to remember here is to release all the resources you already asked for whenever you find an error condition. This kind of task is well handled by the (otherwise unloved) goto statement: code duplication is avoided by jumping to the resource-release part of the function in case of error.
The fragment of code shown accepts load-time configuration for the major number, for the base address of the board's I/O ports, and for the IRQ number. For each “possible” board (in the I/O space), the autodetection function is called. If no boards are detected, init_module() returns -ENODEV to tell insmod that no devices are there.
Sometimes it is wise to allow the driver to be loaded even if its hardware is not installed in the computer. I implement such code in order to develop most of my driver at home. The trick is to have a configuration variable (skel_fake) which allows you to fake a nonexistent board. You can look at the implementation in my own drivers. “Faking boards” is a powerful way to start writing code before you get the hardware, or to test support for two boards even if you only own one of them.
The role of cleanup_module() is to shut down the device and release any resources allocated by init_module(). Our sample code cycles through the array of boards and releases I/O ports and the IRQ, if any. Finally, the major number is released. The initial check for MOD_IN_USE is redundant if you're running a recent kernel, but a wise thing to put in production code, because your customers or users may be running old Linux kernels.
The sample code for init_module() and cleanup_module() is shown in Listing 1. The prefix skel_ is used for all non-local names. The code here is quite simplified, in that it lacks some error-checking, which is vital in production-quality source code.
- The Ubuntu Conspiracy
- Vigilante Malware
- Disney's Linux Light Bulbs (Not a "Luxo Jr." Reboot)
- Vagrant Simplified
- Libreboot on an X60, Part I: the Setup
- Bluetooth Hacks
- Dealing with Boundary Issues
- System Status as SMS Text Messages
- Non-Linux FOSS: Code Your Way To Victory!
- October 2015 Issue of Linux Journal: Raspberry Pi