In-depth understanding of LINUX kernel notes Chapter 4 Interrupts and Exceptions

Nested execution of interrupt and exception handlers

https://blog.csdn.net/denglin12315/article/details/121703669

1. History

In earlier versions of the Linux kernel, interrupts were divided into two types:

1. Fast interrupt, when applying, mark IRQF_DISABLED, and new interrupts are not allowed in IRQ HANDLER;

2. Slow interrupts, without the IRQF_DISABLED flag when applying, allow new other interrupts to be nested in the IRQ HANDLER.

In the old Linux kernel, if an interrupt service routine does not want to be interrupted by other interrupts, we can see such code:

request_irq(FLOPPY_IRQ, floppy_interrupt,- IRQF_DISABLED, "floppy", NULL)

2. Now

IRQF_DISABLED was deprecated in the following commit in 2010:

https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=e58aa3d2d0cc

Its commit log clearly explains some risks that interrupt nesting may introduce, such as stack overflow. In other words, starting from this commit, actual Linux no longer supports nesting of interrupts, and there is no concept of fast and slow interrupts, and the IRQF_DISABLED flag is also invalidated . In IRQ HANDLER, regardless of whether an interrupt is set or not set IRQF_DISABLED, the kernel will not enable the CPU to respond to interrupts:

This obsolete IRQF_DISABLED flag has no meaning in the kernel. Later, this flag itself was also deleted in the kernel, completely becoming a thing of the past:

3. Hardware

After the interrupt occurs, the general hardware will automatically block the CPU's response to the interrupt, and at the software level, the interrupt will not be re-enabled until the IRQ HANDLER is completed. For example, for ARM processors, when an exception comes in, the hardware will automatically block the interrupt:

That is to say, when the ARM processor receives an interrupt, it enters interrupt mode, and the IRQ bit of the ARM processor's CPSR register is set by hardware to mask IRQ.

The Linux kernel will reopen CPSR's response to IRQ at the following two times:

1. Return the SOFTIRQ of the bottom half of the interrupt from IRQ HANDLER

2. Return a thread context from IRQ HANDLER

As you can see from 1, SOFTIRQ can respond to interrupts.

exception handling

Linux arm64 system call process learning record

Interrupt handling

The process is similar to exception handling.

el1_irq
->el1_interrupt_handler handle_arch_irq //handle_arch_irq = gic_handle_irq
	->irq_handler	\handler
		->gic_handle_irq
			->handle_domain_irq
				->irq_enter;
					->->preempt_count_add(HARDIRQ_OFFSET);
				->generic_handle_irq_desc;
					-> desc->handle_irq = handle_edge_irq //handle_irq = handle_edge_irq or handle_level_irq ...
						->handle_irq_event
							->handle_irq_event_percpu
							->__handle_irq_event_percpu
								->res = action->handler(irq, action->dev_id);
				->irq_exit;
	->arm64_preempt_schedule_irq //内核抢占

IRQ data structure

The hw_interrupt_type corresponding to arm64 was not found.

Save register value for interrupt handler

Save recovery registers kernel_entry and kernel_exit through
kernel_entry and kernel_exit

For ARM processors, when an exception comes in, the hardware will automatically mask the interrupt:

That is to say, when the ARM processor receives an interrupt, it enters interrupt mode, and the IRQ bit of the ARM processor's CPSR register is set by hardware to mask IRQ.

Here we focus on ERET. Taking arm64 as an example, after calling this instruction, PSTATE restores the value of SPSR_ELn and PC restores the value of ELR_ELn.
https://blog.csdn.net/weixin_42135087/article/details/107227624

ARMv8-A Process State, PSTATE introduction
https://blog.csdn.net/longwang155069/article/details/105204547

do_IRQ

__do_IRQ

These two sections correspond to the following processes

-> desc->handle_irq = handle_edge_irq //handle_irq = handle_edge_irq or handle_level_irq ...
	->handle_irq_event
		->handle_irq_event_percpu
			->__handle_irq_event_percpu
				->res = action->handler(irq, action->dev_id);
void handle_edge_irq(struct irq_desc *desc)
{
    
    
	raw_spin_lock(&desc->lock);

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

	if (!irq_may_run(desc)) {
    
    
		desc->istate |= IRQS_PENDING;
		mask_ack_irq(desc);
		goto out_unlock;
	}

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

	kstat_incr_irqs_this_cpu(desc);

	/* Start handling the irq */
	desc->irq_data.chip->irq_ack(&desc->irq_data);

	do {
    
    
		if (unlikely(!desc->action)) {
    
    
			mask_irq(desc);
			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->istate & IRQS_PENDING)) {
    
    
			if (!irqd_irq_disabled(&desc->irq_data) &&
			    irqd_irq_masked(&desc->irq_data))
				unmask_irq(desc);
		}

		handle_irq_event(desc);

	} while ((desc->istate & IRQS_PENDING) &&
		 !irqd_irq_disabled(&desc->irq_data));

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

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

irqreturn_t handle_irq_event_percpu(struct irq_desc *desc)
{
    
    
	irqreturn_t retval;
	unsigned int flags = 0;

	retval = __handle_irq_event_percpu(desc, &flags);

	add_interrupt_randomness(desc->irq_data.irq, flags);

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

irqreturn_t __handle_irq_event_percpu(struct irq_desc *desc, unsigned int *flags)
{
    
    
	irqreturn_t retval = IRQ_NONE;
	unsigned int irq = desc->irq_data.irq;
	struct irqaction *action;

	record_irq_time(desc);

	for_each_action_of_desc(desc, action) {
    
    
		irqreturn_t res;

		/*
		 * If this IRQ would be threaded under force_irqthreads, mark it so.
		 */
		if (irq_settings_can_thread(desc) &&
		    !(action->flags & (IRQF_NO_THREAD | IRQF_PERCPU | IRQF_ONESHOT)))
			lockdep_hardirq_threaded();

		trace_irq_handler_entry(irq, action);
		res = action->handler(irq, action->dev_id);
		trace_irq_handler_exit(irq, action, res);

		if (WARN_ONCE(!irqs_disabled(),"irq %u handler %pS 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);

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

		default:
			break;
		}

		retval |= res;
	}

	return retval;
}

Rescue lost interrupts

kernel/irq/chip.c

irq_startup
->irq_enable
->check_irq_resend

int check_irq_resend(struct irq_desc *desc, bool inject)
{
    
    
	int err = 0;

	/*
	 * We do not resend level type interrupts. Level type interrupts
	 * are resent by hardware when they are still active. Clear the
	 * pending bit so suspend/resume does not get confused.
	 */
	if (irq_settings_is_level(desc)) {
    
    
		desc->istate &= ~IRQS_PENDING;
		return -EINVAL;
	}

	if (desc->istate & IRQS_REPLAY)
		return -EBUSY;

	if (!(desc->istate & IRQS_PENDING) && !inject)
		return 0;

	desc->istate &= ~IRQS_PENDING;

	if (!try_retrigger(desc))
		err = irq_sw_resend(desc);

	/* If the retrigger was successfull, mark it with the REPLAY bit */
	if (!err)
		desc->istate |= IRQS_REPLAY;
	return err;
}

Soft interrupts and tasklets

ksoftirqd

The code in this section corresponds to the following smpboot_thread_fn

static struct smp_hotplug_thread softirq_threads = {
    
    
	.store			= &ksoftirqd,
	.thread_should_run	= ksoftirqd_should_run,
	.thread_fn		= run_ksoftirqd,
	.thread_comm		= "ksoftirqd/%u",
};

static __init int spawn_ksoftirqd(void)
{
    
    
	cpuhp_setup_state_nocalls(CPUHP_SOFTIRQ_DEAD, "softirq:dead", NULL,
				  takeover_tasklets);
	BUG_ON(smpboot_register_percpu_thread(&softirq_threads));

	return 0;
}

static int smpboot_thread_fn(void *data)
{
    
    
	struct smpboot_thread_data *td = data;
	struct smp_hotplug_thread *ht = td->ht;

	while (1) {
    
    
		set_current_state(TASK_INTERRUPTIBLE);
		preempt_disable();
		
		...
		
		if (!ht->thread_should_run(td->cpu)) {
    
    
			preempt_enable_no_resched();
			schedule();
		} else {
    
    
			__set_current_state(TASK_RUNNING);
			preempt_enable();
			ht->thread_fn(td->cpu);
		}
	}

static void run_ksoftirqd(unsigned int cpu)
{
    
    
	local_irq_disable();
	if (local_softirq_pending()) {
    
    
		/*
		 * We can safely run softirq on inline stack, as we are not deep
		 * in the task stack here.
		 */
		__do_softirq();
		local_irq_enable();
		cond_resched();
		return;
	}
	local_irq_enable();
}

Guess you like

Origin blog.csdn.net/a13821684483/article/details/127983465