Although some devices can be controlled using nothing but their I/O regions, most real-world devices are a bit more complicated than that. Devices have to deal with the external world, which often includes things such as spinning disks, moving tape, wires to distant places, and so on. Much has to be done in a time frame that is different, and slower, than that of the processor. Since it is almost always undesirable to have the processor wait on external events, there must be a way for a device to let the processor know when something has happened.
That way, of course, is interrupts. An interrupt is simply a signal that the hardware can send when it wants the processor’s attention. Linux handles interrupts in much the same way that it handles signals in user space. For the most part, a driver need only register a handler for its device’s interrupts, and handle them properly when they arrive. Of course, underneath that simple picture there is some complexity; in particular, interrupt handlers are somewhat limited in the actions they can perform as a result of how they are run.
It is difficult to demonstrate the use of interrupts without a real hardware device to generate them. Thus, the sample code used in this chapter works with the parallel port. We’ll be working with the short module from the previous chapter; with some small additions it can generate and handle interrupts from the parallel port. The module’s name, short, actually means short int (it is C, isn’t it?), to remind us that it handles interrupts.
The way that Linux handles interrupts has changed quite a bit over the years, due to changes in design and in the hardware it works with. The PC’s view of interrupts in the early days was quite simple; there were just 16 interrupt lines and one processor to deal with them. Modern hardware can have many more interrupts, and can also be equipped with fancy advanced programmable interrupt controllers (APICs), which can distribute interrupts across multiple processors in an intelligent (and programmable) way.
Happily, Linux has been able to deal with all of these changes with relatively few incompatibilities at the driver level. Thus, the interface described in this chapter works, with few differences, across many kernel versions. Sometimes things do work out nicely.
Unix-like systems have used the functions cli and sti to disable and enable interrupts for many years. In modern Linux systems, however, using them directly is discouraged. It is increasingly impossible for any routine to know whether interrupts are enabled when it is called; thus, simply enabling interrupts with sti before return is a bad practice. Your function may be returning to a function that expects interrupts to be still disabled.
Thus, if you must disable interrupts, it is better to use the following calls:
unsigned long flags; save_flags(flags); cli(); /* This code runs with interrupts disabled */ restore_flags(flags);
Note that save_flags is a macro, and that it is
passed the variable to hold the flags directly—without an
&
operator. There is also an important
constraint on the use of these macros: save_flags
and restore_flags must be called from the same
function. In other words, you cannot pass the
flags
to another function, unless the other
function is inlined. Code that ignores this restriction will work on
some architectures but will fail on others.
Increasingly, however, even code like the previous example is discouraged wherever possible. In a multiprocessor system, critical code cannot be protected just by disabling interrupts; some sort of locking mechanism must be used. Functions such as spin_lock_irqsave (covered in Section 9.8.2, later in this chapter) provide locking and interrupt control together; these functions are the only really safe way to control concurrency in the presence of interrupts.
cli, meanwhile, disables interrupts on all processors on the system, and can thus affect the performance of the system as a whole.[36]
Thus, explicit calls to cli and related functions are slowly disappearing from much of the kernel. There are occasions where you need them in a device driver, but they are rare. Before calling cli, think about whether you really need to disable all interrupts on the system.
[36] The truth is just a little more complicated than this. If you are already handling an interrupt, cli only disables interrupts on the current CPU.
Get Linux Device Drivers, Second Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.