實作ISR
現在我們已經知道如何註冊ISR(Interrupt Service Routine),但是還沒寫出實作的ISR,形式上,ISR只是一般的C函式,其特殊的是其執行的時機-中斷時期(interrupt time)。 我們將要實作的函式利用上節提到函式指標去指定:
void (*handler)(int,void*,struct pt_regs*)
handler引數是中斷處理程序的指標,它的prototype如下所示:
typedef irqreturn_t (*irq_handler_t)(int,void*)
第一引數是IRQ編號,第二引數則會傳入dev_id的內容。中斷處理傳回值是irqreturn_t型別(實際上是int型別),需要傳回適當的數值。在收到中斷後,呼叫中斷處理程序時,很可能是共享IRQ的情形,如果不是這個裝置所負責的中斷處理,就甚麼都不做,但需要傳回IRQ_NONE。
傳回值 | 意義 |
---|---|
IRQ_NONE | 沒有處理這個中斷 |
IRQ_HANDLED | 已經處理這個中斷 |
這些值也可以透過IRQ_RETVAL巨集來選取。
範例(按鈕中斷觸發LED):
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#define LED 27
#define BUTTON 23
#define MY_GPIO_INT_NAME "my_button_int"
#define MY_DEV_NAME "my_device"
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("A Simple GPIO Device Driver module for RaspPi");
static short int button_irq = 0;
static unsigned long flags = 0;
static int led_trigger = 0;
static irqreturn_t button_isr(int irq, void *data)
{
local_irq_save(flags);
printk("button_isr !!!!\n");
gpio_set_value(LED, led_trigger);
led_trigger = led_trigger ? (0):(1);
local_irq_restore(flags);
return IRQ_HANDLED;
}
int init_module(void)
{
// -- setup the led gpio as output
printk("module: button interrupt example.\n");
if(gpio_is_valid(LED) < 0) return -1;
if(gpio_request(LED, "LED") < 0) return -1;
gpio_direction_output(LED, 0 );
// -- setup the button gpio as input and request irq
if(gpio_request(BUTTON,"BUTTON") < 0) return -1;
if(gpio_is_valid(BUTTON) < 0) return -1;
if( (button_irq = gpio_to_irq(BUTTON)) < 0 ) return -1;
if( request_irq( button_irq, button_isr ,IRQF_TRIGGER_FALLING, MY_GPIO_INT_NAME, MY_DEV_NAME)) return -1;
return 0;
}
void cleanup_module(void)
{
gpio_set_value(LED, 0);
gpio_free(LED);
free_irq(button_irq, MY_DEV_NAME);
gpio_free(BUTTON);
}
中斷的生效與失效
有時候驅動程式要暫時抵制特定中斷路線的信號傳達。核心提供三個輔助函式,它們都被定義在<linux/irq.h>
但是如果是共享情形下也沒無法抵制,現在大部分的驅動程式都是共享的,因此這些函式用到的機會就比較少。
void disable_iqr(int irq);
void disable_irq_nosync(int irq);
void enable_irq(int irq);
使用disable_irq(),所有cpu將不會收到此中斷信號,disable_irq_nosync()則是改變完暫存器位元遮罩後立刻返回,不管受影響的IRQ是否有ISR正在執行。enable_irq則是恢復中斷狀態。
倘若要壓制全部中斷的話,可以利用<linux/system.h>
所定義兩個函式之一,來壓制當時處理器受理的任何中斷訊號:
void local_irq_save(unsigned long flags);
void local irq_disable(void);
local_irq_save()會將當時所有中斷狀態存入flags,irq_disable()則不會,而恢復中斷信號能力可用下列兩函式:
void local_irq_retore(unsigned long flags);
void local_irq_disable(void);