linux中断注册及中断线程化

linux中高优先级任务从runable到真正被调度的时间是不确定的,主要有两方面因素,
1,内核中总有些代码持有自旋锁资源(cpu一直等待),或者有些代码段会调用preempt_disable显示的禁止抢占。
2,中断上下文具有更高的优先级(包括hw_irq_handler、softirq、tasklet等)可以抢占进程上下文。

一般外设中断被分为top half和bottom half(softirq、tasklet),而bottom half的优先级也是高于进程的,所以为了进一步提高系统的响应实时性,引入了线程irq,即将外设中断处理线程化,使像内核态线程、用户态线程一样竞争cpu资源。

linux设备驱动中,申请irq的函数

int devm_request_irq(struct device *dev, unsigned int irq, irq_handler_t handler,
unsigned long irqflags, const char *devname, void *dev_id);
以及,
int devm_request_threaded_irq(struct device *dev, unsigned int irq,
irq_handler_t handler, irq_handler_t thread_fn,
unsigned long irqflags, const char *devname,
void *dev_id);
其实devm_request_irq只是devm_request_threaded_irq的wapper,

static inline int __must_check
devm_request_irq(struct device *dev, unsigned int irq, irq_handler_t handler,
unsigned long irqflags, const char *devname, void *dev_id)
{
return devm_request_threaded_irq(dev, irq, handler, NULL, irqflags,
devname, dev_id);
}

参数thread_fn=NULL,表示非线程化irq。

前缀devm_开头的API申请的是内核“managed”资源,作用是一般不需要在出错处理和remove()接口里再显示释放。

//devres.c

/**
 *  devm_request_threaded_irq - allocate an interrupt line for a managed device
 *  @dev: device to request interrupt for
 *  @irq: Interrupt line to allocate
 *  @handler: Function to be called when the IRQ occurs
 *  @thread_fn: function to be called in a threaded interrupt context. NULL
 *          for devices which handle everything in @handler
 *  @irqflags: Interrupt type flags
 *  @devname: An ascii name for the claiming device
 *  @dev_id: A cookie passed back to the handler function
 *
 *  Except for the extra @dev argument, this function takes the
 *  same arguments and performs the same function as
 *  request_threaded_irq().  IRQs requested with this function will be
 *  automatically freed on driver detach.
 *
 *  If an IRQ allocated with this function needs to be freed
 *  separately, devm_free_irq() must be used.
 */
int devm_request_threaded_irq(struct device *dev, unsigned int irq,
                  irq_handler_t handler, irq_handler_t thread_fn,
                  unsigned long irqflags, const char *devname,
                  void *dev_id)
{
    struct irq_devres *dr;
    int rc;

    dr = devres_alloc(devm_irq_release, sizeof(struct irq_devres),
              GFP_KERNEL);
    if (!dr)
        return -ENOMEM;

    rc = request_threaded_irq(irq, handler, thread_fn, irqflags, devname,
                  dev_id);
    if (rc) {
        devres_free(dr);
        return rc;
    }

    dr->irq = irq;
    dr->dev_id = dev_id;
    devres_add(dev, dr);

    return 0;
}

request_threaded_irq是真正的主流程

//manage.c

/**
 *  request_threaded_irq - allocate an interrupt line
 *  @irq: Interrupt line to allocate
 *  @handler: Function to be called when the IRQ occurs.
 *        Primary handler for threaded interrupts
 *        If NULL and thread_fn != NULL the default
 *        primary handler is installed
 *  @thread_fn: Function called from the irq handler thread
 *          If NULL, no irq thread is created
 *  @irqflags: Interrupt type flags
 *  @devname: An ascii name for the claiming device
 *  @dev_id: A cookie passed back to the handler function
 *
 *  This call allocates interrupt resources and enables the
 *  interrupt line and IRQ handling. From the point this
 *  call is made your handler function may be invoked. Since
 *  your handler function must clear any interrupt the board
 *  raises, you must take care both to initialise your hardware
 *  and to set up the interrupt handler in the right order.
 *
 *  If you want to set up a threaded irq handler for your device
 *  then you need to supply @handler and @thread_fn. @handler is
 *  still called in hard interrupt context and has to check
 *  whether the interrupt originates from the device. If yes it
 *  needs to disable the interrupt on the device and return
 *  IRQ_WAKE_THREAD which will wake up the handler thread and run
 *  @thread_fn. This split handler design is necessary to support
 *  shared interrupts.
 *
 *  Dev_id must be globally unique. Normally the address of the
 *  device data structure is used as the cookie. Since the handler
 *  receives this value it makes sense to use it.
 *
 *  If your interrupt is shared you must pass a non NULL dev_id
 *  as this is required when freeing the interrupt.
 *
 *  Flags:
 *
 *  IRQF_SHARED     Interrupt is shared
 *  IRQF_TRIGGER_*      Specify active edge(s) or level
 *
 */
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).
     *
     * Also IRQF_COND_SUSPEND only makes sense for shared interrupts and
     * it cannot be set along with IRQF_NO_SUSPEND.
     */
    if (((irqflags & IRQF_SHARED) && !dev_id) ||
        (!(irqflags & IRQF_SHARED) && (irqflags & IRQF_COND_SUSPEND)) ||
        ((irqflags & IRQF_NO_SUSPEND) && (irqflags & IRQF_COND_SUSPEND)))
        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;

    if (!handler) {
        if (!thread_fn)
            return -EINVAL;
        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;
}

从该函数注释可以看出,如果不需要建立线程化中断服务函数,handler传入正常的中断服务函数,thread_fn传NULL,handler的处理还是在硬中断上下文空间。若希望建立线程化中断服务函数则handler和thread_fn都要提供,并且handler的return是IRQ_WAKE_THREAD,用以唤醒thread_fn,如果没提供handler该函数调用 irq_default_primary_handler其只return IRQ_WAKE_THREAD,用于唤醒thread_fn。若handler,thread_fn均为NULL,return -EINVAL。

你可能感兴趣的:(linux中断注册及中断线程化)