Chinaunix首页 | 论坛 | 博客
  • 博客访问: 41529
  • 博文数量: 21
  • 博客积分: 840
  • 博客等级: 准尉
  • 技术积分: 225
  • 用 户 组: 普通用户
  • 注册时间: 2010-05-24 00:24
文章分类
文章存档

2010年(21)

我的朋友

分类: LINUX

2010-06-08 21:13:22

关于MX27中断系统:

这个文档没有涉及到中断向量初始化的内容,而是这之后的纯软件上的架构分析,至于中断向量初始化及系统从用户模式或系统模式下进入中断的一系列流程将会在另外一篇文档中讲述,如果有可能的话我会将这两部分合成一个文档。

初始化流程:
首先,在arch/arm/mach-mx27/mx27mdk27v0.c中有关于machine的描述:
/*
 * The following uses standard kernel macros define in arch.h in order to
 * initialize __mach_desc_MX27ADS data structure.
 */

/* *INDENT-OFF* */
MACHINE_START(MX27_MDK27V0, "Morninghan i.MX27 MDK27V0")
        /* maintainer: Freescale Semiconductor, Inc. */
#ifdef CONFIG_SERIAL_8250_CONSOLE
        .phys_io = CS5_BASE_ADDR,
        .io_pg_offst = ((CS5_BASE_ADDR_VIRT) >> 18) & 0xfffc,
#else
        .phys_io = AIPI_BASE_ADDR,
        .io_pg_offst = ((AIPI_BASE_ADDR_VIRT) >> 18) & 0xfffc,
#endif
        .boot_params = PHYS_OFFSET + 0x100,
        .fixup = fixup_mxc_board,
        .map_io = mxc_map_io,
        .init_irq = mxc_init_irq,
        .init_machine = mxc_board_init,
        .timer = &mxc_timer,
MACHINE_END
其中有一个mxc_init_irq函数,地址赋给init_irq指针,这个函数就是在异常向量表建立好之后初始化中断处理的时候调用的。我们来看下init_irq指针的去向:
在init/main.c的大名鼎鼎的start_kernel函数中有调用setup_arch(&command_line)(函数原型在arch/arm/kernel/setup.c中),然后setup_arch中有这么一行init_arch_irq = mdesc->init_irq;而这个init_arch_irq为一个全局的指向函数的指针变量,然后在start_kernel函数后面有调用init_IRQ,这是在trap_init(),rcu_init()之后,没错,这个trap_init就是进行传说中的代码(异常向量)大挪移的,回到init_IRQ函数中,它最后有一行init_arch_irq,看到没,终于调用了吧。总结一下:
start_kernel-->setup_arch(指针赋值)返回
-->init_IRQ-->init_arch_irq(即是mxc_init_irq)
函数原型(在arch/arm/plat-mxc/irq.c中):
/*!
 * This function initializes the AVIC hardware and disables all the
 * interrupts. It registers the interrupt enable and disable functions
 * to the kernel for each interrupt source.
 */

void __init mxc_init_irq(void)
{
    int i;
    u32 reg;

    /* put the AVIC into the reset value with
     * all interrupts disabled
     */

    __raw_writel(0, AVIC_INTCNTL);
    __raw_writel(0x1f, AVIC_NIMASK);

    /* disable all interrupts */
    __raw_writel(0, AVIC_INTENABLEH);
    __raw_writel(0, AVIC_INTENABLEL);

    /* all IRQ no FIQ */
    __raw_writel(0, AVIC_INTTYPEH);
    __raw_writel(0, AVIC_INTTYPEL);
    for (i = 0; i < MXC_MAX_INT_LINES; i++) {
#ifdef EDIO_BASE_ADDR
        if (irq_to_edio(i) != -1) {
            mxc_irq_set_edio(i, 0, 0, 0);
            set_irq_chip(i, &mxc_edio_chip);
        } else
#endif
        {
            set_irq_chip(i, &mxc_avic_chip);
        }
        set_irq_handler(i, do_level_IRQ);
        set_irq_flags(i, IRQF_VALID);
    }

    /* Set WDOG2's interrupt the highest priority level (bit 28-31) */
    reg = __raw_readl(AVIC_NIPRIORITY6);
    reg |= (0xF << 28);
    __raw_writel(reg, AVIC_NIPRIORITY6);

    printk(KERN_INFO "MXC IRQ initialized\n");
}

其实在解释这个函数之前有必要先贴一些关于IRQ的数据结构出来:
在include/linux/irq.h中有
/**
 * struct irq_chip - hardware interrupt chip descriptor
 *
 * @name:        name for /proc/interrupts
 * @startup:        start up the interrupt (defaults to ->enable if NULL)
 * @shutdown:        shut down the interrupt (defaults to ->disable if NULL)
 * @enable:        enable the interrupt (defaults to chip->unmask if NULL)
 * @disable:        disable the interrupt (defaults to chip->mask if NULL)
 * @ack:        start of a new interrupt
 * @mask:        mask an interrupt source
 * @mask_ack:        ack and mask an interrupt source
 * @unmask:        unmask an interrupt source
 * @eoi:        end of interrupt - chip level
 * @end:        end of interrupt - flow level
 * @set_affinity:    set the CPU affinity on SMP machines
 * @retrigger:        resend an IRQ to the CPU
 * @set_type:        set the flow type (IRQ_TYPE_LEVEL/etc.) of an IRQ
 * @set_wake:        enable/disable power-management wake-on of an IRQ
 *
 * @release:        release function solely used by UML
 * @typename:        obsoleted by name, kept as migration helper
 */

struct irq_chip {
    const char    *name;
    unsigned int    (*startup)(unsigned int irq);
    void        (*shutdown)(unsigned int irq);
    void        (*enable)(unsigned int irq);
    void        (*disable)(unsigned int irq);

    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);

    void        (*end)(unsigned int irq);
    void        (*set_affinity)(unsigned int irq, cpumask_t dest);
    int        (*retrigger)(unsigned int irq);
    int        (*set_type)(unsigned int irq, unsigned int flow_type);
    int        (*set_wake)(unsigned int irq, unsigned int on);

    /* Currently used only by UML, might disappear one day.*/
#ifdef CONFIG_IRQ_RELEASE_METHOD
    void        (*release)(unsigned int irq, void *dev_id);
#endif
    /*
     * For compatibility, ->typename is copied into ->name.
     * Will disappear.
     */

    const char    *typename;
};
这个chip的描述我的理解是从PC时代延用下来的,当时是抽象出来描述8255这种扩展中断芯片的,而到了我们现在用的各种ARM核的SOC中的话就演变成对一个中断controller的描述,比如在MX27系统中就有
    static struct irqchip mxc_avic_chip = {
        .ack = mxc_mask_irq,
        .mask = mxc_mask_irq,
        .unmask = mxc_unmask_irq,
    };
    这是与ARM端对接的一个CHIP描述,当然还有
    static struct irq_chip gpio_irq_chip = {
        .ack = gpio_ack_irq,
        .mask = gpio_mask_irq,
        .unmask = gpio_unmask_irq,
        .set_type = gpio_set_irq_type,
    };
    这是一个与INT_GPIO中断对接的一个扩展中断CHIP的描述,这些在后面都会有讲述。
从上面的两个例子也可以看到,这个CHIP结构描述虽然抽象出有很多可用的方法,但我们比较常用的也就只有ack,mask,mask_ack和set_type等等,看到这里,也许你会有这样的疑问,为什么mxc_avic_chip中的ack方法用mask来实现呢?这个问题下面会有讲述,还有,为什么mxc_avic_chip没有set_type方法?这个嘛,因为这个CHIP所管的中断只支持电平触发,就这么简单。
/**
 * struct irq_desc - interrupt descriptor
 *
 * @handle_irq:        highlevel irq-events handler [if NULL, __do_IRQ()]
 * @chip:        low level interrupt hardware access
 * @handler_data:    per-IRQ data for the irq_chip methods
 * @chip_data:        platform-specific per-chip private data for the chip
 *            methods, to allow shared chip implementations
 * @action:        the irq action chain
 * @status:        status information
 * @depth:        disable-depth, for nested irq_disable() calls
 * @wake_depth:        enable depth, for multiple set_irq_wake() callers
 * @irq_count:        stats field to detect stalled irqs
 * @irqs_unhandled:    stats field for spurious unhandled interrupts
 * @lock:        locking for SMP
 * @affinity:        IRQ affinity on SMP
 * @cpu:        cpu index useful for balancing
 * @pending_mask:    pending rebalanced interrupts
 * @dir:        /proc/irq/ procfs entry
 * @affinity_entry:    /proc/irq/smp_affinity procfs entry on SMP
 * @name:        flow handler name for /proc/interrupts output
 *
 * Pad this out to 32 bytes for cache and indexing reasons.
 */

struct irq_desc {
    irq_flow_handler_t    handle_irq;
    struct irq_chip            *chip;
    void                                *handler_data;
    void                                *chip_data;
    struct irqaction        *action;    /* IRQ action list */
    unsigned int                status;        /* IRQ status */

    unsigned int                depth;        /* nested irq disables */
    unsigned int                wake_depth;    /* nested wake enables */
    unsigned int                irq_count;    /* For detecting broken IRQs */
    unsigned int                irqs_unhandled;
    spinlock_t                    lock;
#ifdef CONFIG_SMP
    cpumask_t                        affinity;
    unsigned int                cpu;
#endif
#if defined(CONFIG_GENERIC_PENDING_IRQ) || defined(CONFIG_IRQBALANCE)
    cpumask_t                        pending_mask;
#endif
#ifdef CONFIG_PROC_FS
    struct proc_dir_entry    *dir;
#endif
    const char                    *name;
} ____cacheline_aligned;
每个硬件中断都对应着有这个irq_desc的描述,在/kernel/irq/handle.c中有定义:
/*
 * Linux has a controller-independent interrupt architecture.
 * Every controller has a 'controller-template', that is used
 * by the main code to do the right thing. Each driver-visible
 * interrupt source is transparently wired to the appropriate
 * controller. Thus drivers need not be aware of the
 * interrupt-controller.
 *
 * The code is designed to be easily extended with new/different
 * interrupt controllers, without having to do assembly magic or
 * having to touch the generic code.
 *
 * Controller mappings for all interrupt sources:
 */

struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned = {
    [0 ... NR_IRQS-1] = {
        .status = IRQ_DISABLED,
        .chip = &no_irq_chip,
        .handle_irq = handle_bad_irq,
        .depth = 1,
        .lock = SPIN_LOCK_UNLOCKED,
#ifdef CONFIG_SMP
        .affinity = CPU_MASK_ALL
#endif
    }
};
这个NR_IRQS决定了系统中最多可用的中断数,看一下这个在MX27中的定义:
#define NR_IRQS (MXC_MAX_INTS) /*asm/arch-mxc/irqs.h*/

#define MXC_MAX_INTS (MXC_MAX_INT_LINES + \
                                MXC_MAX_GPIO_LINES + \
                                MXC_MAX_EXP_IO_LINES + \
                                MXC_MAX_VIRTUAL_INTS)
  /*=64 + 32*6 + 16(0?) +16 *//*/asm-arm/arch-mxc/hardware.h*/

在init_IRQ中,有对这个irq_desc[]数组初始化的操作:
void __init init_IRQ(void)
{
    int irq;

    for (irq = 0; irq < NR_IRQS; irq++)
        irq_desc[irq].status |= IRQ_NOREQUEST | IRQ_DELAYED_DISABLE | IRQ_NOPROBE;

#ifdef CONFIG_SMP
    bad_irq_desc.affinity = CPU_MASK_ALL;
    bad_irq_desc.cpu = smp_processor_id();
#endif
    init_arch_irq();
}
将所有的status置为IRQ_NOREQUEST | IRQ_DELAYED_DISABLE | IRQ_NOPROBE然后调用上面文档中的init_arch_irq:
void __init mxc_init_irq(void)
{
    int i;
    u32 reg;

    /* put the AVIC into the reset value with
     * all interrupts disabled
     */

    __raw_writel(0, AVIC_INTCNTL);
    __raw_writel(0x1f, AVIC_NIMASK);

    /* disable all interrupts */
    __raw_writel(0, AVIC_INTENABLEH);
    __raw_writel(0, AVIC_INTENABLEL);

    /* all IRQ no FIQ */
    __raw_writel(0, AVIC_INTTYPEH);
    __raw_writel(0, AVIC_INTTYPEL);
    for (i = 0; i < MXC_MAX_INT_LINES; i++) {
#ifdef EDIO_BASE_ADDR
        if (irq_to_edio(i) != -1) {
            mxc_irq_set_edio(i, 0, 0, 0);
            set_irq_chip(i, &mxc_edio_chip);
        } else
#endif
        {
            set_irq_chip(i, &mxc_avic_chip);
        }
        set_irq_handler(i, do_level_IRQ);
        set_irq_flags(i, IRQF_VALID);
    }

    /* Set WDOG2's interrupt the highest priority level (bit 28-31) */
    reg = __raw_readl(AVIC_NIPRIORITY6);
    reg |= (0xF << 28);
    __raw_writel(reg, AVIC_NIPRIORITY6);

    printk(KERN_INFO "MXC IRQ initialized\n");
}
在MX27中支持将64个系统中断配置成FIQ或IRQ模式,并且还支持优先级设置,当然这些都只是设置寄存器,具体中断发生时由硬件去实现判别,set_irq_chip就是将对应的中断irq_desc中的chip方法初始化,上面贴出的mxc_avic_chip中只有ack,mask,unmask这几个方法,而在set_irq_chip中会调用irq_chip_set_defaults初始化enable,disable,startup,shutdown等,这些无非都是用mask和unmask方法填充。
接下来有个set_irq_handler,并且设置的还是do_level_IRQ
/*
 * Set a highlevel flow handler for a given IRQ:
 */

static inline void
set_irq_handler(unsigned int irq, irq_flow_handler_t handle)
{
    __set_irq_handler(irq, handle, 0, NULL);
}
函数调用__set_irq_handler,并且传入的第三个变量为0,表示不是chain方式,这个chain为1的话表明这个中断号所对应的中断接着一个中断扩展,这样的话当然这个中断不能用request_irq来申请了。
    void __set_irq_handler(unsigned int irq, irq_flow_handler_t handle, int is_chained,
             const char *name)
    {
        struct irq_desc *desc;
        unsigned long flags;
    
        if (irq >= NR_IRQS) {
            printk(KERN_ERR
             "Trying to install type control for IRQ%d\n", irq);
            return;
        }
    
        desc = irq_desc + irq;
    
        if (!handle)
            handle = handle_bad_irq;
    
        if (desc->chip == &no_irq_chip) {
            printk(KERN_WARNING "Trying to install %sinterrupt handler "
             "for IRQ%d\n", is_chained ? "chained " : " ", irq);
            /*
             * Some ARM implementations install a handler for really dumb
             * interrupt hardware without setting an irq_chip. This worked
             * with the ARM no_irq_chip but the check in setup_irq would
             * prevent us to setup the interrupt at all. Switch it to
             * dummy_irq_chip for easy transition.
             */

            desc->chip = &dummy_irq_chip;
        }
    
        spin_lock_irqsave(&desc->lock, flags);
    
        /* Uninstall? */
        if (handle == handle_bad_irq) {
            if (desc->chip != &no_irq_chip) {
                desc->chip->mask(irq);
                desc->chip->ack(irq);
            }
            desc->status |= IRQ_DISABLED;
            desc->depth = 1;
        }
        desc->handle_irq = handle;
        desc->name = name;
    
        if (handle != handle_bad_irq && is_chained) {
            desc->status &= ~IRQ_DISABLED;
            /*表明这个中断线上扩展了中断,因此不能用request_irq来申请*/
            desc->status |= IRQ_NOREQUEST | IRQ_NOPROBE;
            desc->depth = 0;
            desc->chip->unmask(irq);
        }
        spin_unlock_irqrestore(&desc->lock, flags);
    }
    返回
接下来调用set_irq_flags,并传了一个IRQF_VALID下去。
void set_irq_flags(unsigned int irq, unsigned int iflags)
{
    struct irqdesc *desc;
    unsigned long flags;

    if (irq >= NR_IRQS) {
        printk(KERN_ERR "Trying to set irq flags for IRQ%d\n", irq);
        return;
    }

    desc = irq_desc + irq;
    spin_lock_irqsave(&desc->lock, flags);
    desc->status |= IRQ_NOREQUEST | IRQ_NOPROBE | IRQ_NOAUTOEN;
    if (iflags & IRQF_VALID)/*驱动中可以用request_irq来申请该中断了*/
        desc->status &= ~IRQ_NOREQUEST;
    if (iflags & IRQF_PROBE)
        desc->status &= ~IRQ_NOPROBE;
    if (!(iflags & IRQF_NOAUTOEN))
        desc->status &= ~IRQ_NOAUTOEN;
    spin_unlock_irqrestore(&desc->lock, flags);
}
最后返回到mxc_init_irq中,看到最后有两行
/* Set WDOG2's interrupt the highest priority level (bit 28-31) */
    reg = __raw_readl(AVIC_NIPRIORITY6);
    reg |= (0xF << 28);
    __raw_writel(reg, AVIC_NIPRIORITY6);
这里不知道什么意思,按注释来说是设置watchdog的优先级为16,但写入的是AVIC_NIPRIORITY6这个寄存器,这个寄存器在手册中对应的应该是中断号为55的中断,也就是INT_USB2所对应的中断号,迷惑??



下面看一下GPIO中断是怎么初始化的:
static int __init _mxc_gpio_init(void)
{
    int i;
    struct gpio_port *port;

    initialized = 1;

    printk(KERN_INFO "MXC GPIO hardware\n");

    for (i = 0; i < GPIO_PORT_NUM; i++) {
        int j, gpio_count = GPIO_NUM_PIN;

        port = &gpio_port[i];
        port->base = mxc_gpio_ports[i].base;
        port->num = mxc_gpio_ports[i].num;
        port->irq = mxc_gpio_ports[i].irq;
        port->virtual_irq_start = mxc_gpio_ports[i].virtual_irq_start;

        port->reserved_map = 0;
        spin_lock_init(&port->lock);

        /* disable the interrupt and clear the status */
        __raw_writel(0, port->base + GPIO_IMR);
        __raw_writel(0xFFFFFFFF, port->base + GPIO_ISR);
        for (j = port->virtual_irq_start;
         j < port->virtual_irq_start + gpio_count; j++) {
            set_irq_chip(j, &gpio_irq_chip);
            set_irq_handler(j, do_edge_IRQ);
            set_irq_flags(j, IRQF_VALID);
        }
#ifndef MXC_MUX_GPIO_INTERRUPTS
        set_irq_chained_handler(port->irq, mxc_gpio_irq_handler);
        set_irq_data(port->irq, port);
#endif
    }

#ifdef MXC_MUX_GPIO_INTERRUPTS
    set_irq_chained_handler(port->irq, mxc_gpio_mux_irq_handler);
    set_irq_data(mxc_gpio_ports[0].irq, gpio_port);
#endif

    return 0;
}
这里MXC_MUX_GPIO_INTERRUPTS是define为1的,看到没,有一个for循环去初始化chip,handler和flags,最后循环出来后,你说这个port->irq是多少?当然是INT_GPIO了, 所以这里调用set_irq_chained_handler,将这个INT_GPIO中断号设置为扩展中断,后面set_irq_data是初始化desc->handler_data = data。



驱动中申请中断都是调用request_irq的,来看下函数原型,在kernel/irq/manage.c中:
/**
 *    request_irq - allocate an interrupt line
 *    @irq: Interrupt line to allocate
 *    @handler: Function to be called when the IRQ occurs
 *    @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.
 *
 *    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_DISABLED    Disable local interrupts while processing
 *    IRQF_SAMPLE_RANDOM    The interrupt can be used for entropy
 *
 */

int request_irq(unsigned int irq, irq_handler_t handler,
        unsigned long irqflags, const char *devname, void *dev_id)
{
    struct irqaction *action;
    int retval;

#ifdef CONFIG_LOCKDEP
    /*
     * Lockdep wants atomic interrupt handlers:
     */

    irqflags |= SA_INTERRUPT;
#endif
    /*
     * 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;
    if (irq >= NR_IRQS)
        return -EINVAL;
    if (irq_desc[irq].status & IRQ_NOREQUEST)
        return -EINVAL;
    if (!handler)
        return -EINVAL;

    action = kmalloc(sizeof(struct irqaction), GFP_ATOMIC);
    if (!action)
        return -ENOMEM;

    action->handler = handler;
    action->flags = irqflags;
    cpus_clear(action->mask);
    action->name = devname;
    action->next = NULL;
    action->dev_id = dev_id;

    select_smp_affinity(irq);

    retval = setup_irq(irq, action);
    if (retval)
        kfree(action);

    return retval;
}
其实每申请一次中断只是填充对应中断变量irq_desc[]中的action指针,所以这里调用kmalloc申请了一段内存,然后将函数传入的handler挂到action->handler下,还有初始化flags,name,dev_id等,最后调用setup_irq:
    /*
     * Internal function to register an irqaction - typically used to
     * allocate special interrupts that are part of the architecture.
     */

    int setup_irq(unsigned int irq, struct irqaction *new)
    {
        struct irq_desc *desc = irq_desc + irq;
        struct irqaction *old, **p;
        const char *old_name = NULL;
        unsigned long flags;
        int shared = 0;
    
        if (irq >= NR_IRQS)
            return -EINVAL;
    
        if (desc->chip == &no_irq_chip)
            return -ENOSYS;
        /*
         * Some drivers like serial.c use request_irq() heavily,
         * so we have to be careful not to interfere with a
         * running system.
         */

        if (new->flags & IRQF_SAMPLE_RANDOM) {
            /*
             * This function might sleep, we want to call it first,
             * outside of the atomic block.
             * Yes, this might clear the entropy pool if the wrong
             * driver is attempted to be loaded, without actually
             * installing a new handler, but is this really a problem,
             * only the sysadmin is able to do this.
             */

            rand_initialize_irq(irq);
        }
    
        /*
         * The following block of code has to be executed atomically
         */

        spin_lock_irqsave(&desc->lock, flags);
        p = &desc->action;
        old = *p;
        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.
             */

            if (!((old->flags & new->flags) & IRQF_SHARED) ||
             ((old->flags ^ new->flags) & IRQF_TRIGGER_MASK)) {
                old_name = old->name;
                goto mismatch;
            }
    
    #if defined(CONFIG_IRQ_PER_CPU)
            /* All handlers must agree on per-cpuness */
            if ((old->flags & IRQF_PERCPU) !=
             (new->flags & IRQF_PERCPU))
                goto mismatch;
    #endif
    
            /* add new interrupt at end of irq queue */
            do {
                p = &old->next;
                old = *p;
            } while (old);
            shared = 1;
        }
    
        *p = new;
    #if defined(CONFIG_IRQ_PER_CPU)
        if (new->flags & IRQF_PERCPU)
            desc->status |= IRQ_PER_CPU;
    #endif
    /*难道共享中断就没有下面的设置了?共享中断不能设置为边沿触发么?*/
        if (!shared) {
            irq_chip_set_defaults(desc->chip);
    
            /* Setup the type (level, edge polarity) if configured: */
            if (new->flags & IRQF_TRIGGER_MASK) {
                if (desc->chip && desc->chip->set_type)
                    desc->chip->set_type(irq,
                            new->flags & IRQF_TRIGGER_MASK);
                else
                    /*
                     * IRQF_TRIGGER_* but the PIC does not support
                     * multiple flow-types?
                     */

                    printk(KERN_WARNING "No IRQF_TRIGGER set_type "
                     "function for IRQ %d (%s)\n", irq,
                     desc->chip ? desc->chip->name :
                     "unknown");
            } else
                compat_irq_chip_set_default_handler(desc);
    
            desc->status &= ~(IRQ_AUTODETECT | IRQ_WAITING |
                     IRQ_INPROGRESS);
    
            if (!(desc->status & IRQ_NOAUTOEN)) {
                desc->depth = 0;
                desc->status &= ~IRQ_DISABLED;
                if (desc->chip->startup)
                    desc->chip->startup(irq);
                else
                    desc->chip->enable(irq);
            } else
                /* Undo nested disables: */
                desc->depth = 1;
        }
        spin_unlock_irqrestore(&desc->lock, flags);
    
        new->irq = irq;
        register_irq_proc(irq);
        new->dir = NULL;
        register_handler_proc(irq, new);
    
        return 0;
    
    mismatch:
        if (!(new->flags & IRQF_PROBE_SHARED)) {
            printk(KERN_ERR "IRQ handler type mismatch for IRQ %d\n", irq);
            if (old_name)
                printk(KERN_ERR "current handler: %s\n", old_name);
            dump_stack();
        }
        spin_unlock_irqrestore(&desc->lock, flags);
        return -EBUSY;
    }
    这个函数有点长,但总的来说就是判断flags里是否有IRQF_SHARED标志,如果有的话就设置action链,将新的中断处理函数与原来的链接在一个链表上,如果不是共享的话,看看flags里是否有标志说明是电平触发还是边沿触发,如果有的话再调用CHIP中的settype方法。讲到这里,我又想起了下面两个概念:
    a,多个中断处理函数共享一个硬件中断线
    b,多个硬件中断线共享一个中断处理函数
    按照上面代码的理解,a情况下request_irq的时候flags中要有IRQF_SHARED标志,并且dev_id要不为空,而b情况下并无这两个要求,你爱注册多少个就注册多少个。
    后面的是注册PROC文件了,也就是可以在/proc/irq/下面可以看到硬件中断号对应的目录,目录里面好像还有一个name吧。
    返回
返回

最后来看下发生中断的时候是怎么调用到action->handler的,当然是从C语言看起(arch/arm/kernel/irq.c),汇编部分下回再分解吧。
/*
 * do_IRQ handles all hardware IRQ's. Decoded IRQs should not
 * come via this function. Instead, they should provide their
 * own 'handler'
 */

asmlinkage void asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
{
    struct pt_regs *old_regs = set_irq_regs(regs);
    struct irqdesc *desc = irq_desc + irq;

    /*
     * Some hardware gives randomly wrong interrupts. Rather
     * than crashing, do something sensible.
     */

    if (irq >= NR_IRQS)
        desc = &bad_irq_desc;

    irq_enter();

#ifdef CONFIG_CODETEST
    ct_isr_enter(irq);
#endif /* CONFIG_CODETEST */

    desc_handle_irq(irq, desc);

    /* AT91 specific workaround */
    irq_finish(irq);

    irq_exit();
#ifdef CONFIG_CODETEST
    ct_isr_exit(irq);
#endif /* CONFIG_CODETEST */
    set_irq_regs(old_regs);
}
这个函数是直接由汇编调用的,从asmlinkage属性就可以看出来。进来就调用irq_enter:
    /*
     * It is safe to do non-atomic ops on ->hardirq_context,
     * because NMI handlers may not preempt and the ops are
     * always balanced, so the interrupted value of ->hardirq_context
     * will always be restored.
     */

    #define irq_enter()                    \
        do {                        \
            account_system_vtime(current);        \
            add_preempt_count(HARDIRQ_OFFSET);    \
            trace_hardirq_enter();            \
        } while (0)
    这个是与进程抢占有关的,还没研究到那么深的地步,无视这个函数先。
然后是调用desc_handle_irq,函数原型如下:
    static inline void desc_handle_irq(unsigned int irq, struct irq_desc *desc)
    {
        desc->handle_irq(irq, desc);
    }
    直的很直接,就调用到中断号对应的handle_irq中了,还记得上面初始化中断mxc_init_irq中设置的内容了么,一般情况下这个desc->handle_irq设置的是什么?是do_level_IRQ,对吧,而如果的GPIO中断所对应的就不一样了,这个取决于中断的type,是电平触发还是边沿触发,边沿触发对应的是do_edge_IRQ。而这些是在include/asm-arm/mach/irq.h中定义的:
    /*
     * This is for easy migration, but should be changed in the source
     */

    #define do_level_IRQ    handle_level_irq
    #define do_edge_IRQ    handle_edge_irq
    #define do_simple_IRQ    handle_simple_irq
    #define irqdesc        irq_desc
    #define irqchip        irq_chip
    先来看下handle_level_irq:
        /**
         *    handle_level_irq - Level type irq handler
         *    @irq:    the interrupt number
         *    @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 fastcall
        handle_level_irq(unsigned int irq, struct irq_desc *desc)
        {
            unsigned int cpu = smp_processor_id();
            struct irqaction *action;
            irqreturn_t action_ret;
        
            spin_lock(&desc->lock);
            mask_ack_irq(desc, irq);
        
            if (unlikely(desc->status & IRQ_INPROGRESS))
                goto out_unlock;
            desc->status &= ~(IRQ_REPLAY | IRQ_WAITING);
            kstat_cpu(cpu).irqs[irq]++;
        
            /*
             * If its disabled or no action available
             * keep it masked and get out of here
             */

            action = desc->action;
            if (unlikely(!action || (desc->status & IRQ_DISABLED))) {
                desc->status |= IRQ_PENDING;
                goto out_unlock;
            }
        
            desc->status |= IRQ_INPROGRESS;
            desc->status &= ~IRQ_PENDING;
            spin_unlock(&desc->lock);
        
            action_ret = handle_IRQ_event(irq, action);
            if (!noirqdebug)
                note_interrupt(irq, desc, action_ret);
        
            spin_lock(&desc->lock);
            desc->status &= ~IRQ_INPROGRESS;
            if (!(desc->status & IRQ_DISABLED) && desc->chip->unmask)
                desc->chip->unmask(irq);
        out_unlock:
            spin_unlock(&desc->lock);
        }
        进来先调用mask_ack_irq:
            static inline void mask_ack_irq(struct irq_desc *desc, int irq)
            {
                if (desc->chip->mask_ack)
                    desc->chip->mask_ack(irq);
                else {
                    desc->chip->mask(irq);
                    desc->chip->ack(irq);
                }
            }
            是先mask,再ack,而我们在中断系统初始化的时候将mxc_avic_chip的ack方法指向mask,而mask的方法也是mask,没错,这里运行的结果是执行了两次mask,也就是说我们在执行真正的中断处理函数的时候该中断线上是不会再出现中断请求的,这个与网上的一篇文章《对中断的一点思考 杨小华(normalnotebook@126.com)》有所不同,她讲述的是PC架构中的中断,也就是8259传为中断控制器下的。其实我也不太清楚为什么ARM芯片对中断的这些处理要与PC中的不一样,接下来也还会有一些不一样的地方。
            返回
        然后置status为IRQ_INPROGRESS,清PENDING,下面再调用handle_IRQ_event:
            /**
             * handle_IRQ_event - irq action chain handler
             * @irq:    the interrupt number
             * @action:    the interrupt action chain for this irq
             *
             * Handles the action chain of an irq event
             */

            irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action)
            {
                irqreturn_t ret, retval = IRQ_NONE;
                unsigned int status = 0;
            
                handle_dynamic_tick(action);
            
                if (!(action->flags & IRQF_DISABLED))
                    local_irq_enable_in_hardirq();
            
                do {
                    ret = action->handler(irq, action->dev_id);
                    if (ret == IRQ_HANDLED)
                        status |= action->flags;
                    retval |= ret;
                    action = action->next;
                } while (action);
            
                if (status & IRQF_SAMPLE_RANDOM)
                    add_interrupt_randomness(irq);
                local_irq_disable();
            
                return retval;
            }
            handle_dynamic_tick?什么啊,无视,后面有判断IRQF_DISABLED的,据说这个标志一般的中断都最好不要用,只有那个什么时钟中断可以用这个标志,因为如果这个标志设置了的话,下面的整个中断的处理都是在关ARM系统中断的环境下执行的。
            后来有个do-while循环,这个就是对于共享中断的实际处理了,如果这个中断线上有几个处理函数的话就轮流着处理了,直到所有的都处理完为止
            返回
        返回来了,因为前面调用了mask_ack,所以现在要调用unmask了,这样才对称处理。。
        返回
    返回handle_level_irq
    
    下面再来看下handle_edge_irq:
        /**
         *    handle_edge_irq - edge type IRQ handler
         *    @irq:    the interrupt number
         *    @desc:    the interrupt description structure for this irq
         *
         *    Interrupt occures on the falling and/or rising edge of a hardware
         *    signal. The occurence is latched into the irq controller hardware
         *    and must be acked in order to be reenabled. After the ack another
         *    interrupt can happen on the same source even before the first one
         *    is handled by the assosiacted event handler. If this happens it
         *    might be necessary to disable (mask) the interrupt depending on the
         *    controller hardware. This requires to reenable the interrupt inside
         *    of the loop which handles the interrupts which have arrived while
         *    the handler was running. If all pending interrupts are handled, the
         *    loop is left.
         */

        void fastcall
        handle_edge_irq(unsigned int irq, struct irq_desc *desc)
        {
            const unsigned int cpu = smp_processor_id();
        
            spin_lock(&desc->lock);
        
            desc->status &= ~(IRQ_REPLAY | IRQ_WAITING);
        
            /*
             * If we're currently running this IRQ, or its disabled,
             * we shouldn't process the IRQ. Mark it pending, handle
             * the necessary masking and go out
             */

             /*
             *这里是处理下面两种情况:
             *a,中断嵌套,当我还在下面的do-while循环的时候,这个中断线上双来了一次中断,这时便简单地置PENDING位,并MASK,后返回,让并中断嵌套转化成串行中断处理。
             *b,没有注册中断处理函数,这时也是简单地置PENDING位,并MASK,后返回。
             *也即是说这个嵌套深度只能为1,因为嵌套一次后都MASK了?非也,在下面的do-while中有判断是否PENDING并unmask的动作!!
             */

            if (unlikely((desc->status & (IRQ_INPROGRESS | IRQ_DISABLED)) ||
                 !desc->action)) {
                desc->status |= (IRQ_PENDING | IRQ_MASKED);
                mask_ack_irq(desc, irq);
                goto out_unlock;
            }
        
            kstat_cpu(cpu).irqs[irq]++;
        
            /* Start handling the irq */
            /*在我们的MX27系统中只有GPIO中断有ack,其实也就是清GPIO脚线对应中断位/
            desc->chip->ack(irq);
        
            /* Mark the IRQ currently in progress.*/

            desc->status |= IRQ_INPROGRESS;
        
            do {
                struct irqaction *action = desc->action;
                irqreturn_t action_ret;
        
                if (unlikely(!action)) {
                    desc->chip->mask(irq);
                    goto out_unlock;
                }
        
                /*
                 * When another irq arrived while we were handling
                 * one, we could have masked the irq.
                 * Renable it, if it was not disabled in meantime.
                 */

                if (unlikely((desc->status &
                     (IRQ_PENDING | IRQ_MASKED | IRQ_DISABLED)) ==
                     (IRQ_PENDING | IRQ_MASKED))) {
                    /*表明有中断嵌套!*/
                    desc->chip->unmask(irq);
                    desc->status &= ~IRQ_MASKED;
                }
        
                desc->status &= ~IRQ_PENDING;
                spin_unlock(&desc->lock);
                action_ret = handle_IRQ_event(irq, action);
                if (!noirqdebug)
                    note_interrupt(irq, desc, action_ret);
                spin_lock(&desc->lock);
        
            } while ((desc->status & (IRQ_PENDING | IRQ_DISABLED)) == IRQ_PENDING);
        
            desc->status &= ~IRQ_INPROGRESS;
        out_unlock:
            spin_unlock(&desc->lock);
        }
        看看上面的注释,应该差不多了吧,再后面也是调用handle_IRQ_event。
    handle_edge_irq 完


阅读(593) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~