1. 程式人生 > 實用技巧 >Linux核心實現透視---硬中斷

Linux核心實現透視---硬中斷

Linux的中斷處理是驅動中比較重要的一部分內容,要清楚具體的實現才能更好的理解而不是靠記住別人理解後總結的規律,所以今天就打算從從原始碼來學習一下Linux核心對於中斷處理過程,設計中斷子系統的初始化的內容比較少,後續有空了在去深入的看看。通過追蹤Linux中斷的響應過程就能知道中斷的具體處理細節。

中斷響應過程

網上總結中斷的執行過程的大致流程是:

  1. 儲存中斷髮生時CPSR暫存器內容到SPSR_irq暫存器中
  2. 修改CPSR暫存器,讓CPU進入處理器模式(processor mode)中的IRQ模式,即修改CPSR暫存器中的M域設定為IRQ Mode。
  3. 硬體自動關閉中斷IRQ或FIQ,即CPSR中的IRQ位或FIQ位置1。
  4. 儲存返回地址到LR_irq暫存器中。
  5. 硬體自動調轉到中斷向量表的IRQ向量。,從此處開始進入軟體領

Linux的原始碼中定義了一段程式碼如下,就是其中斷向量表,然後通過連線指令碼和處理並在啟動過程中將其放到記憶體的特定地址上去並通過CP13暫存器指示。

.section .vectors, "ax", %progbits__vectors_start:
    W(b)    vector_rst
    W(b)    vector_und
    W(ldr)    pc, __vectors_start + 0x1000
    W(b)    vector_pabt
    W(b)    vector_dabt
    W(b)    vector_addrexcptn
    W(b)    vector_irq
    W(b)    vector_fiq

/*
 * Interrupt dispatcher
 */
    @------------------------------------------------vector_stub巨集定義了vector_irq
    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) @----------------------------svc模式數值是0b10011,與上0xf後就是3。 .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巨集定義 .macro vector_stub, name, mode, correction=0 .align 5 vector_\name: .if \correction sub lr, lr, #\correction .endif @ @ Save r0, lr_<exception> (parent PC) and spsr_<exception> @ (parent CPSR) @ stmia sp, {r0, lr} @ save r0, lr mrs lr, spsr str lr, [sp, #8] @ save spsr @ @ Prepare for SVC32 mode. IRQs remain disabled. @ mrs r0, cpsr @修改CPSR暫存器的控制域為SVC模式,為了使中斷處理在SVC模式下執行。 eor r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE) msr spsr_cxsf, r0 @ @ the branch table must immediately follow this code @ @低4位反映了進入中斷前CPU的執行模式,9為USR,3為SVC模式。 and lr, lr, #0x0f THUMB( adr r0, 1f ) @根據中斷髮生點所在的模式,給lr暫存器賦值,__irq_usr或者__irq_svc標籤處。 THUMB( ldr lr, [r0, lr, lsl #2] ) mov r0, spk @得到的lr就是".long __irq_svc" ARM( ldr lr, [pc, lr, lsl #2] ) @把lr的值賦給pc指標,跳轉到__irq_usr或者__irq_svc。 movs pc, lr @ branch to handler in SVC mode ENDPROC(vector_\name)

這裡注意彙編巨集的展開操作,理解了這一部分就能明白,24行處就是定義了vector_irq這個中斷服務函式這個函式內讀取了一些暫存器,然後根據進入中斷前的CPU狀態跳轉到__irq_usr或者 __irq_svc。這裡暫時分析在核心態時的情況所以是跳轉到__irq_svc這個函式這一部分是中斷上下文直接呼叫的所以他也就還在中斷上下文中執行,接著看__irq_svc。

__irq_svc:
    svc_entry
    irq_handler
    @中斷處理結束後,發生搶佔的地方
#ifdef CONFIG_PREEMPT
    get_thread_info tsk
    @獲取thread_info->preempt_cpunt變數;preempt_count為0,說明可以搶佔程序;preempt_count大於0,表示不能搶佔。
    ldr    r8, [tsk, #TI_PREEMPT]        @ get preempt count
    ldr    r0, [tsk, #TI_FLAGS]        @ get flags
    teq    r8, #0                @ if preempt count != 0
    movne    r0, #0                @ force flags to 0
    @判斷是否設定了_TIF_NEED_RESCHED標誌位
    tst    r0, #_TIF_NEED_RESCHED
    blne    svc_preempt
#endif

    svc_exit r5, irq = 1            @ return from exception
 UNWIND(.fnend        )
ENDPROC(__irq_svc)

__irq_svc處理髮生在核心空間的中斷,主要svc_entry保護中斷現場;irq_handler執行中斷處理,如果開啟搶佔功能後續還要檢查是否可以搶佔這裡暫時不看;最後svc_exit執行中斷退出處理。到irq_handler就會呼叫C程式碼部分的通用中斷子系統處理過程。直接上原始碼

.macro    irq_handler
#ifdef CONFIG_MULTI_IRQ_HANDLER
    ldr    r1, =handle_arch_irq
    mov    r0, sp
    @設定返回地址
    adr    lr, BSYM(9997f)
    @呼叫handle_arch_irq 儲存的處理介面,這裡使用的是ldr所以不會覆蓋LR暫存器從而保證了正常返回
    ldr    pc, [r1]
#else
    arch_irq_handler_default
#endif
9997:
    .endm


其中handle_arch_irq的是在中斷子系統初始化過程中繫結的,在gic控制器的環境下為gic_handle_irq的,其中的處理分中斷後大於15和小於等於的兩種情況見原始碼。

static void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
{
    u32 irqstat, irqnr;
    struct gic_chip_data *gic = &gic_data[0];
    void __iomem *cpu_base = gic_data_cpu_base(gic);

    do {
        @讀取IAR暫存器
        irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK);
        @GICC_IAR_INT_ID_MASK為0x3ff,即低10位,所以中斷最多從0~1023。
        irqnr = irqstat & GICC_IAR_INT_ID_MASK;

        if (likely(irqnr > 15 && irqnr < 1021)) {
            handle_domain_irq(gic->domain, irqnr, regs);
            continue;
        }
        @SGI型別的中斷是CPU核間通訊所用,只有定義了CONFIG_SMP才有意義。
        if (irqnr < 16) {
            @直接寫EOI暫存器,表示結束中斷。
            writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);
#ifdef CONFIG_SMP
            handle_IPI(irqnr, regs);
#endif
            continue;
        }
        break;
    } while (1);
}

通過以個迴圈處理所有的硬體中斷,根據GIC中斷控制器的原理小於16的中斷是用於SMP系統的核間通訊的所以,只在SMP架構下起作用。所以常用中斷處理流程走的是irqnr > 15 && irqnr < 1021 這個分支呼叫--->handle_domain_irq(gic->domain, irqnr, regs)//irqnr 是硬體中斷號;reg是svc_entry儲存的中斷現場;domain是中斷子系統初始化時根據中斷控制器的不同初始化的一箇中斷操作介面。由原始碼可以看到中斷處理最重要的是通過呼叫__handle_domain_irq介面,並且在lookp中傳入了ture。

這裡再來看__handle_domain_irq的處理流程:

int __handle_domain_irq(struct irq_domain *domain, unsigned int hwirq,
            bool lookup, struct pt_regs *regs)
{
    struct pt_regs *old_regs = set_irq_regs(regs);
    unsigned int irq = hwirq;
    int ret = 0;

    @通過顯式增加hardirq域計數,通知Linux進入中斷上下文
    irq_enter();

#ifdef CONFIG_IRQ_DOMAIN
    if (lookup)
        @根據硬體中斷號找到對應的軟體中斷號
        irq = irq_find_mapping(domain, hwirq);
#endif

    /*
     * Some hardware gives randomly wrong interrupts.  Rather
     * than crashing, do something sensible.
     */
    if (unlikely(!irq || irq >= nr_irqs)) {
        ack_bad_irq(irq);
        ret = -EINVAL;
    } else {
        @開始具體某一箇中斷的處理,此處irq已經是Linux中斷號。
        generic_handle_irq(irq);
    }

    irq_exit();
    @退出中斷上下文
    set_irq_regs(old_regs);
    return ret;
}

其中處理中斷的介面是generic_handle_irq,進入到generic_handle_irq引數是irq號,irq_to_desc()根據irq號找到對應的struct irq_desc。然後呼叫generic_handle_irq_desc(irq,desc)進一步呼叫呼叫irq_desc->handle_irq處理對應的中斷。

int generic_handle_irq(unsigned int irq)
{
    struct irq_desc *desc = irq_to_desc(irq);

    if (!desc)
        return -EINVAL;
    generic_handle_irq_desc(irq, desc);
    return 0;
}

static inline void generic_handle_irq_desc(unsigned int irq, struct irq_desc *desc)
{
desc->handle_irq(irq, desc);
}

這裡需要明白中斷描述符中的handle_irq介面的初始化也是分情況的。每個中斷描述符註冊的時候,由gic_irq_domain_map根據hwirq號決定。在gic_irq_domain_map的時候根據hw號決定handle,hw硬體中斷號小於32指向handle_percpu_devid_irq,其他情況指向handle_fasteoi_irq。驅動程式設計通常情況都是使外部中斷而非內部異常所以中斷號>32,所以handle_irq = handle_fasteoi_irq。

void handle_fasteoi_irq(unsigned int irq, struct irq_desc *desc)
{
    @chip 中封裝了中斷控制器的ARCH層操作
    struct irq_chip *chip = desc->irq_data.chip;

    raw_spin_lock(&desc->lock);

    if (!irq_may_run(desc))
        goto out;

    desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);
    kstat_incr_irqs_this_cpu(irq, desc);

    /*
     * If its disabled or no action available
     * then mask it and get out of here:
     */
     @如果該中斷沒有指定action描述符或該中斷被關閉了IRQD_IRQ_DISABLED,設定該中斷狀態為IRQS_PENDING,且mask_irq()遮蔽該中斷。
    if (unlikely(!desc->action || irqd_irq_disabled(&desc->irq_data))) {
        desc->istate |= IRQS_PENDING;
        mask_irq(desc);
        goto out;
    }
    @如果中斷是IRQS_ONESHOT,不支援中斷巢狀,那麼應該呼叫mask_irq()來遮蔽該中斷源。
    if (desc->istate & IRQS_ONESHOT)
        mask_irq(desc);

    preflow_handler(desc);
    @實際的中斷處理介面
    handle_irq_event(desc);
    根據不同條件執行unmask_irq()解除中斷遮蔽,或者執行irq_chip->irq_eoi傳送EOI訊號,通知GIC中斷處理完畢。
    cond_unmask_eoi_irq(desc, chip);

    raw_spin_unlock(&desc->lock);
    return;
out:
    if (!(chip->flags & IRQCHIP_EOI_IF_HANDLED))
        chip->irq_eoi(&desc->irq_data);
    raw_spin_unlock(&desc->lock);
}

handle_irq_event呼叫handle_irq_event_percpu,執行action->handler(),如有需要喚醒核心中斷執行緒執行action->thread_fn見原始碼如下:

irqreturn_t handle_irq_event(struct irq_desc *desc)
{
    struct irqaction *action = desc->action;
    irqreturn_t ret;
    @清除IRQS_PENDING標誌位
    desc->istate &= ~IRQS_PENDING;
    @設定IRQD_IRQ_INPROGRESS標誌位,表示正在處理硬體中斷。
    irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS);
    raw_spin_unlock(&desc->lock);

    ret = handle_irq_event_percpu(desc, action);

    raw_spin_lock(&desc->lock);
    @清除IRQD_IRQ_INPROGRESS標誌位,表示中斷處理結束。
    irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);
    return ret;
}

handle_irq_event_percpu,以此處理每個CPU上的中斷的每個action(share):

irqreturn_t handle_irq_event_percpu(struct irq_desc *desc, struct irqaction *action)
{
    irqreturn_t retval = IRQ_NONE;
    unsigned int flags = 0, irq = desc->irq_data.irq;
    @遍歷中斷描述符中的action連結串列,依次執行每個action元素中的primary handler回撥函式action->handler。
    do {
        irqreturn_t res;

        trace_irq_handler_entry(irq, action);
        @執行struct irqaction的handler函式。這就是申請註冊中斷介面時指定的中斷服務函式
        res = action->handler(irq, action->dev_id);
        trace_irq_handler_exit(irq, action, res);

        if (WARN_ONCE(!irqs_disabled(),"irq %u handler %pF enabled interrupts\n",
                  irq, action->handler))
            local_irq_disable();
        @根據中斷服務函式的返回值執行操作
        switch (res) {
        @需要喚醒核心中斷執行緒,去喚醒核心中斷執行緒
        case IRQ_WAKE_THREAD:
            /*
             * Catch drivers which return WAKE_THREAD but
             * did not set up a thread function
             */
            if (unlikely(!action->thread_fn)) {
                @輸出一個打印表示沒有中斷處理函式
                warn_no_thread(irq, action);
                break;
            }
            @喚醒此中斷對應的核心執行緒
            __irq_wake_thread(desc, action);

            /* Fall through to add to randomness */
        case IRQ_HANDLED:
            @已經處理完畢,可以結束。
            flags |= action->flags;
            break;

        default:
            break;
        }

        retval |= res;
        action = action->next;
    } while (action);

    add_interrupt_randomness(irq, flags);

    if (!noirqdebug)
        note_interrupt(irq, desc, retval);
    return retval;
}

到這裡應該大部分處理流程都清楚了,中斷髮生後通過以部分arch層相關的程式碼處理後得到Linux中斷子系統的中斷號進而找到對應的中斷,而驅動在程式設計過程中已經通過request_irq介面註冊好action介面函式到對應的中斷。中斷執行過程中直接找到對應的介面進行呼叫就完成了中斷的響應處理,然後在根據中斷handler處理的返回值如果是IRQ_WAKE_THREAD則喚醒中斷註冊時候建立的執行緒,這個執行緒的具體內容後面中斷註冊內容部分再具體學習。這裡說明request_irq 申請註冊的中斷服務函式的執行時在中斷上下文中的,但是實際上核心為例提高實時性進行了一部分優化通過強制中斷服務介面執行緒化,這個後面學習。 先來看上面提到的區分中斷號小於32的處理介面handle_percpu_devid_irq()。

void handle_percpu_devid_irq(unsigned int irq, struct irq_desc *desc)
{
    struct irq_chip *chip = irq_desc_get_chip(desc);
    struct irqaction *action = desc->action;
    void *dev_id = raw_cpu_ptr(action->percpu_dev_id);
    irqreturn_t res;

    kstat_incr_irqs_this_cpu(irq, desc);

    if (chip->irq_ack)
        chip->irq_ack(&desc->irq_data);

    trace_irq_handler_entry(irq, action);
    res = action->handler(irq, dev_id);
    trace_irq_handler_exit(irq, action, res);

    if (chip->irq_eoi)
        #呼叫gic_eoi_irq()函式
        chip->irq_eoi(&desc->irq_data);
}

首先響應IAR,然後執行handler這裡的介面是核心註冊的介面不是驅動開發過程使用(暫時這麼理解),最後傳送EOI基本上。它的處理流程不同於handle_fasteoi_irq。

中斷註冊流程

  核心驅動的開發過程常用申請中斷的介面是request_irq,他實際上是對另一個介面的呼叫封轉如下,所以進一步檢視request_threaded_irq的處理過程

static inline int __must_check
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
        const char *name, void *dev)
{
    return request_threaded_irq(irq, handler, NULL, flags, name, dev);
}

request_threaded_irq的處理過程才是正真的中斷註冊介面,而request_irq是一個將thread_fn設定為NULL的封裝。這做其實是和他的實現有關係的繼續往下看就明白了。

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;

    /*
     * Sanity-check: shared interrupts must pass in a real dev-ID,
     * otherwise we'll have trouble later trying to figure out
     * which interrupt is which (messes up the interrupt freeing
     * logic etc).
     */
    if ((irqflags & IRQF_SHARED) && !dev_id)
        return -EINVAL;

    desc = irq_to_desc(irq);
    if (!desc)
        return -EINVAL;

    if (!irq_settings_can_request(desc) ||
        WARN_ON(irq_settings_is_per_cpu_devid(desc)))
        return -EINVAL;
    //如果中斷沒有任何響應介面肯定是不行,但是是可以沒有handler
    if (!handler) {
        if (!thread_fn)
            return -EINVAL;
        handler = irq_default_primary_handler;
    }
  //申請一個action結構,這個會在後面繫結到中斷描述符中去以便中斷髮生時能找到這個介面
    action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
    if (!action)
        return -ENOMEM;
    //初始化構造的action
    action->handler = handler;
    action->thread_fn = thread_fn;
    action->flags = irqflags;
    action->name = devname;
    action->dev_id = dev_id;
  //接下來的呼叫是重點,完成了中斷註冊的主要部分
    chip_bus_lock(desc);
    retval = __setup_irq(irq, desc, action);
    chip_bus_sync_unlock(desc);

    if (retval)
        kfree(action);

#ifdef CONFIG_DEBUG_SHIRQ_FIXME
    if (!retval && (irqflags & IRQF_SHARED)) {
        /*
         * It's a shared IRQ -- the driver ought to be prepared for it
         * to happen immediately, so let's make sure....
         * We disable the irq to make sure that a 'real' IRQ doesn't
         * run in parallel with our fake.
         */
        unsigned long flags;

        disable_irq(irq);
        local_irq_save(flags);

        handler(irq, dev_id);

        local_irq_restore(flags);
        enable_irq(irq);
    }
#endif
    return retval;
}

下面這個介面是中斷註冊的關鍵也是能說明現在Linux核心對於中斷的處理的正真實現機制,這裡也就是前面說到的核心對實時性優化的中斷處理機制。

/*
 * Internal function to register an irqaction - typically used to
 * allocate special interrupts that are part of the architecture.
 */
static int
__setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
{
    struct irqaction *old, **old_ptr;
    unsigned long flags, thread_mask = 0;
    int ret, nested, shared = 0;
    cpumask_var_t mask;

    if (!desc)
        return -EINVAL;

    if (desc->irq_data.chip == &no_irq_chip)
        return -ENOSYS;
    if (!try_module_get(desc->owner))
        return -ENODEV;

    /*
     * Check whether the interrupt nests into another interrupt
     * thread.
     */
    //對於設定了_IRQ_NESTED_THREAD巢狀型別的中斷描述符,必須指定thread_fn。
    nested = irq_settings_is_nested_thread(desc);
    if (nested) {
        if (!new->thread_fn) {
            ret = -EINVAL;
            goto out_mput;
        }
        /*
         * Replace the primary handler which was provided from
         * the driver for non nested interrupt handling by the
         * dummy function which warns when called.
         */
        //巢狀型別的中斷的執行緒函式由這個替換,這個在前面設定過,所以這裡是將原來指定的handler 
        //介面丟棄了,替換成現在的介面只是輸出一個警告,所以說明不支援IRQ_NESTED_THREAD的handler
        new->handler = irq_nested_primary_handler;
    } else {
        //強制執行緒化了中斷的服務介面,只要中斷沒有用使用IRQF_NO_THREAD強制不能執行緒化都會執行緒化它
        if (irq_settings_can_thread(desc))
            irq_setup_forced_threading(new);
    }

    /*
     * Create a handler thread when a thread function is supplied
     * and the interrupt does not nest into another interrupt
     * thread.
     */
     //執行緒化中斷設定之後的的執行緒建立
    if (new->thread_fn && !nested) {
        struct task_struct *t;
        static const struct sched_param param = {
            .sched_priority = MAX_USER_RT_PRIO/2,
        };

        t = kthread_create(irq_thread, new, "irq/%d-%s", irq,
                   new->name);
        if (IS_ERR(t)) {
            ret = PTR_ERR(t);
            goto out_mput;
        }
        //設定排程方式為FIFO
        sched_setscheduler_nocheck(t, SCHED_FIFO, &param);

        /*
         * We keep the reference to the task struct even if
         * the thread dies to avoid that the interrupt code
         * references an already freed task_struct.
         */
        get_task_struct(t);
        new->thread = t;
        /*
         * Tell the thread to set its affinity. This is
         * important for shared interrupt handlers as we do
         * not invoke setup_affinity() for the secondary
         * handlers as everything is already set up. Even for
         * interrupts marked with IRQF_NO_BALANCE this is
         * correct as we want the thread to move to the cpu(s)
         * on which the requesting code placed the interrupt.
         */
         //設定CPU親和性
        set_bit(IRQTF_AFFINITY, &new->thread_flags);
    }

    if (!alloc_cpumask_var(&mask, GFP_KERNEL)) {
        ret = -ENOMEM;
        goto out_thread;
    }

    /*
     * Drivers are often written to work w/o knowledge about the
     * underlying irq chip implementation, so a request for a
     * threaded irq without a primary hard irq context handler
     * requires the ONESHOT flag to be set. Some irq chips like
     * MSI based interrupts are per se one shot safe. Check the
     * chip flags, so we can avoid the unmask dance at the end of
     * the threaded handler for those.
     */
    if (desc->irq_data.chip->flags & IRQCHIP_ONESHOT_SAFE)
        new->flags &= ~IRQF_ONESHOT;

    /*
     * The following block of code has to be executed atomically
     */
    //共享中斷可能會有多個Action,這裡吧它全部加進去形成連結串列
    raw_spin_lock_irqsave(&desc->lock, flags);
    old_ptr = &desc->action;
    old = *old_ptr;
    if (old) {
        /*
         * Can't share interrupts unless both agree to and are
         * the same type (level, edge, polarity). So both flag
         * fields must have IRQF_SHARED set and the bits which
         * set the trigger type must match. Also all must
         * agree on ONESHOT.
         */
        if (!((old->flags & new->flags) & IRQF_SHARED) ||
            ((old->flags ^ new->flags) & IRQF_TRIGGER_MASK) ||
            ((old->flags ^ new->flags) & IRQF_ONESHOT))
            goto mismatch;

        /* All handlers must agree on per-cpuness */
        if ((old->flags & IRQF_PERCPU) !=
            (new->flags & IRQF_PERCPU))
            goto mismatch;

        /* add new interrupt at end of irq queue */
        do {
            /*
             * Or all existing action->thread_mask bits,
             * so we can find the next zero bit for this
             * new action.
             */
            thread_mask |= old->thread_mask;
            old_ptr = &old->next;
            old = *old_ptr;
        } while (old);
        shared = 1;
    }

    /*
     * Setup the thread mask for this irqaction for ONESHOT. For
     * !ONESHOT irqs the thread mask is 0 so we can avoid a
     * conditional in irq_wake_thread().
     */
     //RQF_ONESHOT型別中斷 的中斷掩蔽程式碼
    if (new->flags & IRQF_ONESHOT) {
        /*
         * Unlikely to have 32 resp 64 irqs sharing one line,
         * but who knows.
         */
        if (thread_mask == ~0UL) {
            ret = -EBUSY;
            goto out_mask;
        }
        /*
         * The thread_mask for the action is or'ed to
         * desc->thread_active to indicate that the
         * IRQF_ONESHOT thread handler has been woken, but not
         * yet finished. The bit is cleared when a thread
         * completes. When all threads of a shared interrupt
         * line have completed desc->threads_active becomes
         * zero and the interrupt line is unmasked. See
         * handle.c:irq_wake_thread() for further information.
         *
         * If no thread is woken by primary (hard irq context)
         * interrupt handlers, then desc->threads_active is
         * also checked for zero to unmask the irq line in the
         * affected hard irq flow handlers
         * (handle_[fasteoi|level]_irq).
         *
         * The new action gets the first zero bit of
         * thread_mask assigned. See the loop above which or's
         * all existing action->thread_mask bits.
         */
        new->thread_mask = 1 << ffz(thread_mask);

    /*handler使用預設irq_default_primary_handler(),如果中斷觸發型別是LEVEL,
    *如果中斷出發後不清中斷容易引發中斷風暴。提醒驅動開發者,沒有primary handler
    */且中斷控制器不支援硬體oneshot,必須顯式指定IRQF_ONESHOT表示位。

    } else if (new->handler == irq_default_primary_handler &&
           !(desc->irq_data.chip->flags & IRQCHIP_ONESHOT_SAFE)) {
        /*
         * The interrupt was requested with handler = NULL, so
         * we use the default primary handler for it. But it
         * does not have the oneshot flag set. In combination
         * with level interrupts this is deadly, because the
         * default primary handler just wakes the thread, then
         * the irq lines is reenabled, but the device still
         * has the level irq asserted. Rinse and repeat....
         *
         * While this works for edge type interrupts, we play
         * it safe and reject unconditionally because we can't
         * say for sure which type this interrupt really
         * has. The type flags are unreliable as the
         * underlying chip implementation can override them.
         */
        pr_err("Threaded irq requested with handler=NULL and !ONESHOT for irq %d\n",
               irq);
        ret = -EINVAL;
        goto out_mask;
    }
    //非共享中斷情況
    if (!shared) {
        ret = irq_request_resources(desc);
        if (ret) {
            pr_err("Failed to request resources for %s (irq %d) on irqchip %s\n",
                   new->name, irq, desc->irq_data.chip->name);
            goto out_mask;
        }
        //初始化阻塞佇列
        init_waitqueue_head(&desc->wait_for_threads);
        //設定中斷觸發方式
        /* Setup the type (level, edge polarity) if configured: */
        if (new->flags & IRQF_TRIGGER_MASK) {
            ret = __irq_set_trigger(desc, irq,
                    new->flags & IRQF_TRIGGER_MASK);

            if (ret)
                goto out_mask;
        }
        //清楚狀態標誌
        desc->istate &= ~(IRQS_AUTODETECT | IRQS_SPURIOUS_DISABLED | \
                  IRQS_ONESHOT | IRQS_WAITING);
        irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);

        if (new->flags & IRQF_PERCPU) {
            irqd_set(&desc->irq_data, IRQD_PER_CPU);
            irq_settings_set_per_cpu(desc);
        }

        if (new->flags & IRQF_ONESHOT)
            desc->istate |= IRQS_ONESHOT;
        //如果可以直接啟動則直接使能,預設不設定就是自動的
        if (irq_settings_can_autoenable(desc))
            irq_startup(desc, true);
        else
            /* Undo nested disables: */
            desc->depth = 1;

        /* Exclude IRQ from balancing if requested */
        if (new->flags & IRQF_NOBALANCING) {
            irq_settings_set_no_balancing(desc);
            irqd_set(&desc->irq_data, IRQD_NO_BALANCING);
        }

        /* Set default affinity mask once everything is setup */
        setup_affinity(irq, desc, mask);

    } else if (new->flags & IRQF_TRIGGER_MASK) {
        unsigned int nmsk = new->flags & IRQF_TRIGGER_MASK;
        unsigned int omsk = irq_settings_get_trigger_mask(desc);

        if (nmsk != omsk)
            /* hope the handler works with current  trigger mode */
            pr_warning("irq %d uses trigger mode %u; requested %u\n",
                   irq, nmsk, omsk);
    }

    new->irq = irq;
    *old_ptr = new;

    irq_pm_install_action(desc, new);

    /* Reset broken irq detection when installing new handler */
    desc->irq_count = 0;
    desc->irqs_unhandled = 0;

    /*
     * Check whether we disabled the irq via the spurious handler
     * before. Reenable it and give it another chance.
     */
    if (shared && (desc->istate & IRQS_SPURIOUS_DISABLED)) {
        desc->istate &= ~IRQS_SPURIOUS_DISABLED;
        __enable_irq(desc, irq);
    }

    raw_spin_unlock_irqrestore(&desc->lock, flags);

    /*
     * Strictly no need to wake it up, but hung_task complains
     * when no hard interrupt wakes the thread up.
     */
    //喚醒中斷程序,就會阻塞在等來中斷髮生的位置
    if (new->thread)
        wake_up_process(new->thread);
    //建立檔案系統介面部分的內容
    register_irq_proc(irq, desc);
    new->dir = NULL;
    register_handler_proc(irq, new);
    free_cpumask_var(mask);

    return 0;

mismatch:
    if (!(new->flags & IRQF_PROBE_SHARED)) {
        pr_err("Flags mismatch irq %d. %08x (%s) vs. %08x (%s)\n",
               irq, new->flags, new->name, old->flags, old->name);
#ifdef CONFIG_DEBUG_SHIRQ
        dump_stack();
#endif
    }
    ret = -EBUSY;

out_mask:
    raw_spin_unlock_irqrestore(&desc->lock, flags);
    free_cpumask_var(mask);

out_thread:
    if (new->thread) {
        struct task_struct *t = new->thread;

        new->thread = NULL;
        kthread_stop(t);
        put_task_struct(t);
    }
out_mput:
    module_put(desc->owner);
    return ret;
}

這個函式的介面的整個執行流程就是,如果當前介面是要支援巢狀的則直接修改handler介面為irq_nested_primary_handler這個介面後面說明,如果不支援巢狀且沒有強制指定為不可以執行緒化的時候都是進行執行緒化中斷服務介面的通過irq_setup_forced_threading()同樣是後面詳細說明,執行緒化中斷服務介面後就會為這個中斷建立對應的執行緒並配置排程方式為FIFO和CPU親和性。之後就是中斷抽象資料的配置和中斷使能和檔案系統介面的問的建立在/proc目錄下。到這裡處理流程就算結束了,接下來在完善上面介面的詳細內容的探究。

irq_setup_forced_threading

static void irq_setup_forced_threading(struct irqaction *new)
{
    if (!force_irqthreads)
        return;
    if (new->flags & (IRQF_NO_THREAD | IRQF_PERCPU | IRQF_ONESHOT))
        return;

    new->flags |= IRQF_ONESHOT;

    if (!new->thread_fn) {
        set_bit(IRQTF_FORCED_THREAD, &new->thread_flags);
        new->thread_fn = new->handler;
        new->handler = irq_default_primary_handler;
    }
}

通過原始碼可以清楚的看明白,進行的操作就是檢查中斷是否能執行緒化,然後再判斷 thread_fn是否為未設定只有這個介面未設定才能強制執行緒化,原因就很簡單,不能丟棄驅動層註冊的介面內容要不然不就成BUG了嗎。所以執行緒化的方式就是給未設定thread_fn的中斷型別介面直接綁定了一個內建的執行緒irq_thread,然後在將原有介面的中斷服務函式作為執行緒的介面函式繫結到執行緒中,再把原來的handler替換為irq_default_primary_handler,這個介面和前面的irq_nested_primary_handler很相似,只是前面的介面會返回喚醒中斷而第二個則直接答應了一個警告資訊返回了NONE如下:

/*
 * Default primary interrupt handler for threaded interrupts. Is
 * assigned as primary handler when request_threaded_irq is called
 * with handler == NULL. Useful for oneshot interrupts.
 */
static irqreturn_t irq_default_primary_handler(int irq, void *dev_id)
{
    return IRQ_WAKE_THREAD;
}

/*
 * Primary handler for nested threaded interrupts. Should never be
 * called.
 */
static irqreturn_t irq_nested_primary_handler(int irq, void *dev_id)
{
    WARN(1, "Primary handler called for nested irq %d\n", irq);
    return IRQ_NONE;
}

所以到這裡就應該明白通過request_irq介面註冊的中斷服務介面在核心是會有可能被執行緒化的所以,這個介面註冊的中斷服務函式在指定不可以執行緒化的標誌後是執行在中斷上下文的,而其他情況是執行在程序上下文的。這也是我一開始想來研究硬中斷到底執行在哪個上下文的,因為併發的一些介面是需要區別中斷上下文還是程序上下文的。最後再來看一下核心將中斷服務程序執行緒化的執行緒是怎樣執行的。

static int irq_thread(void *data)
{
    struct callback_head on_exit_work;
    struct irqaction *action = data;
    struct irq_desc *desc = irq_to_desc(action->irq);
    irqreturn_t (*handler_fn)(struct irq_desc *desc,
            struct irqaction *action);

    if (force_irqthreads && test_bit(IRQTF_FORCED_THREAD,
                    &action->thread_flags))
        handler_fn = irq_forced_thread_fn;
    else
        handler_fn = irq_thread_fn;

    init_task_work(&on_exit_work, irq_thread_dtor);
    task_work_add(current, &on_exit_work, false);

    irq_thread_check_affinity(desc, action);

    while (!irq_wait_for_interrupt(action)) {
        irqreturn_t action_ret;

        irq_thread_check_affinity(desc, action);
        //執行中斷核心執行緒函式
        action_ret = handler_fn(desc, action);
        if (action_ret == IRQ_HANDLED)
            //增加threads_handled計數
            atomic_inc(&desc->threads_handled);
        //喚醒wait_for_threads等待佇列
        wake_threads_waitq(desc);
    }

    /*
     * This is the regular exit path. __free_irq() is stopping the
     * thread via kthread_stop() after calling
     * synchronize_irq(). So neither IRQTF_RUNTHREAD nor the
     * oneshot mask bit can be set. We cannot verify that as we
     * cannot touch the oneshot mask at this point anymore as
     * __setup_irq() might have given out currents thread_mask
     * again.
     */
    task_work_cancel(current, irq_thread_dtor);
    return 0;
}

這和執行緒接受一個struct irqaction作為引數,執行起來後會在irq_wait_for_interrupt()下阻塞掛起直到中斷上下文執行執行緒喚醒時才會繼續執行。就在中斷執行為完handler後判斷其返回值是否是IRQ_WAKE_THREAD,進而呼叫__irq_wake_thread(desc, action);喚醒中斷服務程式執行緒。而執行緒化則是將handler直接繫結到一個返回IRQ_WAKE_THREAD的介面上從而保證程序一定會被喚醒,以上就是我對於硬中斷的全部理解了。

參考部落格:

https://www.cnblogs.com/sky-heaven/p/11096462.html

https://www.cnblogs.com/arnoldlu/p/8659981.html