Hardware access

ISA I/O

#include <linux/ioport.h> #include <asm/io.h>

Auf ein Geraet darf immer nur ein Treiber zugreifen. mit request_region(baseport, nr, "name") kann man sich den alleinigen Zugriff sichern (die naechsten request_region mit den gleichen Ports schlagen dann fehl). Mit release_region(baseport, nr) kann man das Geraet wieder freigeben. Kontrolle ueber /proc/ioports

lowlevel IO:

 value = in{b,w,l}(port);
 out{b,w,l}(value, port);

Many architectures support the concept of memory mapped IO. The control registers of hardware devices can be mapped into the normal address space directly. Access to such hardware is possible via direct assignments to the corresponding 'memory' address.

To simplify hardware access for drivers, an IO abstraction has been included in newer Linux kernels. All Hardware IO Addresses can be stored in a void __iomem *. Access to the hardware is done via io{read,write}{8,16,32}, similiar to inb/out.

The kernel will automatically choose inb/outb or memmory mapped access based on the initialization of the void __iomem *.

For example:

 iomem = ioport_map(port, nr);
 ioport_unmap(iomem);

or

 iomem = pci_iomap(...);

Interrupt handling

#include <linux/interrupt.h>

Device driver must be able to react on events that occur in the hardware. This is generally implemented via interrupts. If a device needs the attention of its driver, it generates an interrupt request (IRQ) which causes the CPU to enter the operating system at a defined address.

Usually there are several different interrupt lines that can be used to identify the device that caused such an interrupt. The operating system then uses the interrupt number to find the driver which is responsible for the device that generated an IRQ.

In Linux, a driver can ask the operating sytem to call a function each time an IRQ is generated:

 request_irq(nr, handler, flag, name, dev_id)

To remove the interrupt handler from the system, call free_irq(nr, devid).

After calling request_irq, the function handler will be called each time an IRQ with number nr is generated. This handler will take the parameters nr, dev_id, and a structure holding the register contents of the processor at the time the IRQ was raised. The handler should return IRQ_HANDLED if it handled this IRQ or IRQ_NONE if the corresponding device was not responsible for this IRQ.

dev_id must be globally unique as it is used to identify the handler when freeing it, normally the address of a device data structure is used here.

name is only used for administrative purposes and is shown in /proc/interrupts.

flags is a combination of one or more of the following values:

SA_INTERRUPT

all interrupts will be disabled while the handler is running. This should be used always.

SA_SHIRQ

other drivers are allowed to register handlers for the same nr. That means that the handler may be called even when the corresponding hardware did not generate an interrupt and the handler function must be able to cope with unexpected interrupts. If possible, the driver should use SA_SHIRQ. That way it is possible to share interrupt lines with several devices. As interrupt lines are a rare resource in most machines, interrupt sharing has become a neccessity to support many devices.

SA_SAMPLE_RANDOM

the timing of the IRQ is considered random and should be used to fill the entropy pool of the system.

Interrupt handlers have the highest priority in the entire system. The Linux kernel tries to execute them as quickly as possible, deferring all other things the CPU might be doing at that time. Other interrupts are usually disabled to always let one handler complete its operation even when there are many frequent interrupts. Interrupt handlers (and all other parts of the kernel that disable interrupts) should run as quickly as possible. Otherwise important interrupts could be lost or delayed.

To make interrupt handlers as fast as possible, they should only poll the hardware to look for the reason of the interrupt and should defer all lengthly operations. This will be described in the next section.

Be careful to enable interrupts in the hardware only after the kernel interrupt handler was successfully installed and to quiesce the device before removing the kernel interrupt handler.

Tasklets

#include <linux/softirq.h>

It is often neccessary to start lengthly actions like data transfers in response to a hardware interrupt. This is not allowed in a interrupt handler in order to keep overall interrupt latency low. Lengthly actions should be executed in a Softirq instead. Softirqs are started after normal interrupt processing (called hardirq) is finished and before control is given back to userspace applications. While softirqs are running, interrupts are enabled.

There are a number of predefined softirqs that run one after another. The most importat ones from a driver point of view are tasklets and timers. Tasklets allow to schedule one-shot actions that should be executed as soon as possible while timers allow to schedule actions that are executed later.

Tasklets are activated by tasklet_schedule(tasklet) or tasklet_hi_schedule(tasklet). When scheduled by the tasklet_hi_schedule function, the tasklet will be run from the highest priority softirq, that is just after the normal interrupt handler returns. When scheduled by tasklet_schedule, it will be called from a low priority softirq, after the timer, network, and SCSI subsystems.

Tasklets have to be initialized before they can be scheduled. This is done by calling tasklet_init(tasklet, handler, arg). When tasklet is scheduled, handler will be called with the sole argument arg.