Linux ARM中断后的处理(5)

1. 中断进入自定义函数 

在中断发生后,经历ARM通用的处理阶段,到达irq_handler宏,转入C语言阶段。

//arch/arm/kernel/entry-armv.S
/*
*注释:
*宏CONFIG_MULTI_IRQ_HANDLER在.config中有定义,会将handle_arch_irq里的值赋值给pc去执行。
*给handle_arch_irq请看后面的C语言阶段分析
*/
/*
* Interrupt handling.
*/
        .macro irq_handler
#ifdef CONFIG_MULTI_IRQ_HANDLER
        ldr r1, =handle_arch_irq
        mov r0, sp
        badr lr, 9997f
        ldr pc, [r1] //进入C语言阶段的中断处理
#else
        arch_irq_handler_default
#endif
9997:
        .endm


//arch/arm/kernel/entry-armv.S
#ifdef CONFIG_MULTI_IRQ_HANDLER
        .globl handle_arch_irq
handle_arch_irq:
        .space 4
#endif

2. handle_arch_irq

handle_arch_irq赋值在/drivers/irqchip/irq-vic.c的vic_register函数中调用set_handle_irq进行赋值

//driver/irqchip/irq-vic.c
/**
 * vic_register() - Register a VIC.
 * @base: The base address of the VIC.
 * @parent_irq: The parent IRQ if cascaded, else 0.
 * @irq: The base IRQ for the VIC.
 * @valid_sources: bitmask of valid interrupts
 * @resume_sources: bitmask of interrupts allowed for resume sources.
 * @node: The device tree node associated with the VIC.
 *
 * Register the VIC with the system device tree so that it can be notified
 * of suspend and resume requests and ensure that the correct actions are
 * taken to re-instate the settings on resume.
 *
 * This also configures the IRQ domain for the VIC.
 */
// __vic_init(regs, 0, 0, interrupt_mask, wakeup_mask, node);
//vic_register(base, 0, irq_start, vic_sources, 0, node);
static void __init vic_register(void __iomem *base, unsigned int parent_irq,
                                unsigned int irq,
                                u32 valid_sources, u32 resume_sources,
                                struct device_node *node)
{
        struct vic_device *v; 
        int i;

        if (vic_id >= ARRAY_SIZE(vic_devices)) {
                printk(KERN_ERR "%s: too few VICs, increase CONFIG_ARM_VIC_NR\n", __func__);
                return;
        }

         v = &vic_devices[vic_id];
        v->base = base;
        v->valid_sources = valid_sources;
        v->resume_sources = resume_sources;
        set_handle_irq(vic_handle_irq);  //此处被调用
        vic_id++;

        if (parent_irq) {
                irq_set_chained_handler_and_data(parent_irq,
                                                 vic_handle_irq_cascaded, v);
        }

        v->domain = irq_domain_add_simple(node, fls(valid_sources), irq,
                                          &vic_irqdomain_ops, v);
        /* create an IRQ mapping for each valid IRQ */
        for (i = 0; i < fls(valid_sources); i++) {

                if (valid_sources & (1 << i)) {
                        irq_create_mapping(v->domain, i);
                }
        }
        /* If no base IRQ was passed, figure out our allocated base */
        if (irq)
                v->irq = irq;
        else
                v->irq = irq_find_mapping(v->domain, 0);
}


//arch/arm/kernel/irq.c
#ifdef CONFIG_MULTI_IRQ_HANDLER
void __init set_handle_irq(void (*handle_irq)(struct pt_regs *)) 
{
        if (handle_arch_irq)
                return;

        handle_arch_irq = handle_irq;
}
#endif

3. vic_handle_irq

//drivers/irqchip/irq-vic.c
/*
 * Keep iterating over all registered VIC's until there are no pending
 * interrupts.
 */
static void __exception_irq_entry vic_handle_irq(struct pt_regs *regs)
{
        int i, handled;

        do {
                for (i = 0, handled = 0; i < vic_id; ++i)
                        handled |= handle_one_vic(&vic_devices[i], regs);
        } while (handled);
}

4. handle_one_vic

//drivers/irqchip/irq-vic.c
/*
 * Handle each interrupt in a single VIC.  Returns non-zero if we've
 * handled at least one interrupt.  This reads the status register
 * before handling each interrupt, which is necessary given that
 * handle_IRQ may briefly re-enable interrupts for soft IRQ handling.
 */
static int handle_one_vic(struct vic_device *vic, struct pt_regs *regs)
{
        u32 stat, irq;
        int handled = 0;

        while ((stat = readl_relaxed(vic->base + VIC_IRQ_STATUS))) {
                irq = ffs(stat) - 1;
                handle_domain_irq(vic->domain, irq, regs);
                handled = 1;
        }

        return handled;
}


//具体代码参看arch/arm/include/asm/io.h 
/*
 *  Memory access primitives
 *  ------------------------
 *
 * These perform PCI memory accesses via an ioremap region.  They don't
 * take an address as such, but a cookie.
 *
 * Again, this are defined to perform little endian accesses.  See the
 * IO port primitives for more information.
 */
#ifndef readl
#define readb_relaxed(c) ({ u8  __r = __raw_readb(c); __r; })
#define readw_relaxed(c) ({ u16 __r = le16_to_cpu((__force __le16) \
                                        __raw_readw(c)); __r; })
#define readl_relaxed(c) ({ u32 __r = le32_to_cpu((__force __le32) \
                                        __raw_readl(c)); __r; })

#define writeb_relaxed(v,c)     __raw_writeb(v,c)
#define writew_relaxed(v,c)     __raw_writew((__force u16) cpu_to_le16(v),c)
#define writel_relaxed(v,c)     __raw_writel((__force u32) cpu_to_le32(v),c)

#define readb(c)                ({ u8  __v = readb_relaxed(c); __iormb(); __v; })
#define readw(c)                ({ u16 __v = readw_relaxed(c); __iormb(); __v; })
#define readl(c)                ({ u32 __v = readl_relaxed(c); __iormb(); __v; })

#define writeb(v,c)             ({ __iowmb(); writeb_relaxed(v,c); })
#define writew(v,c)             ({ __iowmb(); writew_relaxed(v,c); })
#define writel(v,c)             ({ __iowmb(); writel_relaxed(v,c); })

#define readsb(p,d,l)           __raw_readsb(p,d,l)
#define readsw(p,d,l)           __raw_readsw(p,d,l)
#define readsl(p,d,l)           __raw_readsl(p,d,l)

#define writesb(p,d,l)          __raw_writesb(p,d,l)
#define writesw(p,d,l)          __raw_writesw(p,d,l)
#define writesl(p,d,l)          __raw_writesl(p,d,l)

5. handle_domain_irq

//include/linux/irqdesc.h
#ifdef CONFIG_HANDLE_DOMAIN_IRQ
/*
 * Convert a HW interrupt number to a logical one using a IRQ domain,
 * and handle the result interrupt number. Return -EINVAL if
 * conversion failed. Providing a NULL domain indicates that the
 * conversion has already been done.
 */
int __handle_domain_irq(struct irq_domain *domain, unsigned int hwirq,
                        bool lookup, struct pt_regs *regs);

static inline int handle_domain_irq(struct irq_domain *domain,
                                    unsigned int hwirq, struct pt_regs *regs)
{
        return __handle_domain_irq(domain, hwirq, true, regs);
}
#endif

6. __handle_domain_irq

//kernel/irq/irqdesc.c
#ifdef CONFIG_HANDLE_DOMAIN_IRQ
/**
 * __handle_domain_irq - Invoke the handler for a HW irq belonging to a domain
 * @domain:     The domain where to perform the lookup
 * @hwirq:      The HW irq number to convert to a logical one
 * @lookup:     Whether to perform the domain lookup or not
 * @regs:       Register file coming from the low-level handling code
 *
 * Returns:     0 on success, or -EINVAL if conversion has failed
 */
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;

        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 {
                generic_handle_irq(irq);
        }

        irq_exit();
        set_irq_regs(old_regs);
        return ret;
}
#endif

7. generic_handle_irq

//kernel/irq/irqdesc.c
/**
 * generic_handle_irq - Invoke the handler for a particular irq
 * @irq:        The irq number to handle
 *
 */
int generic_handle_irq(unsigned int irq)
{
        struct irq_desc *desc = irq_to_desc(irq);

        if (!desc)
                return -EINVAL;
        generic_handle_irq_desc(desc);
        return 0;
}
EXPORT_SYMBOL_GPL(generic_handle_irq);

8. generic_handle_irq_desc

//include/linux/irqdesc.h
/*
 * Architectures call this to let the generic IRQ layer
 * handle an interrupt.
 */
static inline void generic_handle_irq_desc(struct irq_desc *desc)
{
        /*
         *handle_irq在struct domain的struct domain_ops的map函数中调用 
         *irq_set_chip_and_handler赋值为handle_level_irq
         */
        desc->handle_irq(desc);  
}

int generic_handle_irq(unsigned int irq);

9. handle_level_irq

//kernel/irq/chip.c
/**
 *      handle_level_irq - Level type irq handler
 *      @desc:  the interrupt description structure for this irq
 *
 *      Level type interrupts are active as long as the hardware line has
 *      the active level. This may require to mask the interrupt and unmask
 *      it after the associated handler has acknowledged the device, so the
 *      interrupt line is back to inactive.
 */
void handle_level_irq(struct irq_desc *desc)
{
        raw_spin_lock(&desc->lock);
        mask_ack_irq(desc);

        if (!irq_may_run(desc))
                goto out_unlock;

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

        /*
         * If its disabled or no action available
         * keep it masked and get out of here
         */
        if (unlikely(!desc->action || irqd_irq_disabled(&desc->irq_data))) {
                desc->istate |= IRQS_PENDING;
                goto out_unlock;
        }

        handle_irq_event(desc);

        cond_unmask_irq(desc);

out_unlock:
        raw_spin_unlock(&desc->lock);
}
EXPORT_SYMBOL_GPL(handle_level_irq);



//kernel/irq/chip.c
static inline void mask_ack_irq(struct irq_desc *desc)
{
         /*desc->irq_data.chip在struct domain的struct domain_ops的map函数中调用 
          *irq_set_chip_and_handler赋值为vic_chip.
          */
        if (desc->irq_data.chip->irq_mask_ack)
                desc->irq_data.chip->irq_mask_ack(&desc->irq_data);
        else {
                desc->irq_data.chip->irq_mask(&desc->irq_data);
                if (desc->irq_data.chip->irq_ack)
                        desc->irq_data.chip->irq_ack(&desc->irq_data);
        }
        irq_state_set_masked(desc);
}


//include/linux/irq.h
/**
 * struct irq_chip - hardware interrupt chip descriptor
 *
 * @name:               name for /proc/interrupts
 * @irq_startup:        start up the interrupt (defaults to ->enable if NULL)
 * @irq_shutdown:       shut down the interrupt (defaults to ->disable if NULL)
 * @irq_enable:         enable the interrupt (defaults to chip->unmask if NULL)
 * @irq_disable:        disable the interrupt
 * @irq_ack:            start of a new interrupt
 * @irq_mask:           mask an interrupt source
 * @irq_mask_ack:       ack and mask an interrupt source
 * @irq_unmask:         unmask an interrupt source
 * @irq_eoi:            end of interrupt
 * @irq_set_affinity:   set the CPU affinity on SMP machines
 * @irq_retrigger:      resend an IRQ to the CPU
 * @irq_set_type:       set the flow type (IRQ_TYPE_LEVEL/etc.) of an IRQ
 * @irq_set_wake:       enable/disable power-management wake-on of an IRQ
 * @irq_bus_lock:       function to lock access to slow bus (i2c) chips
 * @irq_bus_sync_unlock:function to sync and unlock slow bus (i2c) chips
 * @irq_cpu_online:     configure an interrupt source for a secondary CPU
 * @irq_cpu_offline:    un-configure an interrupt source for a secondary CPU
 * @irq_suspend:        function called from core code on suspend once per
 *                      chip, when one or more interrupts are installed
 * @irq_resume:         function called from core code on resume once per chip,
 *                      when one ore more interrupts are installed
 * @irq_pm_shutdown:    function called from core code on shutdown once per chip
 * @irq_calc_mask:      Optional function to set irq_data.mask for special cases
 * @irq_print_chip:     optional to print special chip info in show_interrupts
 * @irq_request_resources:      optional to request resources before calling
 *                              any other callback related to this irq
 * @irq_release_resources:      optional to release resources acquired with
 *                              irq_request_resources
 * @irq_compose_msi_msg:        optional to compose message content for MSI
 * @irq_write_msi_msg:  optional to write message content for MSI
 * @irq_get_irqchip_state:      return the internal state of an interrupt
 * @irq_set_irqchip_state:      set the internal state of a interrupt
 * @irq_set_vcpu_affinity:      optional to target a vCPU in a virtual machine
 * @flags:              chip specific flags
 */
struct irq_chip {
        const char      *name;
        unsigned int    (*irq_startup)(struct irq_data *data);
        void            (*irq_shutdown)(struct irq_data *data);
        void            (*irq_enable)(struct irq_data *data);
        void            (*irq_disable)(struct irq_data *data);

        void            (*irq_ack)(struct irq_data *data);
        void            (*irq_mask)(struct irq_data *data);
        void            (*irq_mask_ack)(struct irq_data *data);
        void            (*irq_unmask)(struct irq_data *data);
        void            (*irq_eoi)(struct irq_data *data);

        int             (*irq_set_affinity)(struct irq_data *data, const struct cpumask *dest, bool force);
        int             (*irq_retrigger)(struct irq_data *data);
        int             (*irq_set_type)(struct irq_data *data, unsigned int flow_type);
        int             (*irq_set_wake)(struct irq_data *data, unsigned int on);

        void            (*irq_bus_lock)(struct irq_data *data);
        void            (*irq_bus_sync_unlock)(struct irq_data *data);

        void            (*irq_cpu_online)(struct irq_data *data);
        void            (*irq_cpu_offline)(struct irq_data *data);
       void            (*irq_suspend)(struct irq_data *data);
        void            (*irq_resume)(struct irq_data *data);
        void            (*irq_pm_shutdown)(struct irq_data *data);

        void            (*irq_calc_mask)(struct irq_data *data);

        void            (*irq_print_chip)(struct irq_data *data, struct seq_file *p);
        int             (*irq_request_resources)(struct irq_data *data);
        void            (*irq_release_resources)(struct irq_data *data);

        void            (*irq_compose_msi_msg)(struct irq_data *data, struct msi_msg *msg);
        void            (*irq_write_msi_msg)(struct irq_data *data, struct msi_msg *msg);

        int             (*irq_get_irqchip_state)(struct irq_data *data, enum irqchip_irq_state which, bool *state);
        int             (*irq_set_irqchip_state)(struct irq_data *data, enum irqchip_irq_state which, bool state);

        int             (*irq_set_vcpu_affinity)(struct irq_data *data, void *vcpu_info);

        unsigned long   flags;
};


//drivers/irqchip/irq-vic.c  在
static struct irq_chip vic_chip = { 
        .name           = "VIC",
        .irq_ack        = vic_ack_irq,
        .irq_mask       = vic_mask_irq,
        .irq_unmask     = vic_unmask_irq,
        .irq_set_wake   = vic_set_wake,
};



static void vic_ack_irq(struct irq_data *d) 
{
        void __iomem *base = irq_data_get_irq_chip_data(d);
        unsigned int irq = d->hwirq;
        writel(1 << irq, base + VIC_INT_ENABLE_CLEAR);
        /* moreover, clear the soft-triggered, in case it was the reason */
        writel(1 << irq, base + VIC_INT_SOFT_CLEAR);
}

static void vic_mask_irq(struct irq_data *d) 
{
        void __iomem *base = irq_data_get_irq_chip_data(d);
        unsigned int irq = d->hwirq;
        writel(1 << irq, base + VIC_INT_ENABLE_CLEAR);
}

static void vic_unmask_irq(struct irq_data *d) 
{
        void __iomem *base = irq_data_get_irq_chip_data(d);
        unsigned int irq = d->hwirq;
        writel(1 << irq, base + VIC_INT_ENABLE);
}

#if defined(CONFIG_PM)
static struct vic_device *vic_from_irq(unsigned int irq)
{
        struct vic_device *v = vic_devices;
        unsigned int base_irq = irq & ~31;
        int id;

        for (id = 0; id < vic_id; id++, v++) {
                if (v->irq == base_irq)
                        return v;
        }

        return NULL;
}
static int vic_set_wake(struct irq_data *d, unsigned int on)
{
        struct vic_device *v = vic_from_irq(d->irq);
        unsigned int off = d->hwirq;
        u32 bit = 1 << off;

        if (!v)
                return -EINVAL;

        if (!(bit & v->resume_sources))
                return -EINVAL;

        if (on)
                v->resume_irqs |= bit;
        else
                v->resume_irqs &= ~bit;

        return 0;
}
#else
#define vic_set_wake NULL
#endif /* CONFIG_PM */


//include/linux/irq.h
static inline void *irq_data_get_irq_chip_data(struct irq_data *d) 
{
        /*
         *d->chip_data在struct domain的struct domain_ops的map函数中调用 
         *irq_set_chip_data赋值为中断控制器寄存器的虚拟地址
         *
         */
        return d->chip_data;
}

10. handle_irq_event

//kernel/irq/handle.c
irqreturn_t handle_irq_event(struct irq_desc *desc)
{
        irqreturn_t ret;

        desc->istate &= ~IRQS_PENDING;
        irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS);
        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;
}

11. handle_irq_event_percpu:

//kernel/irq/handle.c
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 = desc->action;

        /* action might have become NULL since we dropped the lock */
        while (action) {
                irqreturn_t res;

                trace_irq_handler_entry(irq, action);
                /*
                 *action->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;
        }

        add_interrupt_randomness(irq, flags);

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值