linux 中断控制器

SGI-software-generated-interrupt 软件触发中断,通常用于多核之间通信。

PPI-private-peripheral-interrupt 私有外设中断,比如本地cpu时钟中断

spi-shared-peripheral-interrupt 共用外设中断;

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_irq 这个irq在linux 内核中称为irq number或者interrupt line,这是linux 内核管理的虚拟中断号,并不是指硬件中断号,
目前linux内核引入了中断线程化,这个解决了什么问题呢-----------------
kernel 里面中断具有最高优先级,只要中断发生,内核就会去处理,挂起当前执行的上下文,等待挂起的中断以及软中断处理完后才恢复执行进程上下文
,中断上下文总是抢占进程上下文,中断上下文处理的程序有中断服务程序、softirq软中断、tasklet等。
这样可能发生softirq 和硬件中断交替进行,但是优先级较高的进程上下文没有执行。所以中断线程化目的就是把中断处理中一些繁重的任务作为内核线程处理。
实时进程的优先级比中断线程优先级高。这样优先级高的实时进程得到了处理,但是时钟中断不能这样完,时钟信号可是cpu的的心脏。
irq:表示申请的中断号,这里是指软件中断号,并不是硬件中断号。
handler:表示中断服务例程,指向primary handler 和老版本的request-irq的中断处理函数handler类似。中断发生时优先执行primary handler;
        如果primary handler 为null,且thread-fn不为空,那么执行默认primary handler ;irq-default-primary-handler。
thread_fn:中断线程化,此处传递的是NULL。NULL表示没有中断线程化。thread_fn如果该参数不为NULL,内核会为该irq创建一个内核线程,
当中断发生时,如果handler回调返回值是IRQ_WAKE_THREAD,内核将会激活中断线程,
在中断线程中,该回调函数将被调用,所以,该回调函数运行在进程上下文中,允许进行阻塞操作
irqflags;中断标志位。比较重要
devname:表示请求中断的设备的名称。
dev_id: 对应于request_irq()函数中所传递的第五个参数,可取任意值,
但必须唯一能够代表发出中断请求的设备,通常取描述该设备的结构体。 共享中断时所用。

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 (irq == IRQ_NOTCONNECTED)
        return -ENOTCONN;

    /*
     * 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).
     *
     * Also IRQF_COND_SUSPEND only makes sense for shared interrupts and
     * it cannot be set along with IRQF_NO_SUSPEND.
     对于共享中断来说必须传递一个参数 dev-id,用来识别是哪个设备产生中断
     
     */
    if (((irqflags & IRQF_SHARED) && !dev_id) ||
        (!(irqflags & IRQF_SHARED) && (irqflags & IRQF_COND_SUSPEND)) ||
        ((irqflags & IRQF_NO_SUSPEND) && (irqflags & IRQF_COND_SUSPEND)))
        return -EINVAL;
根据irq中断号获取中断描述符
    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;

    if (!handler) {handler 以及thread-fn不能同时为空
        if (!thread_fn)
            return -EINVAL;
            handler为空时 由irq-default-primary-handler返回唤醒中断线程
            IRQ-WAKE-THRAD 唤醒中断线程
        handler = irq_default_primary_handler;
    }

    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;

    chip_bus_lock(desc);
    注册中断函数
    retval = __setup_irq(irq, desc, action);
    chip_bus_sync_unlock(desc);

    if (retval) {
        kfree(action->secondary);
        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;
}

static int irq_setup_forced_threading(struct irqaction *new)
{
    if (!force_irqthreads)
        return 0;
    if (new->flags & (IRQF_NO_THREAD | IRQF_PERCPU | IRQF_ONESHOT))
        return 0;
上半部关中断运行,不会中断嵌套,设置irqf-onshot类型,
保证线程化后,threa-fn执行完后才打开中断源
    new->flags |= IRQF_ONESHOT;

    /*
     * Handle the case where we have a real primary handler and a
     * thread handler. We force thread them as well by creating a
     * secondary action.
     强制化把中断线程把愿挨的primary handler 处理函数
     弄到中断线程中运行 
     */
    if (new->handler != irq_default_primary_handler && new->thread_fn) {
        /* Allocate the secondary action */
        new->secondary = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
        if (!new->secondary)
            return -ENOMEM;
        new->secondary->handler = irq_forced_secondary_handler;
        new->secondary->thread_fn = new->thread_fn;
        new->secondary->dev_id = new->dev_id;
        new->secondary->irq = new->irq;
        new->secondary->name = new->name;
    }
    /* Deal with the primary handler 
    设置IRQTF_FORCED_THREAD 表示 强制中断线程化*/
    set_bit(IRQTF_FORCED_THREAD, &new->thread_flags);
    new->thread_fn = new->handler;
    new->handler = irq_default_primary_handler;
    return 0;
}
static int
setup_irq_thread(struct irqaction *new, unsigned int irq, bool secondary)
{
    struct task_struct *t;
    struct sched_param param = {
        .sched_priority = MAX_USER_RT_PRIO/2,
    };
没有嵌套的线程化中断创建一个内核线程,线程以irq 终端号 中断名称命名
    if (!secondary) {
        t = kthread_create(irq_thread, new, "irq/%d-%s", irq,
                   new->name);
    } else {
        t = kthread_create(irq_thread, new, "irq/%d-s-%s", irq,
                   new->name);
        param.sched_priority -= 1;
    }

    if (IS_ERR(t))
        return PTR_ERR(t);

    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.
     增加task-struct-usage 计数 
    防止释放内核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.
     */
    set_bit(IRQTF_AFFINITY, &new->thread_flags);
    return 0;
}


/*
 * 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;

    new->irq = irq;

    /*
     * Check whether the interrupt nests into another interrupt
     * thread.
     */查看 _IRQ_NESTED_THREAD标志位
    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.
         */
        new->handler = irq_nested_primary_handler;
    } else {如果中断没有设置irq-notthread标志说明可以中断线程化
        if (irq_settings_can_thread(desc)) {检查_IRQ_NOTHREAD 标志
            ret = irq_setup_forced_threading(new);
            if (ret)
                goto out_mput;
        }
    }

    /*
     * 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) {
        ret = setup_irq_thread(new, irq, false);
        if (ret)
            goto out_mput;
        if (new->secondary) {
            ret = setup_irq_thread(new->secondary, irq, true);
            if (ret)
                goto out_thread;
        }
    }

    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
     */
    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.
         *///共享中断必须有相同的属性(触发方式、oneshot、percpu等)
        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.
             *///只是遍历irqaction链表得到thread_mask,每个bit代表一个irqaction,添加的操作在后面
            thread_mask |= old->thread_mask;
            old_ptr = &old->next;//循环结束之后系统thread_mask中置1的bit位表示所有之前挂载过得共享中断的irqaction
            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().
     对于oneshot 中断来说,需要一个位图来管理共享中断,当所有的共享中断
     线程执行完毕,且thread_active 为0 才算完成中断处理
     */
    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);//找到第一个非0的bit作为新irqaction的bit标志位

    } 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) {--非共享中断,注册 /非共享中断的情况下需要设置触发方式,共享中断的情况下,所有共享中断的irq的触发方式等相同
        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,
                        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(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_warn("irq %d uses trigger mode %u; requested %u\n",
                irq, nmsk, omsk);
    }
把新的中断action描述符new 添加到中断desc链表中
    *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);
    }

    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);
    if (new->secondary)
        wake_up_process(new->secondary->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);
    }
    if (new->secondary && new->secondary->thread) {
        struct task_struct *t = new->secondary->thread;

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

nested irq

关于前面提到的nested irq,目前知道的信息如下:
1. 多个中断共享一个中断线,该中断线被认为是父中断,共享的中断被认为是子中断;
2. 中断在执行过程中被打断,发生了嵌套,打断的环境是进程上下文,也就是在threaded irq中(handle_nested_irq - Handle a nested irq from a irq thread );
3. handle_nested_irq 处理的irq的类型是IRQ_NESTED_THREAD,父中断在初始化中断时调用irq_set_nested_thread设置;
4. 对于IRQ_NESTED_THREAD类型的threaded handler,__setup_irq中不会为其创建单独的线程,子中断在父中断的线程上下文中运行。
5. 多用在GPIO driver中

中断发生时-cpu进入到irq模式,关闭对应硬件中断,保存返回地址,pc指针自动跳转到中断向量表的irq中;;

然后中断向量表中找到对应中断服务程序,然后通用的服务程序中找到用户注册的函数;

中断函数一般根据 oenshot 来屏蔽该中断源,因为不支持中断嵌套。

asmlinkage void call_do_IRQ(void); \
  __asm__( \
"\n" __ALIGN_STR"\n" \
 "common_interrupt:\n\t" \
 SAVE_ALL \
 "pushl $ret_from_intr\n\t" \  ------压入返回地址
SYMBOL_NAME_STR(call_do_IRQ)":\n\t" \  
"jmp "SYMBOL_NAME_STR(do_IRQ));   绝对跳转到处理函数
其SAVE_ALL就是 把中断前夕所有的寄存器值保存在堆栈中; 等待中断服务程序完成后恢复现场。

 

_visible unsigned int __irq_entry do_IRQ(struct pt_regs *regs)
{
    struct pt_regs *old_regs = set_irq_regs(regs);
    struct irq_desc * desc;
    /* high bit used in ret_from_ code  */
    unsigned vector = ~regs->orig_ax;

    /*
     * NB: Unlike exception entries, IRQ entries do not reliably
     * handle context tracking in the low-level entry code.  This is
     * because syscall entries execute briefly with IRQs on before
     * updating context tracking state, so we can take an IRQ from
     * kernel mode with CONTEXT_USER.  The low-level entry code only
     * updates the context if we came from user mode, so we won't
     * switch to CONTEXT_KERNEL.  We'll fix that once the syscall
     * code is cleaned up enough that we can cleanly defer enabling
     * IRQs.
     */

    entering_irq();

    /* entering_irq() tells RCU that we're not quiescent.  Check it. */
    RCU_LOCKDEP_WARN(!rcu_is_watching(), "IRQ failed to wake up RCU");

    desc = __this_cpu_read(vector_irq[vector]);

    if (!handle_irq(desc, regs)) {
        ack_APIC_irq();

        if (desc != VECTOR_RETRIGGERED) {
            pr_emerg_ratelimited("%s: %d.%d No irq handler for vector\n",
                         __func__, smp_processor_id(),
                         vector);
        } else {
            __this_cpu_write(vector_irq[vector], VECTOR_UNUSED);
        }
    }

    irq_exit();

    set_irq_regs(old_regs);
    return 1;
}

do_IRQ------desc = __this_cpu_read(vector_irq[vector]);
            handle_irq_event----
            
        irqreturn_t handle_irq_event_percpu(struct irq_desc *desc)
{
    irqreturn_t retval = IRQ_NONE;
    unsigned int flags = 0, irq = desc->irq_data.irq;
    struct irqaction *action;

    for_each_action_of_desc(desc, action) {
        irqreturn_t res;

        trace_irq_handler_entry(irq, action);
        res = action->handler(irq, action->dev_id);
        如果是irq-default-primary-handler 这是返回IRQ_WAKE_THREAD  唤醒中断线程 有的是执行中断服务程序
        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;
    }

    add_interrupt_randomness(irq, flags);

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

irqreturn_t handle_irq_event(struct irq_desc *desc)
{
    irqreturn_t ret;

    desc->istate &= ~IRQS_PENDING;---清楚挂起标志位
    irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS);--设置process 标志位
    raw_spin_unlock(&desc->lock);

    ret = handle_irq_event_percpu(desc);

    raw_spin_lock(&desc->lock);
    irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);
    return ret;
}

/*
For threaded interrupt handlers we expect the hard interrupt handler
part to mask the interrupt on the originating device. The interrupt
line itself is reenabled after the hard interrupt handler has
executed.

This requires access to the originating device from hard interrupt
context which is not always possible. There are devices which can only
be accessed via a bus (i2c, spi, ...). The bus access requires thread
context. For such devices we need to keep the interrupt line masked
until the threaded handler has executed.

Add a new flag IRQF_ONESHOT which allows drivers to request that the
interrupt is not unmasked after the hard interrupt context handler has
been executed and the thread has been woken. The interrupt line is
unmasked after the thread handler function has been executed.

Note that for now IRQF_ONESHOT cannot be used with IRQF_SHARED to
avoid complex accounting mechanisms.

For oneshot interrupts the primary handler simply returns
IRQ_WAKE_THREAD and does nothing else. A generic implementation
irq_oneshot_primary_handler() is provided to avoid useless copies all
over the place.
1. 在线程化中断处理函数中,hardirq在interrupt context中执行时是mask产生中断的外设的对应irq line的,完成硬中断操作后中断线被reenable; 
2. 然而中断上下文中并不是能够access所有的外设,比如某些设备必须要通过i2c、spi等bus才能access,而bus access需要在进程上下文中才能实现,
也就是说,需要将处理中断的代码放到进程上下文中,所以对于这些设备,在进程上下文中也要关闭中断; 3. IRQF_ONESHOT 应运而生,该flag允许中断线程运行期间对应的interrupt line一直是mask的。运行结束后unmask。 设置IRQF_ONESHOT 的情况下,在硬中断处理完毕后,仍然不能打开对应的中断(the irq line disabled),直到线程化handler处理完毕。
*/

handle_level_irq

该函数用于处理电平中断的流控操作。电平中断的特点是,只要设备的中断请求引脚(中断线)保持在预设的触发电平,中断就会一直被请求,所以,为了避免同一中断被重复响应,必须在处理中断前先把mask irq,然后ack irq,以便复位设备的中断请求引脚,响应完成后再unmask irq。实际的情况稍稍复杂一点,在mask和ack之后,还要判断IRQ_INPROGRESS标志位,如果该标志已经置位,则直接退出,不再做实质性的处理,IRQ_INPROGRESS标志在handle_irq_event的开始设置,在handle_irq_event结束时清除,如果监测到IRQ_INPROGRESS被置位,表明该irq正在被另一个CPU处理中,所以直接退出,对电平中断来说是正确的处理方法使用了中断线程(action->thread_fn)来响应中断的时候:通常mask_ack_irq只会清除中断控制器的pending状态,很多慢速设备(例如通过i2c或spi控制的设备)需要在中断线程中清除中断线的pending状态,但是未等到中断线程被调度执行的时候,handle_level_irq早就返回了,这时已经执行过unmask_irq,设备的中断线pending处于有效状态,中断控制器会再次发出中断请求,结果是设备的一次中断请求,产生了两次中断响应。要避免这种情况,最好的办法就是不要单独使用中断线程处理中断,而是要实现request_threaded_irq()的第二个参数irq_handler_t:handler,在handle回调中使用disable_irq()关闭该irq,然后在退出中断线程回调前再enable_irq()

handle_edge_irq

该函数用于处理边沿触发中断的流控操作。边沿触发中断的特点是,只有设备的中断请求引脚(中断线)的电平发生跳变时(由高变低或者有低变高),才会发出中断请求,因为跳变是一瞬间,而且不会像电平中断能保持住电平,所以处理不当就特别容易漏掉一次中断请求,为了避免这种情况,屏蔽中断的时间必须越短越好。内核的开发者们显然意识到这一点,在正是处理中断前,判断IRQ_PROGRESS标志没有被设置的情况下,只是ack irq,并没有mask irq,以便复位设备的中断请求引脚,在这之后的中断处理期间,另外的cpu可以再次响应同一个irq请求,如果IRQ_PROGRESS已经置位,表明另一个CPU正在处理该irq的上一次请求,这种情况下,他只是简单地设置IRQS_PENDING标志,然后mask_ack_irq后退出,中断请求交由原来的CPU继续处理。因为是mask_ack_irq,所以系统实际上只允许挂起一次中断,处理中断期间,另一次请求可能由另一个cpu响应后挂起,所以在处理完本次请求后还要判断IRQS_PENDING标志,如果被置位,当前cpu要接着处理被另一个cpu“委托”的请求。内核在这里设置了一个循环来处理这种情况,直到IRQS_PENDING标志无效为止,而且因为另一个cpu在响应并挂起irq时,会mask irq,所以在循环中要再次unmask irq,以便另一个cpu可以再次响应并挂起irq:

handle_nested_irq

该函数用于实现其中一种中断共享机制,当多个中断共享某一根中断线时,我们可以把这个中断线作为父中断,共享该中断的各个设备作为子中断,在父中断的中断线程中决定和分发响应哪个设备的请求,在得出真正发出请求的子设备后,调用handle_nested_irq来响应中断。所以,该函数是在进程上下文执行的,我们也无需扫描和执行irq_desc结构中的action链表。父中断在初始化时必须通过irq_set_nested_thread函数明确告知中断子系统:这些子中断属于线程嵌套中断类型,这样驱动程序在申请这些子中断时,内核不会为它们建立自己的中断线程,所有的子中断共享父中断的中断线程。

5.2  共享中断模式

共享中断模式充分利用了通用中断子系统的特性,经过前面的讨论,我们知道,irq对应的irq_desc结构中的action字段,本质上是一个链表,这给我们实现中断共享提供了必要的基础,只要我们以相同的irq编号多次申请中断服务,那么,action链表上就会有多个irqaction实例,当中断发生时,中断子系统会遍历action链表,逐个执行irqaction实例中的handler回调,根据handler回调的返回值不同,决定是否唤醒中断线程。需要注意到是,申请多个中断时,irq编号要保持一致,flag参数最好也能保持一致,并且都要设上IRQF_SHARED标志。在使用共享中断时,最好handler和thread_fn都要提供,在各自的中断处理回调handler中,做出以下处理:

 

中断线程嵌套模式

该模式与中断控制器级联模式大体相似,只不过级联模式时,父中断无需通过request_threaded_irq申请中断服务,而是直接更换了父中断的流控回调,在父中断的流控回调中实现子中断的二次分发。但是这在有些情况下会给我们带来不便,因为流控回调要获取子控制器的中断源,而流控回调运行在中断上下文中,对于那些子控制器需要通过慢速总线访问的设备,在中断上下文中访问显然不太合适,这时我们可以把子中断分发放在父中断的中断线程中进行,这就是我所说的所谓中断线程嵌套模式。下面是大概的实现过程:
对于父中断,具体的实现步骤如下:
  • 首先,父中断的irq编号可以从板级代码的预定义中获得,或者通过device的platform_data字段获得;
  • 使用父中断的irq编号,利用request_threaded_irq函数申请中断服务,需要提供thread_fn参数和dev_id参数;
  • dev_id参数要能够用于判别子控制器的中断来源;
  • 实现父中断的thread_fn函数,其中只需获得并计算子设备的irq编号,然后调用handle_nested_irq即可;

对于子设备,具体的实现步骤如下

  • 为设备内的中断控制器实现一个irq_chip结构,实现其中必要的回调,例如irq_mask,irq_unmask,irq_ack等;
  • 循环每一个子设备,做以下动作:
    • 为每个子设备,使用irq_alloc_descs函数申请irq编号;
    • 使用irq_set_chip_data设置必要的cookie数据;
    • 使用irq_set_chip_and_handler设置子控制器的irq_chip实例和子irq的流控处理程序,通常使用标准的流控函数,例如handle_edge_irq;
    • 使用irq_set_nested_thread函数,把子设备irq的线程嵌套特性打开;
  • 子设备的驱动程序使用自身申请到的irq编号,按照正常流程申请中断服务即可。

应为子设备irq的线程嵌套特性被打开,使用request_threaded_irq申请子设备的中断服务时,即是是提供了handler参数,中断子系统也不会使用它,同时也不会为它创建中断线程,子设备的thread_fn回调是在父中断的中断线程中,通过handle_nested_irq调用的,也就是说,尽管子中断有自己独立的irq编号,但是它们没有独立的中断线程,只是共享了父中断的中断服务线程。

  • 判断中断是否来自本设备;
  • 如果不是来自本设备:
    • 直接返回IRQ_NONE;
  • 如果是来自本设备:
    • 关闭irq;
    • 返回IRQ_WAKE_THREAD,唤醒中断线程,thread_fn将会被执行;

IRQS_PENDING----在Pending状态中,GIC会关闭对该中断源的响应,在此期间,如果该中断源上有新的中断到来,所有连接GIC的CPU都无法收到

IRQD_IRQ_INPROGRESS----  cpu 处理中断中

IRQF_ONESHOT  -----



/*
 * Are we doing bottom half or hardware interrupt processing?
 *
 * in_irq()       - We're in (hard) IRQ context
 * in_softirq()   - We have BH disabled, or are processing softirqs
 * in_interrupt() - We're in NMI,IRQ,SoftIRQ context or have BH disabled
 * in_serving_softirq() - We're in softirq context
 * in_nmi()       - We're in NMI context
 * in_task()      - We're in task context
 *
 * Note: due to the BH disabled confusion: in_softirq(),in_interrupt() really
 *       should not be used in new code.
 */
#define in_irq()        (hardirq_count())
#define in_softirq()        (softirq_count())
#define in_interrupt()        (irq_count())
#define in_serving_softirq()    (softirq_count() & SOFTIRQ_OFFSET)
#define in_nmi()        (preempt_count() & NMI_MASK)
#define in_task()        (!(preempt_count() & \
                   (NMI_MASK | HARDIRQ_MASK | SOFTIRQ_OFFSET)))
posted @ 2020-03-11 10:48  codestacklinuxer  阅读(599)  评论(0编辑  收藏  举报