Most of the abstract functionality that is needed in a kernel is already in the Linux kernel. Linux has one of the best-designed buffer caches of any Unix-like operating system. A few things are left to be completed in the memory management and in the networking layer, but as more and more development is done, it seems (and reasonably so) that there are fewer and fewer projects that can be done by people with little operating systems knowledge and experience. Those who know enough to know what needs to be done with the memory management don't need to read documentation on how the kernel works; many of them find it faster to simply read the Linux source code.
That is not to say that there = are few projects in the Linux community that beginners can do; there are very many, but most of these projects are not within the kernel itself.
One project that relative beginners can accomplish, and which will never go out of fashion, is writing device drivers for new hardware. There is still hardware that Linux does not support, new hardware is being released by manufacturers all the time, and Linux users buy hardware and then want to use it with Linux.
Fortunately, the interface used to write device drivers is relatively simple and clean. By clean, I mean that there are not many exceptions to the rules or little tricks you have to play to get things to work. Over the next few columns (few is relative, I could spend a few years at this...) I will cover the information you need to know to write various kinds of device drivers.
Some of this information is already in the Linux Kernel Hackers' Guide, but I will expand on the information here. When I write something new here, it will eventually find its way into the Kernel Hackers' Guide; this is one way that Linux Journal supports the Linux Documentation Project.
In an almost contradictory way, I'm to initiate this Kernel Korner column with a description of how (and when!) to implement a device driver as a user program.
The first rule of adding code to the Linux kernel is don't. The code that is in the kernel cannot be swapped, and therefore takes up precious memory whether it is being used at the time or not. Many hardware devices can be driven by user-space programs which are kept nicely out of the way (either swapped out or not running at all) when the device is not being used. One prime example of devices that are implemented this way are video cards.
While the Linux startup code has options to initialize the video modes for many different kinds of cards, the actual device support for video cards in the Linux kernel proper is extremely limited, and is comprised of support for putting text on the screen on either monochrome (hercules-style) or color (CGA, EGA, VGA, and above) cards. No support for graphics is included.
XFree86 provides user-level drivers for many graphics cards within the X servers. These are only loaded when the X servers are running, and parts that aren't being used at the moment can be swapped out when necessary. In addition, by not making the device use a system-call interface to write, these drivers are faster because they are implemented in user space.
There are, of course, drivers that cannot be written as user-space drivers: most commonly, hardware that requires a driver that can service interrupts. We'll deal with these in a future installment.
Perhaps the most common way that a device driver communicates with hardware (at least on the PC architecture) is through the I/O bus. This is a bus which is completely separate from the memory bus, and which is accessed with special machine instructions. For a concrete example, let's use the parallel port. The parallel port driver is in the kernel for three reasons: it can be interrupt-driven, it manages contention, and it has historically been part of the kernel. It's also reasonably small, and very common, so it doesn't bloat the kernel very much. However, the parallel port can be driven from user space. Let's look at how this could be done.
The parallel port has three addresses on the I/O bus, and they are specified by a base address and two offsets. This is common for devices; many devices have several base addresses to choose from, and any other ports that are used are specified as offsets from the base. The three base addresses for the parallel port are given in linux/lp.h, and are (in hex) Ox3bc, Ox378, and Ox278. The status port is the next port above that, and the control port is above the status port. So if the base I/O port, to which characters are written, is Ox378, then the status port is Ox379 and the control port is Ox380.
Perhaps the most common way that a device driver communicates with hardware... is through the 1/0 bus.
You need enough documentation for a device to know how to talk to it. The 8255 chip is the chip that the parallel port is based on, and the documentation for that chip and for the parallel port interface describes the three ports.
The status port can report several conditions when it is read:
If 0, printer is busy
If 0, printer has ACKnowledged the character sent
If 1, printer is out of paper
If 1, printer is on-line
If 0, printer has in an error condition
The control port controls several things when it is written:
Set to 1 to enable sending interrupts when the printer is ready
Set to 1 to tell printer ready to talk
Set to O to tell printer to initialize itself
Set to 1 to prepare to send another byte to printer
Unfortunately, not all printers agree about all the signals that can be sent, so the least common denominator has to be used. This means that I won't use all of the bits you see in the tables. Also, I obviously won't use the interrupt-enable bit, since interrupts can't be used from user-level programs.
I'm also not going to do any serious error detection; I want to show how simple it can be to write a simple driver that works (more or less). If you want to see how error detection could be handled, simply read include/linux/lp.h and drivers/char/lp.c in the Linux kernel source.
The program userlp.c (see sidebar) needs to be compiled with optimization fumed on and run as root (or setuid root) to work. It takes a file from the standard input and prints it to the printer specified on the command line: O, 1, or 2, corresponding to lpO, lpi, and lp2, respectively.
This has only been lightly tested on one printer, unlike the standard kernel driver, so I can't say that it will work on your printer. This doesn't matter, because this is only an example. Note that I could have written the same driver as a /bin/sh script that used /dev/port and dd, and probably done it in less time, but you are more likely to be writing a device driver in C than in /bin/sh.
- Linux Journal December 2016
- Stepping into Science
- CORSAIR's Carbide Air 740
- Tyson Foods Honored as SUSE Customer of the Year
- A Better Raspberry Pi Streaming Solution
- More on Using the Bash Complete Command
- Tech Tip: Really Simple HTTP Server with Python
- Radio Free Linux
- The Tiny Internet Project, Part II
- FutureVault Inc.'s FutureVault