- interrupt的精神:
- interrupt handlers不能進入sleep,所以要避免呼叫叫到一些functions,內部潛在包含了進入sleep的function calls。
- 當interrupt handlers部分code要進入critical section,請用spinlocks上鎖,而不是mutexs。
- interrupt handlers不能與user space進行資料交換。
- interrupt handlers必須要儘速執行完畢。為了確保這一點,最好將執行工作切成兩部分,分別是top half與bottom half。handler的top half部分會儘速完成工作,然後可以晚點執行的工作放在bottom half,可以透softirqs與tasklets達到此目的。
- interrupt handlers不能重複被呼叫。當一個handler已經在執行,它的對應IRQ必須要被disable,直到handler執行完畢。
- interrupt handlers能被更高權限的handlers中斷。如果要避免被高權限的handlers中斷,你可以將interrupt handler標示為fast handler。但如果太多被標示為fast handlers被導致系統performance下降,因為interrupt latency時間會變長。
- synchronous interrupt(軟體產生):
- 當runtime錯誤發生,會發出此interrupt。
- software interrupts,例如system calls。
當我們為可中斷devices撰寫interrupt handlers,首先我們需要為每個device配置一個IRQ(interrupt request number),然後透過request_irq()將一個IRQ與一個interrupt handler建立關連,如果IRQ不再使用,透過free_irq()將它釋放掉。下面linux指令可印出系統上active IRQs:
$ cat /proc/interrupts底下範例是在mini2440開發版實現按鍵控制LED跑馬燈範例,此範例會透過tasklets將interrupt handler切成top half與bottom half,以確保top half部分會儘速完成工作,更符合interrupt精神:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/irq.h>
#include <asm/irq.h>
#include <linux/interrupt.h>
#include <asm/uaccess.h>
#include <mach/regs-gpio.h>
#include <linux/gpio.h>
#include <linux/random.h>
#define LED_LEFT_TO_RIGHT 1
#define LED_RIGHT_TO_LEFT 2
#define LED_DOUBLE 3
#define LED_RANDOM 4
#define LED_FLASH 5
#define LED_TURNOFF 6
struct button_irq_desc {
int irq;
int pin;
int pin_setting;
int number;
char *name;
};
static struct button_irq_desc button_irqs [] = {
{IRQ_EINT8 , S3C2410_GPG(0) , S3C2410_GPG0_EINT8 , 0, "KEY0"},
{IRQ_EINT11, S3C2410_GPG(3) , S3C2410_GPG3_EINT11 , 1, "KEY1"},
{IRQ_EINT13, S3C2410_GPG(5) , S3C2410_GPG5_EINT13 , 2, "KEY2"},
{IRQ_EINT14, S3C2410_GPG(6) , S3C2410_GPG6_EINT14 , 3, "KEY3"},
{IRQ_EINT15, S3C2410_GPG(7) , S3C2410_GPG7_EINT15 , 4, "KEY4"},
{IRQ_EINT19, S3C2410_GPG(11), S3C2410_GPG11_EINT19, 5, "KEY5"},
};
struct led_desc {
int pin;
int pin_setting;
};
static struct led_desc leds[] = {
{S3C2410_GPB(5), S3C2410_GPIO_OUTPUT},
{S3C2410_GPB(6), S3C2410_GPIO_OUTPUT},
{S3C2410_GPB(7), S3C2410_GPIO_OUTPUT},
{S3C2410_GPB(8), S3C2410_GPIO_OUTPUT},
};
struct timer_list timer;
unsigned long ticks;
unsigned int led = 0;
int current_irq = -1, current_mode = LED_TURNOFF;
struct tasklet_struct *tsklt;
/* timer時間到所啟動的function */
void leds_timer_func(unsigned long data)
{
int i;
for (i = 0; i < sizeof(leds)/sizeof(leds[0]); i++) {
s3c2410_gpio_setpin(leds[i].pin, 1);
}
switch(current_mode) {
case LED_LEFT_TO_RIGHT:
led++;
led %= 4;
s3c2410_gpio_setpin(leds[led].pin, 0);
break;
case LED_RIGHT_TO_LEFT:
led--;
led %= 4;
s3c2410_gpio_setpin(leds[led].pin, 0);
break;
case LED_DOUBLE:
led++;
led %= 4;
s3c2410_gpio_setpin(leds[led].pin, 0);
s3c2410_gpio_setpin(leds[(led+1)%4].pin, 0);
break;
case LED_RANDOM:
get_random_bytes(&led, sizeof(led));
led %= 4;
s3c2410_gpio_setpin(leds[led].pin, 0);
break;
case LED_FLASH:
led++;
for (i = 0; i < sizeof(leds)/sizeof(leds[0]); i++) {
s3c2410_gpio_setpin(leds[i].pin, led%2);
}
break;
case LED_TURNOFF:
for (i = 0; i < sizeof(leds)/sizeof(leds[0]); i++) {
s3c2410_gpio_setpin(leds[i].pin, 1);
}
break;
}
mod_timer(&timer, jiffies + 30);
}
/* 改變LEDs燈閃爍模式 */
void leds_running_mode(unsigned long data) {
if(current_irq > 0) {
switch(current_irq) {
case IRQ_EINT8:
current_mode = LED_DOUBLE;
printk("LEDs controller is in LED_DOUBLE mode\n");
break;
case IRQ_EINT11:
current_mode = LED_RIGHT_TO_LEFT;
printk("LEDs controller is in LED_RIGHT_TO_LEFT mode\n");
break;
case IRQ_EINT13:
current_mode = LED_RANDOM;
printk("LEDs controller is in LED_RANDOM mode\n");
break;
case IRQ_EINT14:
current_mode = LED_FLASH;
printk("LEDs controller is in LED_FLASH mode\n");
break;
case IRQ_EINT15:
current_mode = LED_TURNOFF;
printk("Turn off LEDs\n");
break;
case IRQ_EINT19:
current_mode = LED_LEFT_TO_RIGHT;
printk("LEDs controller is in LED_LEFT_TO_RIGHT mode\n");
break;
}
}
}
/* interrupt handler */
static irqreturn_t buttons_interrupt(int irq, void *dev_id)
{
printk("IRQ number is %d\n", irq);
current_irq = irq;
tasklet_schedule(tsklt);
return IRQ_HANDLED;
}
static int __init LEDs_controller_module_init(void)
{
int i;
int err = 0;
/* 初始化LEDs燈 */
for (i = 0; i < sizeof(leds)/sizeof(leds[0]); i++) {
s3c2410_gpio_cfgpin(leds[i].pin, leds[i].pin_setting);
s3c2410_gpio_setpin(leds[i].pin, 1);
}
/*初始化interrupt的IRQs*/
for (i = 0; i < sizeof(button_irqs)/sizeof(button_irqs[0]); i++) {
if (button_irqs[i].irq < 0)
continue;
s3c2410_gpio_cfgpin(button_irqs[i].pin, button_irqs[i].pin_setting);
err = request_irq(button_irqs[i].irq, buttons_interrupt, IRQF_DISABLED
| IRQF_TRIGGER_RISING, button_irqs[i].name, NULL);
if (err)
break;
}
/* 當errors發生,釋放已註冊的IRQs */
if (err) {
i--;
for (; i >= 0; i--) {
if (button_irqs[i].irq < 0) {
continue;
}
disable_irq(button_irqs[i].irq);
free_irq(button_irqs[i].irq, NULL);
}
return -EBUSY;
}
/* 初始化tasklet */
tsklt = kmalloc(sizeof(struct tasklet_struct),GFP_KERNEL);
tasklet_init(tsklt, leds_running_mode, 0);
/* 初始化timer */
init_timer(&timer);
timer.data = 10;
timer.expires = jiffies + 30;
timer.function = leds_timer_func;
add_timer(&timer);
ticks = jiffies;
printk("LEDs controller module loaded\n");
return 0;
}
static void __exit LEDs_controller_module_exit(void)
{
int i;
del_timer(&timer); /* 移除timer */
/* 將所有LEDs熄滅 */
for (i = 0; i < sizeof(leds)/sizeof(leds[0]); i++) {
s3c2410_gpio_setpin(leds[i].pin, 1);
}
/* 釋放所以IRQs */
for (i = 0; i < sizeof(button_irqs)/sizeof(button_irqs[0]); i++) {
if (button_irqs[i].irq < 0) {
continue;
}
disable_irq(button_irqs[i].irq);
free_irq(button_irqs[i].irq, NULL);
}
/* 釋放tasklet */
tasklet_kill(tsklt);
printk("LEDs controller module unloaded\n");
}
module_init(LEDs_controller_module_init);
module_exit(LEDs_controller_module_exit);
MODULE_DESCRIPTION("Use buttons to control LEDs in mini2440 board");
MODULE_LICENSE("GPL");
MODULE_ALIAS("LEDs controller module");
MODULE_AUTHOR("Renee's Blog");
Makefile內容:
ifneq ($(KERNELRELEASE),) obj-m := leds_controller.o else KDIR := ~/myfs/rootfs/lib/modules/2.6.32.2-FriendlyARM/build all: make -C $(KDIR) M=$(PWD) modules ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi- clean: rm -f *.ko *.o *.mod.o *.mod.c *.symvers endif上面的範例採用tasklet實現bottom half,經過修改並移除tasklet相關的code,讓底下範例採用softirq實現:
... ... ...
/* 改變LEDs燈閃爍模式 */
void leds_running_mode(struct softirq_action *a) {
... ... ...
}
/* interrupt handler */
static irqreturn_t buttons_interrupt(int irq, void *dev_id)
{
printk("IRQ number is %d\n", irq);
current_irq = irq;
raise_softirq(LED_SOFTIRQ);
return IRQ_HANDLED;
}
static int __init LEDs_controller_module_init(void)
{
... ... ...
/* 當errors發生,釋放已註冊的IRQs */
... ... ...
/* 初始化softirq */
open_softirq(LED_SOFTIRQ, leds_running_mode);
/* 初始化timer */
... ... ...
}
static void __exit LEDs_controller_module_exit(void)
{
... ... ...
/* 釋放所以IRQs */
for (i = 0; i < sizeof(button_irqs)/sizeof(button_irqs[0]); i++) {
if (button_irqs[i].irq < 0) {
continue;
}
disable_irq(button_irqs[i].irq);
free_irq(button_irqs[i].irq, NULL);
}
printk("LEDs controller module unloaded\n");
}
... ... ...
在編譯kernel會出現錯誤,還需要修改底下兩個檔案:kernel/softirq.c
... ... ...
char *softirq_to_name[NR_SOFTIRQS] = {
"HI", "TIMER", "NET_TX", "NET_RX", "BLOCK", "BLOCK_IOPOLL",
"TASKLET", "SCHED", "HRTIMER", "RCU", "LED"
};
... ... ...
void raise_softirq(unsigned int nr)
{
unsigned long flags;
local_irq_save(flags);
raise_softirq_irqoff(nr);
local_irq_restore(flags);
}
EXPORT_SYMBOL(raise_softirq);
void open_softirq(int nr, void (*action)(struct softirq_action *))
{
softirq_vec[nr].action = action;
}
EXPORT_SYMBOL(open_softirq);
... ... ...
linux/interrupt.h
enum
{
HI_SOFTIRQ=0,
TIMER_SOFTIRQ,
NET_TX_SOFTIRQ,
NET_RX_SOFTIRQ,
BLOCK_SOFTIRQ,
BLOCK_IOPOLL_SOFTIRQ,
TASKLET_SOFTIRQ,
SCHED_SOFTIRQ,
HRTIMER_SOFTIRQ,
RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */
LED_SOFTIRQ,
NR_SOFTIRQS
};
Intrrupt相關函式:
| 函式 | 位置 | 說明 |
|---|---|---|
| request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev_id) | kernel/irq/manage.c | 註冊一個IRQ,參數如下: irq:IRQ號碼。 handler:interrupt handler,return的資料型態是irq_handler_t,如果它return值是IRQ_HANDLED,表示成功處理完成,但如果return值是IRQ_NONE,表示處理失敗。 flags:僅列出一些,如下 IRQF_DISABLED IRQF_TRIGGER_RISING IRQF_TRIGGER_FALLING IRQF_TRIGGER_HIGH IRQF_TRIGGER_LOW IRQF_SHARED name:用於識別使用此IRQ的裝置名稱,例如cat /proc/interrupts會列出IRQ號碼與裝置名稱。 dev_id:如果IRQ被很多devices共享,表示共用同一個handler,此資料結構會餵給handler,用於識別IRQ正被哪一個device使用,並作對應處理。 |
| free_irq(unsigned int irq, void *dev_id) | kernel/irq/manage.c | 釋放一個由request_irq()註冊的IRQ,參數如下: irq:IRQ號碼。 dev_id:既是request_irq的最後一個參數。 |
| enable_irq(unsigned int irq) | kernel/irq/manage.c | 重新enable被disable_irq或disable_irq_nosync禁止的interrupt。 |
| disable_irq(unsigned int irq) | kernel/irq/manage.c | 禁止某個IRQ發出interrupt。 |
| disable_irq_nosync(unsigned int irq) | kernel/irq/manage.c | 禁止某個IRQ發出interrupt,但不等是否有正在執行的interrupt handler完成與否。 |
| in_irq() | include/linux/hardirq.h | returns true when in interrupt handler |
| in_softirq() | include/linux/hardirq.h | returns true when in bottom half (softirq,tasklet) |
| in_interrupt() | include/linux/hardirq.h | returns true when in interrupt handler or bottom half |
| open_softirq(int nr, void (*action)(struct softirq_action *)) | kernel/softirq.c | 註冊一個software interrupt handler。 |
| raise_softirq(unsigned int nr) | kernel/softirq.c | 觸發software interrupt handler |
| tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data) | ||
| tasklet_schedule(struct tasklet_struct *t) | ||
| tasklet_enable(struct tasklet_struct *t) | ||
| tasklet_disable(struct tasklet_struct *t) | ||
| tasklet_disable_nosync(struct tasklet_struct *t) | ||
| tasklet_kill(struct tasklet_struct *t) |
Demo影片:
沒有留言:
張貼留言