一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

嵌入式linux驅動之中斷架構

 nt_bookworm 2012-03-22


基于內核版本 2.6.30.4


        異常,就是可以打斷CPU正常運行流程的一些事情,比如外部中斷、未定義指令、試圖修改只讀的數(shù)據(jù)、執(zhí)行swi指令(Software Interrupt Instruction ,軟件中斷指令)等。當這些事情發(fā)生時,CPU暫停當前的程序,先處理異常事件,然后再繼續(xù)執(zhí)行被中斷的程序。操作系統(tǒng)中經常通過異常來完成一些特定的功能。其中的中斷也占有很大的一部分。例如下面的這幾種情況:
  • 當CPU執(zhí)行未定義的機器指令時將觸發(fā)“未定義指令異?!?,操作系統(tǒng)可以利用這個特點使用一些自定義的機器指令,它們在異常處理函數(shù)中實現(xiàn)。
  • 當用戶程序試圖讀寫的數(shù)據(jù)或執(zhí)行的指令不在內存中時,也會觸發(fā)一個“數(shù)據(jù)訪問中止異?!被颉爸噶铑A取中止異?!?,在異常處理函數(shù)中將這些數(shù)據(jù)或指令讀入內存,然后重新執(zhí)行被中斷的程序,這樣可以節(jié)省內存,還使得操作系統(tǒng)可以運行這類程序,它們使用的內存遠大于實際的物理內存。



//下面函數(shù)在/arch/arm/kernel/trap.c中

void __init trap_init(void)

{

return;

}


void __init early_trap_init(void)

{

unsigned long vectors = CONFIG_VECTORS_BASE;

extern char __stubs_start[], __stubs_end[];

extern char __vectors_start[], __vectors_end[];

extern char __kuser_helper_start[], __kuser_helper_end[];

int kuser_sz = __kuser_helper_end - __kuser_helper_start;


/*

* Copy the vectors, stubs and kuser helpers (in entry-armv.S)

* into the vector page, mapped at 0xffff0000, and ensure these

* are visible to the instruction stream.

*/

memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);

memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);

memcpy((void *)vectors + 0x1000 - kuser_sz, __kuser_helper_start, kuser_sz);


/*

* Copy signal return handlers into the vector page, and

* set sigreturn to be a pointer to these.

*/

memcpy((void *)KERN_SIGRETURN_CODE, sigreturn_codes,

      sizeof(sigreturn_codes));


flush_icache_range(vectors, vectors + PAGE_SIZE);

modify_domain(DOMAIN_USER, DOMAIN_CLIENT);

}




     這個函數(shù)才是真正要用到的,在init/mian.c中可以找到,調用了trap_init(),而early_trap_init()函數(shù)在setup_arch(&command_line)函數(shù)中調用。在Linux/arch/arm/kernel/setup.c


void __init setup_arch(char **cmdline_p)

{

struct tag *tags = (struct tag *)&init_tags;

struct machine_desc *mdesc;

char *from = default_command_line;


      ................


early_trap_init();

}


       這樣我們就明白了trap_init()函數(shù)的具體調用過程了。下面我們具體來看一下這個trap_init()函數(shù),確切的說是earl_trap_init()函數(shù)。earl_tarp_init函數(shù)(代碼在arch/arm/kernel/traps.c中)被用來設置各種異常的處理向量,包括中斷向量。所謂“向量”,就是一些被安放在固定位置的代碼,當發(fā)生異常時,CPU會自動執(zhí)行這些固定位置上的指令。ARM架構的CPU的異常向量基址可以是0x00000000,也可以是0xffff0000,Linux內核使用后者。earl_trap_init函數(shù)將異常向量復制到0xffff0000處,我們可以在該函數(shù)中看到下面的兩行代碼。

        memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);

    memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);


     (1)、 vectors為目標地自己 等于0xffff0000;
     (2)、地址__vectors_start ~ __vectors_end之間的代碼就是異常向量;在arch/arm/kernel/entry-armv.S中定義,它們復制到地址0xffff0000處。
     (3)、異常向量的代碼很簡單,它們只是一些跳轉指令。發(fā)生異常時,CPU自動執(zhí)行這些指令,跳轉去執(zhí)行更復雜的代碼,比如保存被中斷程序的執(zhí)行環(huán)境,調用異常處理函數(shù),恢復被中斷程序的執(zhí)行環(huán)境并重新運行。
     (4)、這些“更復雜的代碼”在地址__stubs_start ~__stubs_end之間,它們在arch/arm/kernel/entry-armv.S中定義。將它們復制到地址0xffff0000+0x200處。 異常向量、異常向量跳去執(zhí)行的代碼都是使用匯編寫的,它們在arch/arm/kernel/entry-armv.S中。
      (5)、異常向量的代碼如下,其中的“stubs_offset”用來重新定位跳轉的位置(向量被復制到地址0xffff0000處,跳轉的目的代碼被復制到地址0xffff0000+0x200處)。


   

中斷向量表

.globl __vectors_start

__vectors_start:

swi SYS_ERROR0                                    //復位時跳轉到此條代碼處

b vector_und + stubs_offset

ldr pc, .LCvswi + stubs_offset

b vector_pabt + stubs_offset

b vector_dabt + stubs_offset

b vector_addrexcptn + stubs_offset

b vector_irq + stubs_offset                      //irq異常中斷

b vector_fiq + stubs_offset


.globl __vectors_end

__vectors_end:


(1)當異常發(fā)生時跳轉到相應的項去執(zhí)行;

 (2)其中,vector_und、vector_pabt等表示要跳轉去執(zhí)行的代碼。以vector_irq為例,它仍在arch/arm/kernel/entry-armv.S中,通過vector_stub宏來定義。

/*

 * Interrupt dispatcher

 */

vector_stub irq, IRQ_MODE, 4


.long __irq_usr @  0  (USR_26 / USR_32)

.long __irq_invalid @  1  (FIQ_26 / FIQ_32)

.long __irq_invalid @  2  (IRQ_26 / IRQ_32)

.long __irq_svc @  3  (SVC_26 / SVC_32)

.long __irq_invalid @  4

.long __irq_invalid @  5

.long __irq_invalid @  6

.long __irq_invalid @  7

.long __irq_invalid @  8

.long __irq_invalid @  9

.long __irq_invalid @  a

.long __irq_invalid @  b

.long __irq_invalid @  c

.long __irq_invalid @  d

.long __irq_invalid @  e

.long __irq_invalid @  f


        vector_stub是一個宏,它根據(jù)后面的參數(shù)"irq, IRQ_MODE"定義了以“vector_irq”為標號的一段代碼。vector_stub宏的功能為:計算處理完異常后的返回地址、保存一引起寄存器(比如r0、lr、spsr),然后進行管理模式,最后根據(jù)被中斷的工作模式調用下面的某個跳轉分支。當發(fā)生異常時,CPU會根據(jù)異常的類型進入某個工作模式,但是很快vector_stub宏又會強制CPU進行管理模式,在管理模式下進行后續(xù)處理,這種方法簡化了程序的設計,使得異常發(fā)生前的工作模式要么是用戶模式,要么是管理模式

init_IRQ函數(shù)分析

       中斷也是一種異常,之所以把它單獨的列出來,是因為中斷的處理與具體的開發(fā)板密切相關,除一些必須、共用的中斷(比如系統(tǒng)時鐘中斷、片內外設UART中斷)外,必須由驅動開發(fā)者提供處理函數(shù)。內核提煉出中斷處理的共性,搭建一個非常容易擴充的中斷處理體系。
       init_IRQ函數(shù)(代碼在arch/arm/kernel/irq.c中)被用來初始化中斷和處理框架,設置各種中斷的默認
處理函數(shù)。
void __init init_IRQ(void)
{
int irq;

init_vectors();

for (irq = 0; (irq < NR_IRQS); irq++) {
irq_desc[irq].status = IRQ_DISABLED;
irq_desc[irq].action = NULL;
irq_desc[irq].depth = 1;
irq_desc[irq].chip = &m_irq_chip;
}
}

        當發(fā)生中斷時,中斷總入口函數(shù)asm_do_IRQ就可以調用這些函數(shù)進行下一步處理。


       中斷處理的一般過程:

                                      (1)異常發(fā)生;

                                      (2)分辨是哪個中斷;(從INTOFFSET寄存器中獲得)

                                      (3)調用處理函數(shù);

                                      (4)清中斷  (以便下一個中斷發(fā)生,EINTFEND)                                 后面的三項都是在linux內核中的asm_do_IRQ函數(shù)中實現(xiàn).


 asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)

{

struct pt_regs *old_regs = set_irq_regs(regs);


irq_enter();


/*

* Some hardware gives randomly wrong interrupts.  Rather

* than crashing, do something sensible.

*/

if (irq >= NR_IRQS)

handle_bad_irq(irq, &bad_irq_desc);

else

generic_handle_irq(irq);


/* AT91 specific workaround */

irq_finish(irq);


irq_exit();

set_irq_regs(old_regs);

}


static inline void generic_handle_irq(unsigned int irq)

{

generic_handle_irq_desc(irq, irq_to_desc(irq));

}


static inline void generic_handle_irq_desc(unsigned int irq, struct irq_desc *desc)

{

#ifdef CONFIG_GENERIC_HARDIRQS_NO__DO_IRQ

desc->handle_irq(irq, desc);

#else

if (likely(desc->handle_irq))

desc->handle_irq(irq, desc);

else

__do_IRQ(irq);

#endif

}



現(xiàn)在 通過irq_desc結構數(shù)組就可以了解中斷處理體系結構,irq_desc結構的數(shù)據(jù)類型include/linux/irq.h
中定義,
struct irq_desc {
    unsigned int        irq;
    struct timer_rand_state *timer_rand_state;
    unsigned int *kstat_irqs;
#ifdef CONFIG_INTR_REMAP
    struct irq_2_iommu *irq_2_iommu;
#endif
    irq_flow_handler_t    handle_irq; // 當前中斷的處理函數(shù)入口

    struct irq_chip        *chip; //低層的硬件訪問

    struct msi_desc        *msi_desc;
    void            *handler_data;
    void            *chip_data;
    struct irqaction    *action;    // 用戶提供的中斷處理函數(shù)鏈表

    unsigned int        status;        //IRQ狀態(tài)
                ........

    const char        *name; //中斷的名稱

} ____cacheline_internodealigned_in_smp;
 
  

        handle_irq是這個或這組中斷的處理函數(shù)入口。發(fā)生中斷時,總入口函數(shù)asm_do_IRQ將根據(jù)中斷號調用相應irq_desc數(shù)組項中handle_irq.handle_irq使用chip結構中的函數(shù)清除、屏蔽或者重新使能中斷,還要調用用戶在action鏈表中注冊的中斷處理函數(shù)。

       irq_chip結構類型也是在include/linux/irq.h中定義,其中的成員大多用于操作底層硬件,比如設置寄存器以屏蔽中斷,使能中斷,清除中斷等。

struct irq_chip {
    const char  *name;
    unsigned in (*startup)(unsigned int irq);//啟動中斷,如果不設置,缺省為“enable
    void        (*shutdown)(unsigned int irq);/*關閉中斷,如果不設置,缺省為"disab
    void        (*enable)(unsigned int irq);// 使用中斷,如果不設置,缺省為"unmask"
    void        (*disable)(unsigned int irq);//禁止中斷,如果不設置,缺省為“mask”
    void        (*ack)(unsigned int irq);/*響應中斷,通常是清除當前中斷使得可以接收下一個中
    void        (*mask)(unsigned int irq); //屏蔽中斷源
    void        (*mask_ack)(unsigned int irq);//屏蔽和響應中斷
    void        (*unmask)(unsigned int irq);//開啟中斷源
    void        (*eoi)(unsigned int irq);
    ........
    const char    *typename;

};


       irq_desc結構中的irqaction結構類型在include/linux/iterrupt.h中定義。用戶注冊的每個中斷處理函數(shù)用一個irqaction結構來表示,一個中斷比如共享中斷可以有多個處理函數(shù),它們的irqaction結構鏈接成一個鏈表,以action為表頭。irqation結構定義如下:

struct irqaction {
    irq_handler_t handler; //用戶注冊的中斷處理函數(shù)
    unsigned long flags; //中斷標志,比如是否共享中斷,電平觸發(fā)還是邊沿觸發(fā)
    const char *name; //用戶注冊的中斷名字
    void *dev_id; //用戶傳給上面的handler的參數(shù),還可以用來區(qū)分共享中斷
    struct irqaction *next; //指向下一個用戶注冊函數(shù)的指針
    int irq; //中斷號
    struct proc_dir_entry *dir;
    irq_handler_t thread_fn;
    struct task_struct *thread;
    unsigned long thread_flags;
}; 
   irq_desc結構數(shù)組、它的成員“struct irq_chip *chip” "struct irqaction *action",這3種數(shù)據(jù)結構構成了中斷處理體系的框架。下圖中描述了Linxu中斷處理體系結構的關系圖:

 
 

中斷處理流程如下:
                                 (1)發(fā)生中斷時,CPU執(zhí)行異常向量vector_irq的代碼
                                 (2)在vector_irq里面,最終會調用中斷處理的總入口函數(shù)asm_do_IRQ
                                 (3)asm_do_IRQ根據(jù)中斷號調用irq_desc數(shù)組項中的handle_irq。
                                 (4)handle_irq會使用chip成員中的函數(shù)來設置硬件,比如清除中斷、禁止中斷、重新使能中斷等
                                 (5)handle_irq逐個調用用戶在aciton鏈表中注冊的處理函數(shù)

        中斷體系結構的初始化就是構造這些數(shù)據(jù)結構,比如irq_desc數(shù)組項中的handle_irq、chip等成員;用戶注冊中斷時就是構造action鏈表;用戶卸載中斷時就是從action鏈表中去除不需要的項。

中斷處理體系結構的初始化
init_IRQ函數(shù)被用來初始化中斷處理體系結構,代碼在arch/arm/kernel/irq.c中
void __init init_IRQ(void)
{
int irq;

init_vectors();

for (irq = 0; (irq < NR_IRQS); irq++) {
irq_desc[irq].status = IRQ_DISABLED;
irq_desc[irq].action = NULL;
irq_desc[irq].depth = 1;
irq_desc[irq].chip = &m_irq_chip;
}
}


         for循環(huán) 初始化irq_desc結構數(shù)組中每一項的中斷狀態(tài)

irq_desc[irq].chip = &m_irq_chip; 調用架構相關的中斷初始化函數(shù)。對于S3C2440開發(fā)板,這個函數(shù)就是s3c24xx_init_irq,移植machine_desc結構中的init_irq成員就指向這個函數(shù)s3c24xx_init_irq函數(shù)在arch/arm/plat-s3c24xx/irq.c中定義,它為所有中斷設置了芯片相關的數(shù)據(jù)結構(irq_desc[irq].chip),設置了處理函數(shù)入口(irq_desc[irq].handle_irq)。以外部中斷EINT4-EINT23為例,用來設置它們的代碼如下:

void __init s3c24xx_init_irq(void)
{
unsigned long pend;
unsigned long last;
int irqno;
int i;

irqdbf("s3c2410_init_irq: clearing interrupt status flags\n");

/* first, clear all interrupts pending... */

last = 0;
for (i = 0; i < 4; i++) {
pend = __raw_readl(S3C24XX_EINTPEND);

if (pend == 0 || pend == last)
break;

__raw_writel(pend, S3C24XX_EINTPEND);
printk("irq: clearing pending ext status %08x\n", (int)pend);
last = pend;
}

last = 0;
for (i = 0; i < 4; i++) {
pend = __raw_readl(S3C2410_INTPND);

if (pend == 0 || pend == last)
break;

__raw_writel(pend, S3C2410_SRCPND);
__raw_writel(pend, S3C2410_INTPND);
printk("irq: clearing pending status %08x\n", (int)pend);
last = pend;
}

last = 0;
for (i = 0; i < 4; i++) {
pend = __raw_readl(S3C2410_SUBSRCPND);

if (pend == 0 || pend == last)
break;

printk("irq: clearing subpending status %08x\n", (int)pend);
__raw_writel(pend, S3C2410_SUBSRCPND);
last = pend;
}

/* register the main interrupts */

irqdbf("s3c2410_init_irq: registering s3c2410 interrupt handlers\n");

for (irqno = IRQ_EINT4t7; irqno <= IRQ_ADCPARENT; irqno++) {
/* set all the s3c2410 internal irqs */

switch (irqno) {
/* deal with the special IRQs (cascaded) */

case IRQ_EINT4t7:
case IRQ_EINT8t23:
case IRQ_UART0:
case IRQ_UART1:
case IRQ_UART2:
case IRQ_ADCPARENT:
set_irq_chip(irqno, &s3c_irq_level_chip);
set_irq_handler(irqno, handle_level_irq);
break;

case IRQ_RESERVED6:
case IRQ_RESERVED24:
/* no IRQ here */
break;

default:
//irqdbf("registering irq %d (s3c irq)\n", irqno);
set_irq_chip(irqno, &s3c_irq_chip);
set_irq_handler(irqno, handle_edge_irq);
set_irq_flags(irqno, IRQF_VALID);
}
}

/* setup the cascade irq handlers */

set_irq_chained_handler(IRQ_EINT4t7, s3c_irq_demux_extint4t7);
set_irq_chained_handler(IRQ_EINT8t23, s3c_irq_demux_extint8);

set_irq_chained_handler(IRQ_UART0, s3c_irq_demux_uart0);
set_irq_chained_handler(IRQ_UART1, s3c_irq_demux_uart1);
set_irq_chained_handler(IRQ_UART2, s3c_irq_demux_uart2);
set_irq_chained_handler(IRQ_ADCPARENT, s3c_irq_demux_adc);

/* external interrupts */

for (irqno = IRQ_EINT0; irqno <= IRQ_EINT3; irqno++) {
irqdbf("registering irq %d (ext int)\n", irqno);
set_irq_chip(irqno, &s3c_irq_eint0t4);
set_irq_handler(irqno, handle_edge_irq);
set_irq_flags(irqno, IRQF_VALID);
}

for (irqno = IRQ_EINT4; irqno <= IRQ_EINT23; irqno++) {
irqdbf("registering irq %d (extended s3c irq)\n", irqno);
set_irq_chip(irqno, &s3c_irqext_chip);
set_irq_handler(irqno, handle_edge_irq);
set_irq_flags(irqno, IRQF_VALID);
}

/* register the uart interrupts */

irqdbf("s3c2410: registering external interrupts\n");

for (irqno = IRQ_S3CUART_RX0; irqno <= IRQ_S3CUART_ERR0; irqno++) {
irqdbf("registering irq %d (s3c uart0 irq)\n", irqno);
set_irq_chip(irqno, &s3c_irq_uart0);
set_irq_handler(irqno, handle_level_irq);
set_irq_flags(irqno, IRQF_VALID);
}

for (irqno = IRQ_S3CUART_RX1; irqno <= IRQ_S3CUART_ERR1; irqno++) {
irqdbf("registering irq %d (s3c uart1 irq)\n", irqno);
set_irq_chip(irqno, &s3c_irq_uart1);
set_irq_handler(irqno, handle_level_irq);
set_irq_flags(irqno, IRQF_VALID);
}

for (irqno = IRQ_S3CUART_RX2; irqno <= IRQ_S3CUART_ERR2; irqno++) {
irqdbf("registering irq %d (s3c uart2 irq)\n", irqno);
set_irq_chip(irqno, &s3c_irq_uart2);
set_irq_handler(irqno, handle_level_irq);
set_irq_flags(irqno, IRQF_VALID);
}

for (irqno = IRQ_TC; irqno <= IRQ_ADC; irqno++) {
irqdbf("registering irq %d (s3c adc irq)\n", irqno);
set_irq_chip(irqno, &s3c_irq_adc);
set_irq_handler(irqno, handle_edge_irq);
set_irq_flags(irqno, IRQF_VALID);
}

irqdbf("s3c2410: registered interrupt handlers\n");

set_irq_chip(irqno, &s3c_irq_chip);函數(shù)的作用就是“irq_desc[irno].chip = &s3c_irqext_chip”,以后就可能通過irq_desc[irqno].chip結構中的函數(shù)指針設置這些外部中斷的觸發(fā)方式(電平觸發(fā),邊沿觸發(fā)),使能中斷,禁止中斷。

 set_irq_handler(irqno, handle_edge_irq); 設置這些中斷的處理函數(shù)入口為handle_edge_irq,即“irq_desc[irqno].handle_irq =handle_edge_irq”.發(fā)生中斷時,handle_edge_irq函數(shù)會調用用戶注冊的具體處理函數(shù)

set_irq_flags(irqno, IRQF_VALID); 設置中斷標志為“IRQF_VALID”,表示可以使用它們。init_IRQ函數(shù)執(zhí)行完后,irq_desc數(shù)組項的chip,handl_irq成員都被設置   。


用戶注冊中斷處理函數(shù)的過程


    用戶驅動程序通過request_irq函數(shù)向內核注冊中斷處理函數(shù),request_irq函數(shù)根據(jù)中斷號找到irq_desc數(shù)組項,然后在它的action鏈表添加一個表項。

int request_threaded_irq(unsigned int irq, irq_handler_t handler,

irq_handler_t thread_fn, unsigned long irqflags,

const char *devname, void *dev_id)

{

struct irqaction *action;

struct irq_desc *desc;

int retval;

if ((irqflags & (IRQF_SHARED|IRQF_DISABLED)) ==

(IRQF_SHARED|IRQF_DISABLED)) {

pr_warning(

 "IRQ %d/%s: IRQF_DISABLED is not guaranteed on shared IRQs\n",

irq, devname);

}

irqflags |= IRQF_DISABLED;

if ((irqflags & IRQF_SHARED) && !dev_id)

return -EINVAL;

desc = irq_to_desc(irq);

if (!desc)

return -EINVAL;

if (desc->status & IRQ_NOREQUEST)

return -EINVAL;

if (!handler)

return -EINVAL;

action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);

if (!action)

return -ENOMEM;

        action->handler = handler;

action->thread_fn = thread_fn;

action->flags = irqflags;

action->name = devname;

action->dev_id = dev_id;


retval = __setup_irq(irq, desc, action);

if (retval)

kfree(action);

unsigned long flags;

                 disable_irq(irq);

local_irq_save(flags);

               handler(irq, dev_id);

local_irq_restore(flags);

enable_irq(irq);

}

return retval;

}      

retval = __setup_irq(irq, desc, action);setup_irq函數(shù)也是在kernel/irq.manage.c中定義,它完成如下3個主要功能

(1)將新建的irqaction結構鏈入irq_desc[irq]結構的action鏈表中,這有兩種可能。
如果action鏈表為空,則直接鏈入,否則先判斷新建的irqaction結構和鏈表中的irqaction結構所表示的中斷類型是否一致,即是否都聲明為"可共享的"(IRQF_SHARED)、是否都使用相同的觸發(fā)方式,如果一致,則將新建的irqation結構鏈入


  (2)設置irq_desc[irq]結構中chip成員的還沒設置的指針,讓它們指向一些默認函數(shù)
chip成員在init_IRQ函數(shù)初始化中斷體系結構的時候已經設置了,這里只是設置其中還沒設置的指針這通過irq_chip_set_defaults函數(shù)來完成,它在kernel/irq/chip.c中定義


void irq_chip_set_defaults(struct irq_chip *chip)

{

if (!chip->enable)

chip->enable = default_enable;

if (!chip->disable)

chip->disable = default_disable;

if (!chip->startup)

chip->startup = default_startup;

if (!chip->shutdown)

chip->shutdown = chip->disable != default_disable ?

chip->disable : default_shutdown;

if (!chip->name)

chip->name = chip->typename;

if (!chip->end)

chip->end = dummy_irq_chip.end;

}


    (3) 啟動中斷,如果irq_desc[irq]結構中status成員沒有被指明IRQ_NOAUTOEN(表示注冊中斷時不要使用中斷),還要調用chip->startup或chip->enable來啟動中斷,所謂啟動中斷通常就是使用中斷。一般情況下,只有那些“可以自動使能的”中斷對應的irq_desc[irq].status才會被指明為IRQ_NOAUTOEN,所以,無論哪種情況,執(zhí)行request_irq注冊中斷之后,這個中斷就已經被使能了。

總結一下request_irq函數(shù)注冊


(1)irq_des[irq]結構中的action鏈表中已經鏈入了用戶注冊的中斷處理函數(shù)
(2)中斷的觸發(fā)方式已經被設好
(3)中斷已經被使能



中斷的處理過程

asm_do_IRQ是中斷的C語言總入口函數(shù),它在/arch/arm/kernel/irq.c中定義,

106 asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
107 {
108 struct pt_regs *old_regs = set_irq_regs(regs);
109 
110 irq_enter();
111 
112 /*
113 * Some hardware gives randomly wrong interrupts. Rather
114 * than crashing, do something sensible.
115 */
116 if (unlikely(irq >= NR_IRQS)) {
117 if (printk_ratelimit())
118 printk(KERN_WARNING "Bad IRQ%u\n", irq);
119 ack_bad_irq(irq);
120 } else {
121 generic_handle_irq(irq);
122 }
123 
124 /* AT91 specific workaround */
125 irq_finish(irq);
126 
127 irq_exit();
128 set_irq_regs(old_regs);
129 }


desc_hand_irq函數(shù)直接調用desc結構中的hand_irq成員函數(shù),它就是irq_desc[irq].handle.irq

asm_do_IRQ函數(shù)中參數(shù)irq的取值范圍為IRQ_EINT0~(IRQ_EINT0 + 31),只有32個取值。它可能是一個實際的中斷號,也可能是一組中斷的中斷號。這里有S3C2440的芯片特性決定的:發(fā)生中斷時,INTPND寄存器的某一位被置1,INTOFFSET寄存器中記錄了是哪一位(0--31),中斷向量調用asm_do_IRQ之前要把INTOFFSET寄存器的值確定irq參數(shù)。每一個實際的中斷在irq_desc數(shù)組中都有一項與它對應,它們的數(shù)目不止32.當asm_do_IRQ函數(shù)參數(shù)irq表示的是“一組”中斷時,irq_desc[irq].handle_irq成員函數(shù)還需要先分辨出是哪一個中斷,然后調用irq_desc[irqno].handle_irq來進一步處理。

以外部中斷EINT8—EINT23為例,它們通常是邊沿觸發(fā)

(1) 它們被觸發(fā)里,INTOFFSET寄存器中的值都是5,asm_do_IRQ函數(shù)中參數(shù)irq的值為(IRQ_EINTO+5),即IRQ_EINT8t23,

(2)irq_desc[IRQ_EINT8t23].handle_irq在前面init_IRQ函數(shù)初始化中斷體系結構的時候被設為s3c_irq_demux_extint8.

(3)s3c_irq_demux_extint8函數(shù)的代碼在arch/arm/plat-s3c24xx/irq.c中,它首先讀取EINTPEND、EINTMASK寄存器,確定發(fā)生了哪些中斷,重新計算它們的中斷號,然后調用irq_desc數(shù)組項中的handle_irq成員函數(shù)

453 s3c_irq_demux_extint8(unsigned int irq,
454 struct irq_desc *desc)
455 {
456 unsigned long eintpnd = __raw_readl(S3C24XX_EINTPEND); //EINT8-EINT23 發(fā)生時,相應位被置1

457 unsigned long eintmsk = __raw_readl(S3C24XX_EINTMASK);//屏蔽寄存器

458 
459 eintpnd &= ~eintmsk; //清除被屏蔽的位

460 eintpnd &= ~0xff; /* 清除低8位(EINT8對應位8)ignore lower irqs */
461 
462 /* 循環(huán)處理所有子中斷*/
463 
464 while (eintpnd) {
465 irq = __ffs(eintpnd); //確定eintpnd中為1的最高位

466 eintpnd &= ~(1<<irq); //將此們清0

467 
468 irq += (IRQ_EINT4 - 4);//重新計算中斷號,前面計算出irq等于8時,中斷號為

                            IRQ_EINT8
469 generic_handle_irq(irq);//調用這中斷的真正的處理函數(shù)

470 }
471 
472 }
void

(4)IRQ_EINT8--IRQ_EINT23這幾個中斷的處理函數(shù)入口,在init_IRQ函數(shù)初始化中斷體系結構的時候已經被設置為handle_edge_irq函數(shù),desc_handle_irq(irq,irq_desc+irq)就是調用這個函數(shù),它在kernel/irq/chip.c中定義,它用來處理邊沿觸發(fā)的中斷,

中斷發(fā)生的次數(shù)統(tǒng)計

531 handle_edge_irq(unsigned int irq, struct irq_desc *desc)
532 {
533 spin_lock(&desc->lock);
534 
535 desc->status &= ~(IRQ_REPLAY | IRQ_WAITING);
536 
537 /*
538 * If we're currently running this IRQ, or its disabled,
539 * we shouldn't process the IRQ. Mark it pending, handle
540 * the necessary masking and go out
541 */
542 if (unlikely((desc->status & (IRQ_INPROGRESS | IRQ_DISABLED)) ||
543 !desc->action)) {
544 desc->status |= (IRQ_PENDING | IRQ_MASKED);
545 mask_ack_irq(desc, irq);
546 goto out_unlock;
547 }
548 kstat_incr_irqs_this_cpu(irq, desc);
549 
550 /* Start handling the irq */
551 if (desc->chip->ack)
552 desc->chip->ack(irq);
553 
554 /* Mark the IRQ currently in progress.*/
555 desc->status |= IRQ_INPROGRESS;
556 
557 do {
558 struct irqaction *action = desc->action;
559 irqreturn_t action_ret;
560 
561 if (unlikely(!action)) {
562 desc->chip->mask(irq);
563 goto out_unlock;
564 }
565 
566 /*
567 * When another irq arrived while we were handling
568 * one, we could have masked the irq.
569 * Renable it, if it was not disabled in meantime.
570 */
571 if (unlikely((desc->status &
572 (IRQ_PENDING | IRQ_MASKED | IRQ_DISABLED)) ==
573 (IRQ_PENDING | IRQ_MASKED))) {
574 desc->chip->unmask(irq);
575 desc->status &= ~IRQ_MASKED;
576 }
577 
578 desc->status &= ~IRQ_PENDING;
579 spin_unlock(&desc->lock);
580 action_ret = handle_IRQ_event(irq, action);
581 if (!noirqdebug)
582 note_interrupt(irq, desc, action_ret);
583 spin_lock(&desc->lock);
584 
585 } while ((desc->status & (IRQ_PENDING | IRQ_DISABLED)) == IRQ_PENDING);
586 
587 desc->status &= ~IRQ_INPROGRESS;
588 out_unlock:
589 spin_unlock(&desc->lock);
590 }
591 


響應中斷,通常是清除當前中斷使得可以接收下一個中斷,對于IRQ_EINT8~IRQ_EINT23這幾個中斷,desc->chip在前面init_IRQ函數(shù)初始化中斷體系結構的時候被設為s3c_irqext_chip.desc->chip->ack就是s3c_irqext_ack函數(shù),(arch/armplat-s3c24xx/irq.c)它用來清除中斷

handle_IRQ_event函數(shù)來逐個執(zhí)行action鏈表中用戶注冊的中斷處理函數(shù),它在kernel/irq/handle.c中定義。

do {
379 trace_irq_handler_entry(irq, action);
380 ret = action->handler(irq, action->dev_id);//執(zhí)行用戶注冊的中斷處理函數(shù)

381 trace_irq_handler_exit(irq, action, ret);
382 
383 switch (ret) {
384 case IRQ_WAKE_THREAD:
385 /*
386 * Set result to handled so the spurious check
387 * does not trigger.
388 */
389 ret = IRQ_HANDLED;
390 
391 /*
392 * Catch drivers which return WAKE_THREAD but
393 * did not set up a thread function
394 */
395 if (unlikely(!action->thread_fn)) {
396 warn_no_thread(irq, action);
397 break;
398 }
399 
400 /*
408 if (likely(!test_bit(IRQTF_DIED,
409 &action->thread_flags))) {
410 set_bit(IRQTF_RUNTHREAD, &action->thread_flags);
411 wake_up_process(action->thread);
412 }
413 
414 /* Fall through to add to randomness */
415 case IRQ_HANDLED:
416 status |= action->flags;
417 break;
418 
419 default:
420 break;
421 }
422 
423 retval |= ret;
424 action = action->next; //下一個

425 } while (action);

用戶注冊的中斷處理函數(shù)的參數(shù)為中斷號irq,action->dev_id。后一個參數(shù)是通過request_irq函數(shù)注冊中斷時傳入的dev_id參數(shù),它由用戶自己指定、自己使用,可以為空,當這個中斷是“共享中斷”時除外。

對于電平觸發(fā)的中斷,它們的irq_desc[irq].handle_irq通常是handle_level_irq函數(shù)。它也是在kernel/irq/chip.c中定義,其功能與上述handle_edge_irq函數(shù)相似,

對于handle_level_irq函數(shù)已經清除了中斷,但是它只限于清除SoC內部的的信號,如果外設輸入到SoC的中斷信號仍然有效,這就會導致當前中斷處理完成后,會誤認為再次發(fā)生了中斷,對于這種情況,需要用戶注冊的中斷處理函數(shù)中清除中斷,先清除外設的中斷,然后再清除SoC內部的中斷號。

中斷的處理流程可以總結如下

(1)中斷向量調用總入口函數(shù)asm_do_IRQ,傳入根據(jù)中斷號irq

(2)asm_do_IRQ函數(shù)根據(jù)中斷號irq調用irq_desc[irq].handle_irq,它是這個中斷的處理函數(shù)入口,對于電平觸發(fā)的中斷,這個入口函數(shù)通常為handle_level_irq,對于邊沿觸發(fā)的中斷,這個入口通常為handle_edge_irq

(3)入口函數(shù)首先清除中斷,入口函數(shù)是handle_level_irq時還要屏蔽中斷

(4)逐個調用用戶在irq_desc[irq].aciton鏈表中注冊的中斷處理函數(shù)

(5) 入口函數(shù)是handle_level_irq時還要重新開啟中斷

  卸載中斷處理函數(shù)這通過free_irq函數(shù)來實現(xiàn),它與request_irq一樣,也是在kernel/irq/mangage.c中定義。

它需要用到兩個參數(shù):irq和dev_id,它們與通過request_irq注冊中斷函數(shù)時使用的參數(shù)一樣,使用中斷號irq定位action鏈表,再使用dev_id在action鏈表中找到要卸載的表項。同一個中斷的不同中斷處理函數(shù)必須使用不同的dev_id來區(qū)分,在注冊共享中斷時參數(shù)dev_id必惟一。

free_irq函數(shù)的處理過程與request_irq函數(shù)相反

(1)根據(jù)中斷號irq,dev_id從action鏈表中找到表項,將它移除

(2)如果它是惟一的表項,還要調用IRQ_DESC[IRQ].CHIP->SHUTDOWN 或IRQ_DESC[IRQ].CHIP->DISABLW來關閉中斷。

在響應一個特定的中斷的時候,內核會執(zhí)行一個函數(shù),該函數(shù)叫做中斷處理程序(interrupt handler)或中斷服務例程(interrupt service routine ,ISP).產生中斷的每個設備都有一個相應的中斷處理程序,中斷處理程序通常不和特定的設備關聯(lián),而是和特定的中斷關聯(lián)的,也就是說,如果一個設備可以產生多種不同的中斷,那么該就可以對應多個中斷處理程序,相應的,該設備的驅動程序也就要準備多個這樣的函數(shù)。在Linux內核中處理中斷是分為上半部(top half),和下半部(bottom half)之分的。上半部只做有嚴格時限的工作,例如對接收到的中斷進行應答或復位硬件,這些工作是在所有的中斷被禁止的情況下完成的,能夠被允許稍后完成的工作會推遲到下半部去。要想了解上半部和下半部的機制可以閱讀一下《Linux內核設計與實現(xiàn)》

     

                                                                      來源:http://shoufuban.net/content/12/0209/10/1317564_185224326.shtml







    本站是提供個人知識管理的網(wǎng)絡存儲空間,所有內容均由用戶發(fā)布,不代表本站觀點。請注意甄別內容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權內容,請點擊一鍵舉報。
    轉藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多

    欧美综合色婷婷欧美激情| 亚洲国产成人一区二区在线观看 | 国产对白老熟女正在播放| 精品国产亚洲av成人一区| 国产日韩欧美国产欧美日韩 | 男女激情视频在线免费观看| 久久黄片免费播放大全| 日韩精品一区二区三区射精| 久久精品中文字幕人妻中文| 香蕉久久夜色精品国产尤物| 中文字幕在线区中文色 | 丁香六月啪啪激情综合区| 国内尹人香蕉综合在线| 麻豆果冻传媒一二三区| 午夜精品国产精品久久久| 69久久精品亚洲一区二区| 人妻内射在线二区一区| 欧美一级黄片免费视频| 日本道播放一区二区三区| 亚洲专区一区中文字幕| 黑人粗大一区二区三区| 久久夜色精品国产高清不卡| 亚洲最新一区二区三区| 国产午夜精品久久福利| 国内女人精品一区二区三区| 亚洲黄片在线免费小视频| 91偷拍与自偷拍精品| 国产日产欧美精品视频| 色综合久久六月婷婷中文字幕 | 久久精品少妇内射毛片| 青青操视频在线播放免费| 久久精品国产一区久久久| 精品欧美日韩一二三区| 国产精品成人一区二区三区夜夜夜| 欧美一级日韩中文字幕| 国产一区二区三区色噜噜| 国产免费操美女逼视频| 偷拍洗澡一区二区三区| 污污黄黄的成年亚洲毛片| 色综合久久中文综合网| 日韩精品一区二区三区射精 |