Ejecución anidada de controladores de interrupciones y excepciones.
https://blog.csdn.net/denglin12315/article/details/121703669
1. Historia
En versiones anteriores del kernel de Linux, las interrupciones se dividían en dos tipos:
1. Interrupción rápida, al aplicar, marque IRQF_DISABLED y no se permiten nuevas interrupciones en IRQ HANDLER;
2. Las interrupciones lentas, sin el indicador IRQF_DISABLED cuando se aplica, permiten que se aniden otras interrupciones nuevas en IRQ HANDLER.
En el antiguo kernel de Linux, si una rutina de servicio de interrupción no quiere ser interrumpida por otras interrupciones, podemos ver el siguiente código:
request_irq(FLOPPY_IRQ, disquete_interrupt,- IRQF_DISABLED, "disquete", NULL)
2. Ahora
IRQF_DISABLED quedó obsoleto en la siguiente confirmación en 2010:
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=e58aa3d2d0cc
Su registro de confirmación explica claramente algunos riesgos que puede introducir la interrupción del anidamiento, como el desbordamiento de la pila, etc. En otras palabras, a partir de esta confirmación, Linux real ya no admite el anidamiento de interrupciones, no existe el concepto de interrupciones rápidas y lentas, y el indicador IRQF_DISABLED también se invalida . En IRQ HANDLER, ya sea que se establezca una interrupción o no se establezca IRQF_DISABLED, el kernel no habilitará la respuesta de la CPU a las interrupciones:
Este indicador IRQF_DISABLED invalidado no tiene significado en el kernel. Más tarde, la marca en sí fue eliminada del kernel y pasó a ser cosa del pasado:
3. Hardware
Después de que ocurre una interrupción, el hardware general bloqueará automáticamente la respuesta de la CPU a la interrupción y, a nivel de software, la interrupción no se volverá a habilitar hasta que se complete IRQ HANDLER. Por ejemplo, para los procesadores ARM, cuando llega una excepción, el hardware enmascarará automáticamente la interrupción:
Es decir, cuando el procesador ARM recibe una interrupción, ingresa al modo de interrupción y el hardware configura el bit IRQ del registro CPSR del procesador ARM para enmascarar la IRQ.
El kernel de Linux reabrirá la respuesta de CPSR a IRQ en los dos momentos siguientes:
1. Devuelve el SOFTIRQ de la mitad inferior de la interrupción de IRQ HANDLER
2. Devolver un contexto de hilo desde IRQ HANDLER
Como puede ver en 1, SOFTIRQ puede responder a las interrupciones.
Manejo de excepciones
Registro de aprendizaje del proceso de llamada del sistema Linux arm64
Manejo de interrupciones
El proceso es similar al manejo de excepciones.
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 //内核抢占
Estructura de datos IRQ
No se encontró el hw_interrupt_type correspondiente a arm64.
Guardar valor de registro para el controlador de interrupciones
Guarde los registros de recuperación kernel_entry y kernel_exit a través de
kernel_entry y kernel_exit
Para los procesadores ARM, cuando ocurre una excepción, el hardware enmascarará automáticamente la interrupción:
Es decir, cuando el procesador ARM recibe una interrupción, ingresa al modo de interrupción y el hardware configura el bit IRQ del registro CPSR del procesador ARM para enmascarar la IRQ.
Aquí nos centramos en ERET. Tomando arm64 como ejemplo, después de llamar a esta instrucción, PSTATE restaura el valor de SPSR_ELn y la PC restaura el valor de ELR_ELn.
https://blog.csdn.net/weixin_42135087/article/details/107227624
Estado del proceso ARMv8-A, introducción a PSTATE
https://blog.csdn.net/longwang155069/article/details/105204547
hacer_IRQ
__do_IRQ
Estas dos secciones corresponden a los siguientes procesos
-> 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;
}
Rescatar interrupciones perdidas
núcleo/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;
}
Interrupciones suaves y tasklets
ksoftirqd
El código de esta sección corresponde al siguiente 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();
}