内核中断流程

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/hz5034/article/details/80944160

init_IRQ()

init_IRQ() -> native_init_IRQ() -> init_ISA_irqs() -> set_irq_chip_and_handler_name() -> __set_irq_handler()

void __init init_IRQ(void)
{
   x86_init.irqs.intr_init(); // native_init_IRQ()
}

struct x86_init_ops x86_init __initdata = {

   .resources = {
      .probe_roms       = x86_init_noop,
      .reserve_resources = reserve_standard_io_resources,
      .memory_setup     = default_machine_specific_memory_setup,
   },

   .mpparse = {
      .mpc_record       = x86_init_uint_noop,
      .setup_ioapic_ids  = x86_init_noop,
      .mpc_apic_id      = default_mpc_apic_id,
      .smp_read_mpc_oem  = default_smp_read_mpc_oem,
      .mpc_oem_bus_info  = default_mpc_oem_bus_info,
      .find_smp_config   = default_find_smp_config,
      .get_smp_config       = default_get_smp_config,
   },

   .irqs = {
      .pre_vector_init   = init_ISA_irqs,
      .intr_init    = native_init_IRQ,
      .trap_init    = x86_init_noop,
   },

   .oem = {
      .arch_setup       = x86_init_noop,
      .banner          = default_banner,
   },

   .paging = {
      .pagetable_setup_start = native_pagetable_setup_start,
      .pagetable_setup_done  = native_pagetable_setup_done,
   },

   .timers = {
      .setup_percpu_clockev  = setup_boot_APIC_clock,
      .tsc_pre_init     = x86_init_noop,
      .timer_init       = hpet_time_init,
   },
};

void __init native_init_IRQ(void)
{
   int i;

   /* Execute any quirks before the call gates are initialised: */
   x86_init.irqs.pre_vector_init(); // init_ISA_irqs()

   apic_intr_init();

   /*
    * Cover the whole vector space, no vector can escape
    * us. (some of these will be overridden and become
    * 'special' SMP interrupts)
    */
   for (i = FIRST_EXTERNAL_VECTOR; i < NR_VECTORS; i++) {
      /* IA32_SYSCALL_VECTOR could be used in trap_init already. */
      if (!test_bit(i, used_vectors))
         set_intr_gate(i, interrupt[i-FIRST_EXTERNAL_VECTOR]);
   }

   if (!acpi_ioapic)
      setup_irq(2, &irq2);

#ifdef CONFIG_X86_32
   /*
    * External FPU? Set up irq13 if so, for
    * original braindamaged IBM FERR coupling.
    */
   if (boot_cpu_data.hard_math && !cpu_has_fpu)
      setup_irq(FPU_IRQ, &fpu_irq);

   irq_ctx_init(smp_processor_id());
#endif
}

void __init init_ISA_irqs(void)
{
   int i;

#if defined(CONFIG_X86_64) || defined(CONFIG_X86_LOCAL_APIC)
   init_bsp_APIC();
#endif
   init_8259A(0);

   /*
    * 16 old-style INTA-cycle interrupts:
    */
   for (i = 0; i < NR_IRQS_LEGACY; i++) { // 遍历irq_desc数组
      struct irq_desc *desc = irq_to_desc(i);

      desc->status = IRQ_DISABLED;
      desc->action = NULL;
      desc->depth = 1;

      set_irq_chip_and_handler_name(i, &i8259A_chip,
                     handle_level_irq, "XT"); // set_irq_chip_and_handler_name()
   }
}

void
set_irq_chip_and_handler_name(unsigned int irq, struct irq_chip *chip,
               irq_flow_handler_t handle, const char *name)
{
   set_irq_chip(irq, chip);
   __set_irq_handler(irq, handle, 0, name); // __set_irq_handler()
}

void
__set_irq_handler(unsigned int irq, irq_flow_handler_t handle, int is_chained,
          const char *name)
{
    struct irq_desc *desc = irq_to_desc(irq);
    unsigned long flags;

    if (!desc) {
        printk(KERN_ERR
               "Trying to install type control for IRQ%d\n", irq);
        return;
    }

    if (!handle)
        handle = handle_bad_irq;
    else 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;
    }

    chip_bus_lock(irq, desc);
    spin_lock_irqsave(&desc->lock, flags);

    /* Uninstall? */
    if (handle == handle_bad_irq) {
        if (desc->chip != &no_irq_chip)
            mask_ack_irq(desc, irq);
        desc->status |= IRQ_DISABLED;
        desc->depth = 1;
    }
    desc->handle_irq = handle; // 设置desc->handle_irq为handle_level_irq()
    desc->name = name;

    if (handle != handle_bad_irq && is_chained) {
        desc->status &= ~IRQ_DISABLED;
        desc->status |= IRQ_NOREQUEST | IRQ_NOPROBE;
        desc->depth = 0;
        desc->chip->startup(irq);
    }
    spin_unlock_irqrestore(&desc->lock, flags);
    chip_bus_sync_unlock(irq, desc);
}

do_IRQ()

do_IRQ() -> handle_irq() -> generic_handle_irq_desc() -> handle_level_irq()

unsigned int __irq_entry do_IRQ(struct pt_regs *regs)
{
   struct pt_regs *old_regs = set_irq_regs(regs);

   /* high bit used in ret_from_ code  */
   unsigned vector = ~regs->orig_ax;
   unsigned irq;

   exit_idle();
   irq_enter();

   irq = __get_cpu_var(vector_irq)[vector]; // 得到中断号

   if (!handle_irq(irq, regs)) { // handle_irq()
      ack_APIC_irq();

      if (printk_ratelimit())
         pr_emerg("%s: %d.%d No irq handler for vector (irq %d)\n",
            __func__, smp_processor_id(), vector, irq);
   }

   irq_exit(); // irq_exit()

   set_irq_regs(old_regs);
   return 1;
}

bool handle_irq(unsigned irq, struct pt_regs *regs)
{
   struct irq_desc *desc;

   stack_overflow_check(regs);

   desc = irq_to_desc(irq); // 得到中断号对应的irq_desc
   if (unlikely(!desc))
      return false;

   generic_handle_irq_desc(irq, desc); // generic_handle_irq_desc()
   return true;
}

static inline void generic_handle_irq_desc(unsigned int irq, struct irq_desc *desc)
{
#ifdef CONFIG_GENERIC_HARDIRQS_NO__DO_IRQ
   desc->handle_irq(irq, desc);
#else
   if (likely(desc->handle_irq))
      desc->handle_irq(irq, desc); // handle_level_irq()
   else
      __do_IRQ(irq);
#endif
}

handle_level_irq()

handle_level_irq() -> handle_IRQ_event() -> ixgbe_msix_clean_many()

void
handle_level_irq(unsigned int irq, struct irq_desc *desc)
{
   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_incr_irqs_this_cpu(irq, desc);

   /*
    * If its disabled or no action available
    * keep it masked and get out of here
    */
   action = desc->action; // 得到irq_desc的irqaction
   if (unlikely(!action || (desc->status & IRQ_DISABLED)))
      goto out_unlock;

   desc->status |= IRQ_INPROGRESS;
   spin_unlock(&desc->lock);

   action_ret = handle_IRQ_event(irq, action); // handle_IRQ_event()
   if (!noirqdebug)
      note_interrupt(irq, desc, action_ret);

   spin_lock(&desc->lock);
   desc->status &= ~IRQ_INPROGRESS;

   if (unlikely(desc->status & IRQ_ONESHOT))
      desc->status |= IRQ_MASKED;
   else if (!(desc->status & IRQ_DISABLED) && desc->chip->unmask)
      desc->chip->unmask(irq);
out_unlock:
   spin_unlock(&desc->lock);
}

irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action)
{
   irqreturn_t ret, retval = IRQ_NONE;
   unsigned int status = 0;

   if (!(action->flags & IRQF_DISABLED))
      local_irq_enable_in_hardirq();

   do {
      trace_irq_handler_entry(irq, action);
      ret = action->handler(irq, action->dev_id); // ixgbe_msix_clean_many()
      trace_irq_handler_exit(irq, action, ret);

      switch (ret) {
      case IRQ_WAKE_THREAD:
         /*
          * Set result to handled so the spurious check
          * does not trigger.
          */
         ret = IRQ_HANDLED;

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

         /*
          * Wake up the handler thread for this
          * action. In case the thread crashed and was
          * killed we just pretend that we handled the
          * interrupt. The hardirq handler above has
          * disabled the device interrupt, so no irq
          * storm is lurking.
          */
         if (likely(!test_bit(IRQTF_DIED,
                    &action->thread_flags))) {
            set_bit(IRQTF_RUNTHREAD, &action->thread_flags);
            wake_up_process(action->thread);
         }

         /* Fall through to add to randomness */
      case IRQ_HANDLED:
         status |= action->flags;
         break;

      default:
         break;
      }

      retval |= ret;
      action = action->next;
   } while (action);

   if (status & IRQF_SAMPLE_RANDOM)
      add_interrupt_randomness(irq);
   local_irq_disable();

   return retval;
}

request_irq()

request_irq() -> request_threaded_irq() -> __setup_irq()

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

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;

   /*
    * handle_IRQ_event() always ignores IRQF_DISABLED except for
    * the _first_ irqaction (sigh).  That can cause oopsing, but
    * the behavior is classified as "will not fix" so we need to
    * start nudging drivers away from using that idiom.
    */
   if ((irqflags & (IRQF_SHARED|IRQF_DISABLED)) ==
               (IRQF_SHARED|IRQF_DISABLED)) {
      pr_warning(
        "IRQ %d/%s: IRQF_DISABLED is not guaranteed on shared IRQs\n",
         irq, devname);
   }

#ifdef CONFIG_LOCKDEP
   /*
    * Lockdep wants atomic interrupt handlers:
    */
   irqflags |= IRQF_DISABLED;
#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;

   desc = irq_to_desc(irq); // 得到中断号对应的irq_desc
   if (!desc)
      return -EINVAL;

   if (desc->status & IRQ_NOREQUEST)
      return -EINVAL;

   if (!handler) {
      if (!thread_fn)
         return -EINVAL;
      handler = irq_default_primary_handler;
   }

   action = kzalloc(sizeof(struct irqaction), GFP_KERNEL); // 创建irqaction
   if (!action)
      return -ENOMEM;

   action->handler = handler; // 设置irqaction的handler
   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); // __setup_irq()
   chip_bus_sync_unlock(irq, desc);

   if (retval)
      kfree(action);

#ifdef CONFIG_DEBUG_SHIRQ
   if (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
__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)
      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);
   }

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

   /*
    * Check whether the interrupt nests into another interrupt
    * thread.
    */
   nested = desc->status & IRQ_NESTED_THREAD;
   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;
   }

   /*
    * 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) {
      struct task_struct *t;

      t = kthread_create(irq_thread, new, "irq/%d-%s", irq,
               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
    */
   spin_lock_irqsave(&desc->lock, flags);
   old_ptr = &desc->action; // 将irq_desc->action的地址赋给old_ptr
   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.
       */
      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 {
         old_ptr = &old->next;
         old = *old_ptr;
      } while (old);
      shared = 1;
   }

   if (!shared) {
      irq_chip_set_defaults(desc->chip);

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

      /*
       * Force MSI interrupts to run with interrupts
       * disabled. The multi vector cards can cause stack
       * overflows due to nested interrupts when enough of
       * them are directed to a core and fire at the same
       * time.
       */
      if (desc->msi_desc)
         new->flags |= IRQF_DISABLED;

      if (!(desc->status & IRQ_NOAUTOEN)) {
         desc->depth = 0;
         desc->status &= ~IRQ_DISABLED;
         desc->chip->startup(irq);
      } 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; // 将new赋给irq_desc->action

   /* 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->status & IRQ_SPURIOUS_DISABLED)) {
      desc->status &= ~IRQ_SPURIOUS_DISABLED;
      __enable_irq(desc, irq, false);
   }

   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);
   new->dir = NULL;
   register_handler_proc(irq, new);

   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:
   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;
}

irq_exit()

irq_exit() -> do_softirq() -> __do_softirq() ->

void irq_exit(void)
{
   account_system_vtime(current);
   trace_hardirq_exit();
   sub_preempt_count(IRQ_EXIT_OFFSET);
   if (!in_interrupt() && local_softirq_pending())
      invoke_softirq(); // do_softirq()

#ifdef CONFIG_NO_HZ
   /* Make sure that timer wheel updates are propagated */
   rcu_irq_exit();
   if (idle_cpu(smp_processor_id()) && !in_interrupt() && !need_resched())
      tick_nohz_stop_sched_tick(0);
#endif
   preempt_enable_no_resched();
}

asmlinkage void do_softirq(void)
{
   __u32 pending;
   unsigned long flags;

   if (in_interrupt())
      return;

   local_irq_save(flags);

   pending = local_softirq_pending();

   if (pending)
      __do_softirq();

   local_irq_restore(flags);
}

asmlinkage void __do_softirq(void)
{
   struct softirq_action *h;
   __u32 pending;
   int max_restart = MAX_SOFTIRQ_RESTART;
   int cpu;

   pending = local_softirq_pending();
   account_system_vtime(current);

   __local_bh_disable((unsigned long)__builtin_return_address(0));
   lockdep_softirq_enter();

   cpu = smp_processor_id();
restart:
   /* Reset the pending bitmask before enabling irqs */
   set_softirq_pending(0);

   local_irq_enable();

   h = softirq_vec;

   do {
      if (pending & 1) {
         int prev_count = preempt_count();
         kstat_incr_softirqs_this_cpu(h - softirq_vec);

         trace_softirq_entry(h, softirq_vec);
         h->action(h);
         trace_softirq_exit(h, softirq_vec);
         if (unlikely(prev_count != preempt_count())) {
            printk(KERN_ERR "huh, entered softirq %td %s %p"
                   "with preempt_count %08x,"
                   " exited with %08x?\n", h - softirq_vec,
                   softirq_to_name[h - softirq_vec],
                   h->action, prev_count, preempt_count());
            preempt_count() = prev_count;
         }

         rcu_bh_qs(cpu);
      }
      h++;
      pending >>= 1;
   } while (pending);

   local_irq_disable();

   pending = local_softirq_pending();
   if (pending && --max_restart)
      goto restart;

   if (pending)
      wakeup_softirqd();

   lockdep_softirq_exit();

   account_system_vtime(current);
   _local_bh_enable();
}

猜你喜欢

转载自blog.csdn.net/hz5034/article/details/80944160