與Linux設備驅動中中斷處理相關的首先是申請與釋放IRQ的API: request_irq()和free_irq()。
request_irq()的原型為:
int request_irq(unsigned int irq,
void (*handler)(int irq, void *dev_id, struct pt_regs *regs),
unsigned long irqflags,
const char * devname,
void *dev_id); |
irq是要申請的硬件中斷號;
handler是向系統(tǒng)登記的中斷處理函數(shù),是一個回調函數(shù),中斷發(fā)生時,系統(tǒng)調用這個函數(shù),dev_id參數(shù)將被傳遞;
irqflags是中斷處理的屬性,若設置SA_INTERRUPT,標明中斷處理程序是快速處理程序,快速處理程序被調用時屏蔽所有中斷,慢速處理程序不屏蔽;若設置SA_SHIRQ,則多個設備共享中斷,dev_id在中斷共享時會用到,一般設置為這個設備的device結構本身或者NULL。
free_irq()的原型為:
void free_irq(unsigned int irq,void *dev_id);
另外,與Linux中斷息息相關的一個重要概念是Linux中斷分為兩個半部:上半部(tophalf)和下半部(bottom half)。上半部的功能是"登記中斷",當一個中斷發(fā)生時,它進行相應地硬件讀寫后就把中斷例程的下半部掛到該設備的下半部執(zhí)行隊列中去。因此,上半部執(zhí)行的速度就會很快,可以服務更多的中斷請求。但是,僅有"登記中斷"是遠遠不夠的,因為中斷的事件可能很復雜。因此,Linux引入了一個下半部,來完成中斷事件的絕大多數(shù)使命。下半部和上半部最大的不同是下半部是可中斷的,而上半部是不可中斷的,下半部幾乎做了中斷處理程序所有的事情,而且可以被新的中斷打斷!下半部則相對來說并不是非常緊急的,通常還是比較耗時的,因此由系統(tǒng)自行安排運行時機,不在中斷服務上下文中執(zhí)行。
Linux實現(xiàn)下半部的機制主要有tasklet和工作隊列。
tasklet基于Linux softirq,其使用相當簡單,我們只需要定義tasklet及其處理函數(shù)并將二者關聯(lián):
void my_tasklet_func(unsigned long); //定義一個處理函數(shù):
DECLARE_TASKLET(my_tasklet,my_tasklet_func,data); //定義一個tasklet結構my_tasklet,與my_tasklet_func(data)函數(shù)相關聯(lián)
然后,在需要調度tasklet的時候引用一個簡單的API就能使系統(tǒng)在適當?shù)臅r候進行調度運行:
tasklet_schedule(&my_tasklet);
此外,Linux還提供了另外一些其它的控制tasklet調度與運行的API:
DECLARE_TASKLET_DISABLED(name,function,data); //與DECLARE_TASKLET類似,但等待tasklet被使能 tasklet_enable(struct tasklet_struct *); //使能tasklet tasklet_disble(struct tasklet_struct *); //禁用tasklet tasklet_init(struct tasklet_struct *,void (*func)(unsigned long),unsigned long); //類似DECLARE_TASKLET() tasklet_kill(struct tasklet_struct *); // 清除指定tasklet的可調度位,即不允許調度該tasklet |
我們先來看一個tasklet的運行實例,這個實例沒有任何實際意義,僅僅為了演示。它的功能是:在globalvar被寫入一次后,就調度一個tasklet,函數(shù)中輸出"tasklet is executing":
#include <linux/interrupt.h> … //定義與綁定tasklet函數(shù) void test_tasklet_action(unsigned long t); DECLARE_TASKLET(test_tasklet, test_tasklet_action, 0); void test_tasklet_action(unsigned long t) { printk("tasklet is executing\n"); } … ssize_t globalvar_write(struct file *filp, const char *buf, size_t len, loff_t *off) { … if (copy_from_user(&global_var, buf, sizeof(int))) { return - EFAULT; } //調度tasklet執(zhí)行 tasklet_schedule(&test_tasklet); return sizeof(int); } |
由于中斷與真實的硬件息息相關,脫離硬件而空談中斷是毫無意義的,我們還是來舉一個簡單的例子。這個例子來源于SAMSUNG S3C2410嵌入式系統(tǒng)實例,看看其中實時鐘的驅動中與中斷相關的部分:
static struct fasync_struct *rtc_async_queue;
static int __init rtc_init(void)
{
misc_register(&rtc_dev);
create_proc_read_entry("driver/rtc", 0, 0, rtc_read_proc, NULL);
#if RTC_IRQ
if (rtc_has_irq == 0)
goto no_irq2;
init_timer(&rtc_irq_timer);
rtc_irq_timer.function = rtc_dropped_irq;
spin_lock_irq(&rtc_lock);
/* Initialize periodic freq. to CMOS reset default, which is 1024Hz */
CMOS_WRITE(((CMOS_READ(RTC_FREQ_SELECT) &0xF0) | 0x06), RTC_FREQ_SELECT);
spin_unlock_irq(&rtc_lock);
rtc_freq = 1024;
no_irq2:
#endif
printk(KERN_INFO "Real Time Clock Driver v" RTC_VERSION "\n");
return 0;
}
static void __exit rtc_exit(void)
{
remove_proc_entry("driver/rtc", NULL);
misc_deregister(&rtc_dev);
release_region(RTC_PORT(0), RTC_IO_EXTENT);
if (rtc_has_irq)
free_irq(RTC_IRQ, NULL);
}
static void rtc_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
/*
* Can be an alarm interrupt, update complete interrupt,
* or a periodic interrupt. We store the status in the
* low byte and the number of interrupts received since
* the last read in the remainder of rtc_irq_data.
*/
spin_lock(&rtc_lock);
rtc_irq_data += 0x100;
rtc_irq_data &= ~0xff;
rtc_irq_data |= (CMOS_READ(RTC_INTR_FLAGS) &0xF0);
if (rtc_status &RTC_TIMER_ON)
mod_timer(&rtc_irq_timer, jiffies + HZ / rtc_freq + 2 * HZ / 100);
spin_unlock(&rtc_lock);
/* Now do the rest of the actions */
wake_up_interruptible(&rtc_wait);
kill_fasync(&rtc_async_queue, SIGIO, POLL_IN);
}
static int rtc_fasync (int fd, struct file *filp, int on)
{
return fasync_helper (fd, filp, on, &rtc_async_queue);
}
static void rtc_dropped_irq(unsigned long data)
{
unsigned long freq;
spin_lock_irq(&rtc_lock);
/* Just in case someone disabled the timer from behind our back... */
if (rtc_status &RTC_TIMER_ON)
mod_timer(&rtc_irq_timer, jiffies + HZ / rtc_freq + 2 * HZ / 100);
rtc_irq_data += ((rtc_freq / HZ) << 8);
rtc_irq_data &= ~0xff;
rtc_irq_data |= (CMOS_READ(RTC_INTR_FLAGS) &0xF0); /* restart */
freq = rtc_freq;
spin_unlock_irq(&rtc_lock);
printk(KERN_WARNING "rtc: lost some interrupts at %ldHz.\n", freq);
/* Now we have new data */
wake_up_interruptible(&rtc_wait);
kill_fasync(&rtc_async_queue, SIGIO, POLL_IN);
} |
RTC中斷發(fā)生后,激發(fā)了一個異步信號,因此本驅動程序提供了對第6節(jié)異步信號的支持。并不是每個中斷都需要一個下半部,如果本身要處理的事情并不復雜,可能只有一個上半部,本例中的RTC驅動就是如此。