Interrupt handling
The method for notifying the processor when a device generates an event is to interrupt the device. An "interrupt" is only a signal, which can be sent when the hardware needs attention from the processor. Linux processes interruptions in a similar way in user space.
In most cases, a driver registers a processing routine for the interruption of its device and performs proper processing when the interruption arrives. Essentially, interrupt processing routines run in parallel with other code. Therefore, they inevitably cause concurrency issues and compete for data structures and hardware.
A thorough understanding of concurrency control technology is very important for interruptions.
Installation interrupt handling routine
The kernel maintains a registry for the interrupt signal line, which is similar to the I/O port registry. Before using the service, the module requests an interrupt channel (or an IRQ interrupt request), and then releases the channel after use. In many cases, the module also hopes to share the interrupt signal line with other drivers.
The following two interfaces are defined in the <Linux/sched. h> header file.
Int request_irq (unsigned int IRQ, irqreturn_t (* Handler) (INT, void *, struct pt_regs *), unsigned long flags,
Const char * dev_name, void * dev_id );
Void free_irq (unsigned int IRQ, void * dev_id );
When the value returned by the request_irq function to the Request function is 0, the application is successful. If the value is negative, the error code is returned. Function return-ebusy indicates that another driver occupies the interrupt signal line.
Unsigned int IRQ: the interrupt number to be applied.
Irqreturn_t (* Handler) (INT, void *, struct pt_regs *): This is the interrupt handler pointer to be installed.
Unsigned long flags: bit mask related to interrupt management.
Const char * dev_name: string passed to request_irq, used to display the owner of the interrupt in/proc/interrupts.
Void * dev_id: This pointer is used to share the interrupt signal line. It is a unique identifier and can be used when the interrupt signal line is idle, the driver can also point to the driver's own private data zone (used to identify which device has an interruption ). Dev_id can be set to null when the sharing mode is not forced. It is a good idea to use it to point to the data structure of the device.
The bit set in flag is as follows:
Sa_interrupt: When this bit is set, it indicates that this is a "fast" Interrupt Processing routine, and the fast processing routine runs in the interrupted state.
Sa_shirq: This bit indicates that interruption can be shared between devices.
Sa_sample_random: This bit indicates that the generated interrupt can contribute to the entropy pool used by/dev/random and/dev/urandom.
Reading these devices will return real random numbers, helping the application software select a security key for encryption.
This flag should be set if the device is interrupted in a real random period. This flag is not worth setting if device interruption is predictable. This flag should not be set for devices that may be affected by attackers. For more information, see
The comment of drivers/Char/random. C.
The Interrupt Processing Routine can be installed when the driver is initialized or when the device is enabled for the first time. Because the number of Interrupt signals is very limited, we recommend that you install the device when the device is turned on, instead of during initialization, so that you can share these limited resources.
The correct location for calling request_irq should be before the device is enabled for the first time and the hardware is notified of the interruption. The location where free_irq is called is the last time the device is shut down and the hardware is notified that the processor is no longer interrupted. In this way, we must maintain an open count for each device.
If (short_irq> = 0) {// short_irq is the interrupt number we want to apply
Result = request_irq (short_irq, short_interrupt,
Sa_interrupt, "short", null); is a fast processing routine.
, Does not support interrupted sharing
If (result ){
Printk (kern_info "short: Can't get assigned IRQ % I/N", short_irq );
Short_irq =-1; // The interrupt number is negative, indicating that the application failed.
}
}
If (short_irq> = 0 ){
Result = request_irq (short_irq, short_interrupt,
Sa_interrupt, "short", null );
If (result ){
Printk (kern_info "short: Can't get assigned a IRQ % I/N", short_irq );
Short_irq =-1;
} Else {
Outb (0x10, short_base + 2 );
}
}
The following functions are defined in the i386 and x86 architectures:
Int can_request_irq (unsigned int IRQ, unsigned long flags );
If a given interrupt can be successfully allocated, the function returns a non-zero value. However, there may be some changes between request_irq and can_request_irq.
/Porc Interface
When the hardware interrupt arrives at the processor, an internal count increases. This checks whether the device works as expected and provides a way to display the interrupt report in the file/proc/interrupts. Another useful file:/proc/STAT. The meaning of fields in the file can be involved in the data. The difference between the two is that the interrupts file is not used in the architecture, while the stat file is dependent on: the number of fields depends on the hardware under the kernel
<ASM/IRQ. h>. Arm is defined:
Automatic IRQ Detection
One of the most urgent problems during driver initialization is to determine the IRQ to be used by the device.
The driver needs information to correctly install the processing routine. Automatic detection of Interrupt numbers is a basic requirement for driver availability. Sometimes Automatic Detection depends on the default features of some devices. The following are typical parallel interrupt detection programs:
If (short_irq <0 ){
Switch (short_base ){
Case 0x378:
Short_irq = 7;
Break;
Case 0x278:
Short_irq = 2;
Break;
Case 0x3bc:
Short_irq = 5;
Break;
}
}
When the target device has the ability to inform the driver of the interrupt number to be used, the automatic detection of the interrupt number only means that the detection device does not need to do additional work to detect the interruption.
But not every device is friendly to programmers, and they still need some testing work. This work is technically simple:
The driver informs the device of the interruption and observes what happened. If everything goes well, only one interrupt signal line is activated. Although the probe is theoretically simple, the implementation may not be simple. There are two methods to detect interruptions:
Call the auxiliary functions defined by the kernelAndDIY Testing.
Kernel-assisted Detection
The Linux Kernel provides an underlying facility to detect interrupt numbers. It can only work in non-shared interrupt mode, but most hardware can work in shared interrupt mode, it also provides a better way to find and configure the interrupt number. The kernel is composed of two functions, which are declared in the header file <Linux/iterrupt. h>.
Unsigned long probe_irq_on (void );
This function returns a bit mask with no interruptions allocated. The driver must save the returned mask and pass it to the following probe_irq_off function. After calling this function, the driver must schedule the device to interrupt at least once.
Unsigned long probe_irq_off (unsigned long );
After the device is interrupted, the driver calls this function and passes the bitmask returned by probe_irq_on as a parameter to it. Probe_irq_off returns the interrupt number after "probe_irq_on". If no interrupt occurs, 0 is returned (therefore, IRQ 0 cannot be detected, but in any supported architecture, no device can use IRQ 0 ). If multiple interruptions occur, probe_irq_off returns a negative value (with two meanings ).
Note that the interrupt on the device is enabled after probe_irq_on is called, and the interrupt is disabled before probe_irq_off is called. After probe_irq_off, you need to handle the interruption of the previous generation of the device.
Int COUNT = 0;
Do {
Unsigned long mask; defines the variable for saving the probe_irq_on return mask
Maske = probe_irq_on ();
Outb (0x10, short_base + 2); Enable interrupt, fourth pin of short_base + 2
Outb (0x00, short_base); clears the short_base bit
Outb (0xff, short_base); set this bit to interrupt and write data to the data port.
Outb (0x00, short_base + 2); Disable interrupt before calling probe_irq_off
Udelay (5); leave interruption detection for a period of time
Short_irq = probe_irq_off (mask );
If (short_irq = 0) {the return value of probe_irq_off is judged here. If it is zero, no interruption is generated.
Printk (kern_info "Short _; no IRQ reported by probe/N ");
Short_irq =-1;
}
/* If multiple interrupts have been generated, the result is a negative value.
* We should interrupt the service and try again multiple times.
* Retry a maximum of five times and give up
*/
} While (short_irq <0 & COUNT ++ <5)
If (short_irq <0 ){
Printk ("short: probe failed % I times, giving up/N", count );
}
Note that you can call udelay before calling prpbe_irq_off. Detection is a time-consuming task. Therefore, the best method is to detect the interrupt signal line once during module initialization, which is irrelevant to the interrupt processing function arrangement in the initialization function when the device is turned on (recommended) or in the initialization function (not recommended.
DIY Testing
Enable all unused interruptions and observe what will happen. However, we need to give full play to our understanding of the relevant devices. Generally, a device can use one of three or four IRQ numbers for configuration. This allows us to detect the correct IRQ Number without testing all possible IRQ numbers.
The following code tests all possible interruptions and observes what will happen for interruption detection. The trials array lists IRQ that needs to be tested with 0 as the end mark. The tried array is used to record which processing routine is registered by the driver.
Int trials [] = {3, 5, 7, 9, 0 };
Int tried [] = {0, 0, 0, 0 };
Int I, Count = 0;
Install the probe processing routine for all possible disconnections.
For (I = 0; trials [I]; I ++ ){
Tried [I] = request_irq (trials [I], short_probing, sa_interruptible, "short probe", null );
}
Do {
Short_irq = 0;
Outb (ox10, short_base + 2 );
Outb (0x00, short_base );
Outb (0xff, short_base );
Outb (0x00, short_base + 2 );
Udelay (5); // The value of short_irq will be reset in handler if the interrupt occurs.
If (short_irq = 0 ){
Printk (kern_info "short: No IRQ reported by probe./N ");
}
} While (short_irq <= 0 & COUNT ++ <5)
For (I = 0; tried [I]; I ++ ){
If (tried [I] = 0)
Free_irq (trials [I], null );
}
If (short_irq <0 ){
Printk (kern_info "short: probe failed % I times, giving up/N", count );
}
The handler function is implemented as follows:
Irqreturn_t short_probing (int irq, void * dev_id, struct pt_regs * regs)
{
If (short_irq = 0) short_irq = IRQ;
If (short_irq! = IRQ) short_irq =-IRQ;
Return irq_handled;
}
Sometimes, we cannot predict the possible IRQ value. In this case, you need to detect all idle device numbers, not just the interrupt numbers listed by the trials array. In order to detect all interruptions, the IRQ 0 has to be detected from IRQ 0 that the IRQ NR_IRQS-1NR_IRQS is a platform-related constant defined in the header file <ASM/IRQ. h>.
Fast and slow processing routines
Fast interruptions are those that can be processed quickly, and it takes longer to process slow interruptions. When processing a slow interrupt, the processor re-enables the interrupt to avoid the delay of the fast interrupt being too long. In the modern kernel, the difference between fast interruption and slow interruption has disappeared, leaving only one: Fast interruption (Use
Sa_interrupt) other interruptions on the current processor are prohibited during execution. Note: Other processors can still handle interruptions.
Unless you have sufficient reasons to disable other interruptions to run the interrupt processing routine, you should not use sa_interrupt.
Interrupt handling routine
Restrictions on interrupt handlers: 1. processing routines cannot send or accept data to user spaces because they are not executed in the context of any process. 2. The processing routine cannot perform any operations that may cause sleep, such as calling wait_event, using memory allocation operations without the gfp_atomic sign, or locking a semaphore. 3. The schedule function cannot be called for the final processing routine.
The Interrupt Processing routine function is to feed the information about interrupt reception to the device and read or write the data according to the meaning of the service interruption. The first step of the interrupt processing routine is usually to clear a single bit on the Interface Card. Most hardware devices do not produce any other interruptions until their "interruptible-pending" bit is cleared. This also depends on how the hardware works. This step may also need to be done at the end rather than at the beginning; there is no general rule here. Some devices do not need this step because they do not have a "Suspend" bit;
There are few such devices.
A typical task of the interrupt processing routine is to wake up a sleep process on the device if the event waiting for the interrupt notification process has occurred, for example, if new data has arrived.
For example, in the frame capture card example, a process continuously reads the device to obtain a series of images. Before reading each frame of data, the read call is blocked. Whenever a new data frame arrives, the Interrupt Processing routine will wake up the process.
Whether it is a fast or slow processing routine, the programmer should write a processing routine with the shortest execution time. If you need to perform long-term computing, the best way is to use tasklet or workqueue.
Schedule computing tasks at a safer time (see "top half and bottom half ").
Irqreturn_t short_interrupt (int irq, void * dev_id, struct pt_reg * regs)
{
Struct timeval TV;
Int written;
Do_gettimeofday (TV );
Written = sprintf (char *) short_head, "% 08u. % 06u/N ", (INT) (TV. TV _sec % 100000000), (INT) (TV. TV _usec ));
Sprintf is a Formatting Function, prototype: int sprintf (char * string, char * format, arg_list ); the function is to format the parameter and output it to the target string (the first parameter is the target string). The return value indicates the number of characters that are input to the target string.
Bug_on (written! = 16 );
Short_incr_bp (& short_head, written );
Wake_up_interruptible (& short_queue );
Return irq_handled;
}
Static inline short_incr_bp (volatile unsigned long * index, int delta)
{
Unsigned long new = * index + delta;
Barrier (); // optimization of the first and second statements is prohibited.
* Index = (New> (short_buffer + page_size ))? Short_buffer: New );
}
Processing the parameters and return values of a routine
Three parameters are used in the interrupt processing routine: int IRQ, void * dev_id, and struct pt_reg * regs:
1. If there are any messages that can be printed to the log, the interrupt number (int irq) is very useful.
2. Void * dev_id is a type of customer data (that is, private data available for the driver ). The void * parameter passed to the request_irq function is returned as a processing routine when the interrupt occurs (the void * dev_id parameter is the same as * dev_id in the request_irq function ). In general, we will pass a Data Structure pointer to our device for dev_id. In this way, a driver managing several identical devices does not need any additional code in the interrupt processing routine to find out which device generates the current interrupt event.
3. the last parameter struct pt_reg * regs is rarely used. It is used to save the processor context snapshot before the processor enters the interrupted code. This register can be used for monitoring and debugging, generally, a device driver task is not required.
Static irqreturn_t sample_interrupt (int irq, void * dev_id, struct pt_regs * regs)
{
Struct sample_dev * Dev = dev_id;
...
}
Typical open code corresponding to the processing routine:
Static void sample_open (struct inode * inode, struct file * filp)
{
Struct sample_dev * Dev = hwinfo + minor (inode-> I _rdev );
Request_irq (Dev-> IRQ, sample_interrupt,
0/* flags */, "sample", dev/* dev_id */);
...
Return 0;
}
The Interrupt Processing routine should return a value to indicate whether an interrupt is actually handled. If the processing routine finds that the device does need to be processed, irq_handled should be returned; otherwise, the return value is irq_none.
Enable and disable interrupt
Sometimes the device driver must block the interruption in a short period of time. In general, we must block the interruption when we have a spin lock to avoid deadlock. When a spin lock is involved, there are multiple ways to disable interruption. However, we should pay attention to disabling interrupt as little as possible, even in the device driver.
Disable a single interruption: Sometimes (but rarely), the driver needs to disable the interruption of a specific disconnection. The kernel provides three functions <ASM/IRQ. h>. It should be noted that we cannot disable shared disconnection, which is common in modern systems.
Void disable_irq (int irq); // not only will the specified interrupt be prohibited, but it will also wait for the Interrupt Processing Routine being executed to complete. Note that if the thread that calls disable_irq has any resources required to interrupt the processing routine, the system will deadlock.
Void disable_irq_nosync (int irq); // different from disable_irq, disable_irq_nosync returns immediately. Therefore, it will be faster after use, but it is likely to put the driver in the final state.
Void enable_irq (int irq );
Calling any function may update the mask of a specific IRQ in a programmable controller (PIC) to disable or enable all processor-specific IRQ. These functions can be nested, that is, if
If disable_irq is called twice consecutively, two enable_irq requests are required to re-enable IRQ.
. These functions can be called in the Interrupt Processing routine, but it is not good to open them when processing an IRQ.
Disable all interrupts: In <ASM/system. h>, two functions are defined:
Void local_irq_save (unsigned long flags); Save the current interrupt status to flags, and disable interrupt sending on the current processor. Note that flags are directly transmitted.
Void local_irq_disable (void); Disable sending interruption on the local processor without saving the status. It is not recommended.
If multiple functions in the call chain need to disable interrupt, use local_irq_save.
Open interrupt: void local_irq_restore (unsigned long flags );
Void local_irq_enable (void );
In the current 2.6 kernel, there is no way to globally disable all interruptions on the entire system.