irq interrupt subsystem

Posted by julien on Fri, 31 May 2019 19:17:24 +0200

Reference resources: http://blog.csdn.net/adaptiver/article/details/6834337
1 Because when an interrupt occurs, the system automatically shuts down and interrupts when it enters by the interrupt door (for x86 platform, if position of eflags register is 0), the interrupt is restored in irq_exit
2 It appears that handle_edge_irq also has operations for interrupt closing and opening
The first is that because linux does not support interrupt priority, any interrupt can preempt other interrupts, but no preemption will occur for the same type of interrupt (that is, an interrupt defined with the same interrupt line), and they will be called to execute this type of interrupt in turn.Second, the so-called "Interrupt can preempt the kernel as long as it occurs" has certain restrictions, because when an interrupt occurs, the system automatically closes and interrupts when the interrupt door enters (for x86 platform, if position of eflags register is 0), only after the interrupt is opened during the execution of the interrupt function (handle_IRQ_event).

For linux systems, interrupts occur as follows:
Jump to the specified position of the interrupt vector table according to the interrupt number --> Protect the field, and convert mode to SVC32 --> Jump further to the processing function as directed by the vector table --> Jump further u irq_svc, and save those registers for interrupt processing when the interrupt returns --> irq_handler Interrupt Processing -->
asm_do_IRQ ->generic_handle_irq(irq) -> desc->handle_irq(irq, desc), handle_edge_irq, etc. ->shield the same interrupt, clear the interrupt so that the interrupt can be registered ->handle_IRQ_event ->action->handler (irq, action->dev_id) ->interrupt returns irq_exit ->invoke_softirq()->on-site recovery if there is still a soft interrupt not executed

The interrupt generation combination function is used to illustrate the interrupt process:
1. Important structures
/include/linux/irq.h
The irq_desc kernel records an array of irq_desc. Each item of the array corresponds to an interrupt or a set of interrupts using the same interrupt number. An irq_desc sentence records almost everything interrupt related. This structure is the core of interrupts.These include two important structures, irq_chip and irqaction.
This structure acts much like gpio_desc, it is an array, and the corresponding chip and other information is found through the index.

struct irq_desc {
        unsigned int            irq;                                              interrupt number
        struct timer_rand_state *timer_rand_state;
        unsigned int            *kstat_irqs;
#ifdef CONFIG_INTR_REMAP
        struct irq_2_iommu      *irq_2_iommu;
#endif
        irq_flow_handler_t      handle_irq;                                          //Interrupt Handling Entry Function
        struct irq_chip         *chip;                                               //irq interrupt subsystem interface function
        struct msi_desc         *msi_desc;
        void                    *handler_data;                                        
        void                    *chip_data;
        struct irqaction        *action;        /* IRQ action list */                 //Hang interrupt functions on this list when registering interrupts
        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 long           last_unhandled; /* Aging timer for unhandled count */
        unsigned int            irqs_unhandled;
        raw_spinlock_t          lock;
#ifdef CONFIG_SMP
        cpumask_var_t           affinity;
        const struct cpumask    *affinity_hint;
        unsigned int            node;
#ifdef CONFIG_GENERIC_PENDING_IRQ
        cpumask_var_t           pending_mask;
#endif
#endif
        atomic_t                threads_active;
        wait_queue_head_t       wait_for_threads;
#ifdef CONFIG_PROC_FS
        struct proc_dir_entry   *dir;
#endif
        const char              *name;
} ____cacheline_internodealigned_in_smp;

//Next, look at the irq interrupt subsystem interface function
struct irq_chip {  
    const char  *name;  
    unsigned int    (*startup)(unsigned int irq); Start Interrupt  
    void        (*shutdown)(unsigned int irq); Close Interrupt  
    void        (*enable)(unsigned int irq);   Enable interruption  
    void        (*disable)(unsigned int irq);  Prohibit interruption  

    void        (*ack)(unsigned int irq);   The interrupt response function is to clear the interrupt identification function  
    void        (*mask)(unsigned int irq);   Interrupt shielding function  
    void        (*mask_ack)(unsigned int irq); Shielding interrupt response function, commonly used for level triggering, needs shielding before answering  
    void        (*unmask)(unsigned int irq);  Open Interrupt  
    void        (*eoi)(unsigned int irq);  

    void        (*end)(unsigned int irq);  
    int     (*set_affinity)(unsigned int irq,  
                    const struct cpumask *dest);  
    int     (*retrigger)(unsigned int irq);  
    int     (*set_type)(unsigned int irq, unsigned int flow_type); Set interrupt type, including settings GPIO Mouth is interrupt input  
    int     (*set_wake)(unsigned int irq, unsigned int on);  

    void        (*bus_lock)(unsigned int irq);  Uplock function  
    void        (*bus_sync_unlock)(unsigned int irq); Unlock  

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

We can see that what we're implementing here is a framework that requires us to further populate the functions inside.We are analyzing another structure, irqaction
include/linux/interrupt.h

struct irqaction {  
    irq_handler_t handler;  User-registered interrupt handler  
    unsigned long flags;    Interrupt Identification  
    const char *name;       User registered interrupt name, cat/proc/interrupts As you can see  
    void *dev_id;           It can be a user-passed parameter or used to distinguish shared interrupts  
    struct irqaction *next; irqaction Structural chains, a shared interrupt can have multiple interrupt handlers  
    int irq;                interrupt number  
    struct proc_dir_entry *dir;  
    irq_handler_t thread_fn;  
    struct task_struct *thread;  
    unsigned long thread_flags;  
};  

//Step by step analysis of request_irq
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);
}

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);                                            according to irq Found Initialized irq_desc Structures, followed by an initializer for this structure
        if (!desc)
                return -EINVAL;

        if (desc->status & IRQ_NOREQUEST)                                  If it can't be requested
                return -EINVAL;

        if (!handler) {                                                        If no interrupt handling function is defined
                if (!thread_fn)                                                                     
                        return -EINVAL;
                handler = irq_default_primary_handler;                          Give a default function to handler
        }

        action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);                 Apply for one action structural morphology
        if (!action)
                return -ENOMEM;

        action->handler = handler;                                              The following assignments are made to each member of the structure
        action->thread_fn = thread_fn;
        action->flags = irqflags;
        action->name = devname;
        action->dev_id = dev_id;

        chip_bus_lock(irq, desc);
        retval = __setup_irq(irq, desc, action);                               See explanation below
        chip_bus_sync_unlock(irq, desc);

        if (retval)
                kfree(action);
        return retval;
}
EXPORT_SYMBOL(request_threaded_irq);



static int
__setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
{
        struct irqaction *old, **old_ptr;
        const char *old_name = NULL;
        unsigned long flags;
        int nested, shared = 0;
        int ret;

        if (!desc)                                                             
                return -EINVAL;

        if (desc->chip == &no_irq_chip)                                       Determine whether to use no_irq_chip Application program interface
                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) {                                  Sometimes used for serial driver
                /*
                 * 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);
        }

        /* Oneshot interrupts are not allowed with shared */
        if ((new->flags & IRQF_ONESHOT) && (new->flags & IRQF_SHARED))        Oneshot Sharing interrupts are not allowed   
                return -EINVAL;

        /*
         * Check whether the interrupt nests into another interrupt
         * thread.
         */
        nested = desc->status & IRQ_NESTED_THREAD;                               Whether nested flags are defined, obviously not implemented
        if (nested) {
                if (!new->thread_fn)
                        return -EINVAL;
                /*
                 * 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;
        }
        if (new->thread_fn && !nested) {                                          Is there a thread function definition //Do not execute
                struct task_struct *t;

                t = kthread_create(irq_thread, new, "irq/%d-%s", irq,                Open Thread
                                   new->name);
                if (IS_ERR(t))
                        return PTR_ERR(t);
                /*
                 * 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;
        }

        /*
         * The following block of code has to be executed atomically
         */
        raw_spin_lock_irqsave(&desc->lock, flags);
        old_ptr = &desc->action;                                                    From the old desc Remove the original from the structure action
        old = *old_ptr;
        if (old) {                                                                   Sharing interrupts if any
                /*
                 * 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) ||                     Determine if they are all shared or triggered the same way
                    ((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 {
                        old_ptr = &old->next;                                      On already mounted action The last empty position at the end of the list for new action To mount
                        old = *old_ptr;
                } while (old);
                shared = 1;
        }

        if (!shared) {
                irq_chip_set_defaults(desc->chip);                                     If individual is not set chip Interface Settings Default Function

                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,                                Set trigger mode 
                                        new->flags & IRQF_TRIGGER_MASK);

                        if (ret)
                                goto out_thread;
                } else
                        compat_irq_chip_set_default_handler(desc);        
#if defined(CONFIG_IRQ_PER_CPU)
                if (new->flags & IRQF_PERCPU)
                        desc->status |= IRQ_PER_CPU;
#endif

                desc->status &= ~(IRQ_AUTODETECT | IRQ_WAITING | IRQ_ONESHOT |
                                  IRQ_INPROGRESS | IRQ_SPURIOUS_DISABLED);

                if (new->flags & IRQF_ONESHOT)
                        desc->status |= IRQ_ONESHOT;

                if (!(desc->status & IRQ_NOAUTOEN)) {
                        desc->depth = 0;
                        desc->status &= ~IRQ_DISABLED;                             Set state
                        desc->chip->startup(irq);                                   Open Interrupt
                } else
                        /* Undo nested disables: */
                        desc->depth = 1;

                /* Exclude IRQ from balancing if requested */
                if (new->flags & IRQF_NOBALANCING)
                        desc->status |= IRQ_NO_BALANCING;

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

        } else if ((new->flags & IRQF_TRIGGER_MASK)
                        && (new->flags & IRQF_TRIGGER_MASK)
                                != (desc->status & IRQ_TYPE_SENSE_MASK)) {
                /* hope the handler works with the actual trigger mode... */
                pr_warning("IRQ %d uses trigger mode %d; requested %d\n",
                                irq, (int)(desc->status & IRQ_TYPE_SENSE_MASK),
                                (int)(new->flags & IRQF_TRIGGER_MASK));
        }

        new->irq = irq;                                                             
        *old_ptr = new;                                                            stay irq Owning desc Structures will be new action Hang to Chain List

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

         */
        if (shared && (desc->status & IRQ_SPURIOUS_DISABLED)) {
                desc->status &= ~IRQ_SPURIOUS_DISABLED;
                __enable_irq(desc, irq, false);
        }

        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);                                            stay proc Create under irq File Node         
        new->dir = NULL;
        register_handler_proc(irq, new);                                           stay proc Create under handler File Node

        return 0;

mismatch:
#ifdef CONFIG_DEBUG_SHIRQ
        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();
        }
#endif
        ret = -EBUSY;

out_thread:
        raw_spin_unlock_irqrestore(&desc->lock, flags);
        if (new->thread) {
                struct task_struct *t = new->thread;

                new->thread = NULL;
                if (likely(!test_bit(IRQTF_DIED, &new->thread_flags)))
                        kthread_stop(t);
                put_task_struct(t);
        }
        return ret;
}

All of the above are done: create an action structure, take out the original daction structure from the desc structure, and mount the new action structure into the original acton chain table.

_u setup_irq has a lot of content, mainly completes several aspects of work

1. To add the IRQ action structure to the action list of irq_desc, it is necessary to determine whether it is a shared interrupt. Only a shared interrupt can add more than one interrupt handler. If it is a shared interrupt, it is necessary to check whether the interrupt handler is the same as other functions in the chain table, and only a consistent one can be added to the chain table.

2. Set some function pointers in the irq_chip structure to point to default functions

3. Set the trigger mode of interrupt and start interrupt

<1>Interrupt the initialization process

The following is a copy of someone else's blog at the original address http://blog.chinaunix.net/space.php?uid=15193587&do=blog&cuid=2194431
Next we analyze the process of kernel interrupt initialization and how to call the irq initialization function on a new platform.
Here we take the s3c2410 platform as an example. Its interrupt initialization function is defined as:

/* arch/arm/mach-s3c2410/irq.c */
void __init s3c24xx_init_irq(void)
{
......
}

The s3c24xx_init_irq is assigned to the.Init_irq member of the mach_desc structure through the MACHINE_START macro in arch/arm/mach-s3c2410/mach-smdk2410.c.

MACHINE_START(SMDK2410,"SMDK2410")/* @TODO: request a new identifier and switch
        * to SMDK2410 */
 /* Maintainer: Jonas Dietsche */
 .phys_io = S3C2410_PA_UART,
 .io_pg_offst =(((u32)S3C24XX_VA_UART)>> 18)& 0xfffc,
 .boot_params = S3C2410_SDRAM_PA+ 0x100,
 .map_io = smdk2410_map_io,
 .init_irq = s3c24xx_init_irq,
 .init_machine = smdk_machine_init,
 .timer = &s3c24xx_timer,
MACHINE_END

//Note: The purpose of the MACHINE_START macro is to initialize the mach_desc structure.Some key architecture-related functions are defined in mach_desc.This structure is critical when Porting kernel s move to a new platform.

init_irq This member is assigned to when the system is initialized init_arch_irq Global variables, as follows:
/* arch/arm/kernel/setup.c */
void __init setup_arch(char**cmdline_p)
{
 ......
 cpu_init();
 /*
  * Set up various architecture-specific pointers
  */
 init_arch_irq = mdesc->init_irq;
 system_timer = mdesc->timer;
 init_machine = mdesc->init_machine;
 ......
}

Note: You can see that not only the init_arch_irq global variable is initialized here, but also the system_timer,init_machine, and other global variables.This is a mechanism for kernel s to support multiple platforms.Of course, I don't describe system_timer and init_machine here much, so you can see them for yourself if you're interested.The mechanism is similar to init_arch_irq.

The init_arch_irq function pointer is defined within Architecture Independent arch/arm/kernel/irq.c
/* arch/arm/kernel/irq.c */
void (*init_arch_irq)(void) __initdata = NULL;

It is executed within the init_IRQ function.

/* arch/arm/kernel/irq.c */
void __init init_IRQ(void)
{
 int irq;
 for (irq = 0; irq < NR_IRQS; irq++)
  irq_desc[irq].status|= IRQ_NOREQUEST| IRQ_DELAYED_DISABLE|                  //desc status proceed flag
   IRQ_NOPROBE;
#ifdef CONFIG_SMP
 bad_irq_desc.affinity = CPU_MASK_ALL;
 bad_irq_desc.cpu = smp_processor_id();
#endif
 init_arch_irq();
}

Where was init_IRQ called? We guess it was called at system initialization.
As a result, init_IRQ is called within the start_kernel function in init/main.c:

asmlinkage void __init start_kernel(void)
{
 ......
 trap_init();
 rcu_init();
 init_IRQ();
 pidhash_init();
 clockevents_init();
 init_timers();
 ......
}

init_arch_irq function pointer, which means this is a global function pointer and is architecture dependent.Let me continue with the above analysis of the s3c24xx_init_irq() function

void __init s3c24xx_init_irq(void)
{
        unsigned long pend;
        unsigned long last;
        int irqno;
        int i;

#ifdef CONFIG_FIQ
        init_FIQ();
#endif

        irqdbf("s3c2410_init_irq: clearing interrupt status flags\n");

        /* first, clear all interrupts pending... */

        last = 0;
        for (i = 0; i < 4; i++) {
                pend = __raw_readl(S3C24XX_EINTPEND);                                     Clear interrupt operation

                if (pend == 0 || pend == last)
                        break;

                __raw_writel(pend, S3C24XX_EINTPEND);
                printk("irq: clearing pending ext status %08x\n", (int)pend);
                last = pend;
        }

        last = 0;
        for (i = 0; i < 4; i++) {
                pend = __raw_readl(S3C2410_INTPND);

                if (pend == 0 || pend == last)
                        break;

                __raw_writel(pend, S3C2410_SRCPND);
                __raw_writel(pend, S3C2410_INTPND);
                printk("irq: clearing pending status %08x\n", (int)pend);
                last = pend;
        }

        last = 0;
        for (i = 0; i < 4; i++) {
                pend = __raw_readl(S3C2410_SUBSRCPND);

                if (pend == 0 || pend == last)
                        break;

                printk("irq: clearing subpending status %08x\n", (int)pend);
                __raw_writel(pend, S3C2410_SUBSRCPND);
                last = pend;
        }

        /* register the main interrupts */
        for (irqno = IRQ_EINT0; irqno <= IRQ_EINT3; irqno++) {
                irqdbf("registering irq %d (ext int)\n", irqno);
                set_irq_chip(irqno, &s3c_irq_eint0t4);                          take chip Structural Initialization Initialization desc in
                set_irq_handler(irqno, handle_edge_irq);                         take handler Function Initialization Initiation desc in
                set_irq_flags(irqno, IRQF_VALID);                                 Clear Status Flag Bits IRQ_NOREQUEST
        }
. . . . . 
}

1 The for loop above clears the interrupt identifier and sets a last variable. I don't know if this is to prevent one erase interrupt from failing.
2 Fill in the irq_chip structure first, then set the handler entry for the interrupt. When this response is interrupted, the user-registered interrupt handler is called through the function entry, and the interrupt identifier is set to be usable.
Above references: http://blog.csdn.net/yimu13/article/details/6803957?locationNum=5
Next, let's look at the interrupt handling process, which starts a series of processes when an interrupt occurs.
Reference resources: https://wenku.baidu.com/view/6c6e30634b35eefdc9d33300.html

Topics: Linux PHP