Linux kernel learning 6 -- code analysis and application of the upper part of interrupt

Here to show you a simple interrupt program in Linux

1, View interrupt

The common application of interrupt is in the driver, such as our keyboard and mouse. Every time we click, an interrupt will be generated, so that the computer can recognize it. The experiment is to virtualize a device, register the device in the system, and then use the interrupt service routine written by yourself. First, take a look at the interrupt in the current system.

input

cat /proc/interrupts

This is the current interrupt in our system

Column 1: IRQ serial number

CPU0/CPU1/CPU3/CPU4 are the number of interrupts on the current CPU

Next column: interrupt controller name, such as IO-APIC

Last column: device name, such as timer

2, Write interrupt program

From the knowledge of interrupt, we can know that if we want to write an interrupt program, we must apply for a medium break line, and an interrupt line corresponds to an IRQ number. Here, we choose to use No. 1 interrupt line and i8042 to share a medium break line.

Here we take a look at our complete interrupt program interrupt.c

# include <linux/kernel.h>
# include <linux/init.h>
# include <linux/module.h>
# include <linux/interrupt.h>

static int irq;			//irq number		
static char * devname;		//Equipment name	
			
//These two are used to let us pass in parameters on the command line
module_param(irq,int,0644);
module_param(devname,charp,0644);    //Here, charp is equivalent to char *, which is a character pointer
			
struct myirq
{
    int devid;     //This is mainly used in shared irq
};

struct myirq mydev={1119};
		
//Interrupt handling function
static irqreturn_t myirq_handler(int irq,void * dev)
{
    struct myirq mydev;
    static int count=1;
    mydev = *(struct myirq*)dev;		
    printk("key: %d..\n",count);
    printk("devid:%d ISR is working..\n",mydev.devid);
    printk("ISR is leaving......\n");
    count++;
    return IRQ_HANDLED;
}


//Kernel module initialization function
static int __init myirq_init(void)    //The most important work is to register the interrupt line, register the interrupt service routine written by yourself, and use request_irq complete
{
    printk("Module is working...\n");
    if(request_irq(irq,myirq_handler,IRQF_SHARED,devname,&mydev)!=0)
    {
        printk("%s request IRQ:%d failed..\n",devname,irq);
        return -1;
    }
    printk("%s request IRQ:%d success...\n",devname,irq);
    return 0;
}

//Kernel module exit function
static void __exit myirq_exit(void)
{
    printk("Module is leaving...\n");
    free_irq(irq,&mydev);            //Logoff function
    printk("Free the irq:%d..\n",irq);
}

MODULE_LICENSE("GPL");
module_init(myirq_init);
module_exit(myirq_exit);

Next, read it

request_irq is in the kernel source code include/linux/interrupt.h

request_ The first parameter IRQ of IRQ is the interrupt number, which corresponds to the number of IRQ on the interrupt controller

The second parameter handler, that is, the interrupt service routine we want to register, needs to be implemented by ourselves. It has a specific type of return value irqreturn_t. This type can be found in the irqreturn.h file

The next parameter, flags, specifies the processing attributes of fast interrupts or interrupts in interrupt sharing. Here, the value of flags is IRQ_SHARED, that is, sharing is allowed.

Parameter name: it is the device name, that is, the last column we just used in the cat /proc/interrupt command to view

The parameter dev refers to dev_id, mainly used to share interrupt lines. Note that the type of this parameter is void, that is, it can be cast to any type. It can be used as a parameter to distinguish interrupts when sharing interrupts.

Enter request_ The IRQ function actually calls request_threaded_irq function. This function is in kerenl/irq/manage.c.

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;
 
	if (irq == IRQ_NOTCONNECTED)
		return -ENOTCONN;
 
	/*
	 * 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).
	 *
	 * Also IRQF_COND_SUSPEND only makes sense for shared interrupts and
	 * it cannot be set along with IRQF_NO_SUSPEND.
	 */
	if (((irqflags & IRQF_SHARED) && !dev_id) ||
	    (!(irqflags & IRQF_SHARED) && (irqflags & IRQF_COND_SUSPEND)) ||
	    ((irqflags & IRQF_NO_SUSPEND) && (irqflags & IRQF_COND_SUSPEND)))
		return -EINVAL;
 
	desc = irq_to_desc(irq);
	if (!desc)
		return -EINVAL;
 
	if (!irq_settings_can_request(desc) ||
	    WARN_ON(irq_settings_is_per_cpu_devid(desc)))
		return -EINVAL;
 
	if (!handler) {
		if (!thread_fn)
			return -EINVAL;
		handler = irq_default_primary_handler;
	}
 
	action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
	if (!action)
		return -ENOMEM;
 
	action->handler = handler;
	action->thread_fn = thread_fn;
	action->flags = irqflags;
	action->name = devname;
	action->dev_id = dev_id;
 
	retval = irq_chip_pm_get(&desc->irq_data);
	if (retval < 0) {
		kfree(action);
		return retval;
	}
 
	retval = __setup_irq(irq, desc, action);
 
	if (retval) {
		irq_chip_pm_put(&desc->irq_data);
		kfree(action->secondary);
		kfree(action);
	}
 
#ifdef CONFIG_DEBUG_SHIRQ_FIXME
	if (!retval && (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;
}

irqreturn_t in the kernel source code include/linux/irqreturn.h

/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _LINUX_IRQRETURN_H
#define _LINUX_IRQRETURN_H
 
/**
 * enum irqreturn
 * @IRQ_NONE		interrupt was not from this device or was not handled
 * @IRQ_HANDLED		interrupt was handled by this device
 * @IRQ_WAKE_THREAD	handler requests to wake the handler thread
 */
enum irqreturn {
	IRQ_NONE		= (0 << 0),
	IRQ_HANDLED		= (1 << 0),
	IRQ_WAKE_THREAD		= (1 << 1),
};
 
typedef enum irqreturn irqreturn_t;
#define IRQ_RETVAL(x)	((x) ? IRQ_HANDLED : IRQ_NONE)
 
#endif

request_threaded_irq function. This function is in kerenl/irq/manage.c

Here we first define two very important structures, irqaction *action and irq_desc, then make a series of judgments, and then execute "desc = irq_to_desc(irq);". This line is based on the interrupt number IRQ, in irq_desc array, return a specific irq_desc, in fact, our registration is to generate actions one by one from the parameters we pass in, and then add them to irq_desc, specifically through the "retval = _setup_irq (IRQ, DESC, action);" function.

action->handler = handler;
action->thread_fn = thread_fn;
action->flags = irqflags;
action->name = devname;
action->dev_id = dev_id;

Since there is registration, there will be a corresponding logoff function. In interrupt.c, we put the logoff function in the exit function, free_irq (IRQ, & mydev);

The free_irq function is still in the manage.c file. It mainly performs action = _free_irq (DESC, dev_id); and then releases action, kfree(action);

const void *free_irq(unsigned int irq, void *dev_id)
{
	struct irq_desc *desc = irq_to_desc(irq);
	struct irqaction *action;
	const char *devname;
 
	if (!desc || WARN_ON(irq_settings_is_per_cpu_devid(desc)))
		return NULL;
 
#ifdef CONFIG_SMP
	if (WARN_ON(desc->affinity_notify))
		desc->affinity_notify = NULL;
#endif
 
	action = __free_irq(desc, dev_id);
 
	if (!action)
		return NULL;
 
	devname = action->name;
	kfree(action);
	return devname;
}

Next, look at the interrupt service routine.

//Interrupt handling function
static irqreturn_t myirq_handler(int irq,void * dev)
{
    struct myirq mydev;
    static int count=1;
    mydev = *(struct myirq*)dev;		
    printk("key: %d..\n",count);
    printk("devid:%d ISR is working..\n",mydev.devid);
    printk("ISR is leaving......\n");
    count++;
    return IRQ_HANDLED;
}

In interrupt.c, the interrupt service routine is named myirq_handler, and the corresponding name should be consistent with the name used in registration. In myirq_handler, it is actually a count, that is, when an interrupt is entered, the count will increase by 1, and then IRQ_HANDLER will be returned. As mentioned earlier, the returned value represents that an accurate interrupt signal has been received and processed correctly So far, the module has been written.

3, Insert module

After that, insert the module. As long as an interrupt occurs on interrupt line 1, whoever sends it will execute our interrupt service routine.

Makefile files are as follows:

book@100ask:~/Mooc/CH05$ cat Makefile
# Makefile file note: add the previous. c file and name it first.c, so the. o file in the makefile file here should be named first.o
# # Only the root user can load and unload modules
obj-m:=interrupt.o                                     # Generate the target file of the first module
# #The object file should be the same as the module name
CURRENT_PATH:=$(shell pwd)
LINUX_KERNEL:=$(shell uname -r)
LINUX_KERNEL_PATH:=/usr/src/linux-headers-$(LINUX_KERNEL)
#
all:
        make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules     # Compilation module
#       #[TAB] the path of the kernel. Where is the current directory after compilation? It indicates that the kernel module is compiled
#
clean:
        make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean     # Cleaning module

make compile first

Insert the module and assign irq and devname values

sudo insmod interrupt.ko irq=1 devname=myirq 

View interruption

cat /proc/interrupts 

It is found that we have connected the device myirq to line 1
Let's take a look at the log information dmesg

You can see that the program runs correctly, and some information will be displayed every time you enter an interrupt

Exit module

sudo rmmod interrupt

The module has exited correctly

4, Analyze kernel source code

This experiment is just a simple experience of the interrupt program. In fact, we can verify what we have learned in theory from the source code. What is the process from the occurrence of an interrupt to the actual execution of our interrupt service routine

This is actually a very complex process. If we want to trace the source, we can start from the initialization of the kernel. In fact, this process is very interesting. We can find the main function in our kernel, from initializing our interrupt to actually executing our interrupt service routine. Here we start from the do_IRQ function.

Every time we enter the do_IRQ function, we jump in from the compiled code. It can be said that after an interrupt occurs, do_IRQ is the first C language function we execute, and we start to analyze it here. The code here is closely related to the architecture. Our do_IRQ function is located in arch/x86/kernel/irq.c under the root directory

The core part of this function is to execute the handle_irq function, enter from handle_irq, and return to exiting_irq(),

/*
 * do_IRQ handles all normal device IRQ's (the special    //do_IRQ Handle interrupts for all common devices 
 * SMP cross-CPU interrupts have their own specific
 * handlers).
 */
__visible unsigned int __irq_entry do_IRQ(struct pt_regs *regs)
{
	struct pt_regs *old_regs = set_irq_regs(regs);
	struct irq_desc * desc;
	/* high bit used in ret_from_ code  */
	unsigned vector = ~regs->orig_ax;
	entering_irq();
	/* entering_irq() tells RCU that we're not quiescent.  Check it. */
	RCU_LOCKDEP_WARN(!rcu_is_watching(), "IRQ failed to wake up RCU");
 
	desc = __this_cpu_read(vector_irq[vector]);
 
	if (!handle_irq(desc, regs)) {
		ack_APIC_irq();
 
		if (desc != VECTOR_RETRIGGERED && desc != VECTOR_SHUTDOWN) {
			pr_emerg_ratelimited("%s: %d.%d No irq handler for vector\n",
					     __func__, smp_processor_id(),
					     vector);
		} else {
			__this_cpu_write(vector_irq[vector], VECTOR_UNUSED);
		}
	}
 
	exiting_irq();
 
	set_irq_regs(old_regs);
	return 1;
}

Enter arch/x86/kernel/irq_64.c and find that in the handle_irq function, the generic_handle_irq_desc() function is finally executed. The passed in parameter is DESC, that is, the parameter of struct irq_desc type,

bool handle_irq(struct irq_desc *desc, struct pt_regs *regs)
{
	if (IS_ERR_OR_NULL(desc))
		return false;
	
 
	generic_handle_irq_desc(desc);
	return true;
}

Enter / include/linux/irqdesc.h and find that generic_handle_irq_desc finally needs to execute the handel_irq function under desc,

static inline void generic_handle_irq_desc(struct irq_desc *desc)
{
	desc->handle_irq(desc);
}

It should be noted that the function here is the handel_irq function, which is in the irq_desc structure,

hadle_irq is also a callback function, which is responsible for handling the underlying details, such as interrupt confirmation, edge trigger and level trigger. Finally, this function calls the handler in irqaction,

In fact, the handler is the last interrupt service routine we want.

Here, take the registration of APIC as an example to see the handle_ Which function is called back in IRQ.

Let's go to / arch/x86/kernel/apic/io_apic.c, find the handle here_ The mounting process of IRQ.

In the last two lines of the following functions, you can see the selection method here (HDL = fasteoi? Handle_fasteoi_irq: handle_edge_irq;). You will follow the handle_ edge_ IRQ mode, that is, edge trigger mode.

What is actually being done is__ irq_set_handler function, let's enter this function

static void mp_register_handler(unsigned int irq, unsigned long trigger)
{
	irq_flow_handler_t hdl;
	bool fasteoi;
 
	if (trigger) {
		irq_set_status_flags(irq, IRQ_LEVEL);
		fasteoi = true;
	} else {
		irq_clear_status_flags(irq, IRQ_LEVEL);
		fasteoi = false;
	}
 
	hdl = fasteoi ? handle_fasteoi_irq : handle_edge_irq;
	__irq_set_handler(irq, hdl, 0, fasteoi ? "fasteoi" : "edge");
}

/kernel/irq/chip.c

void
__irq_set_handler(unsigned int irq, irq_flow_handler_t handle, int is_chained,
		  const char *name)
{
	unsigned long flags;
	struct irq_desc *desc = irq_get_desc_buslock(irq, &flags, 0);
 
	if (!desc)
		return;
 
	__irq_do_set_handler(desc, handle, is_chained, name);
	irq_put_desc_busunlock(desc, flags);
}
EXPORT_SYMBOL_GPL(__irq_set_handler);

The next step is to enter__ irq_do_set_handler function. This function is relatively long. It will have a series of judgments. The key statement is "desc - > handle"_ IRQ = handle ", that is, the handle is mounted on handle_irq.

/kernel/irq/chip.c

static void
__irq_do_set_handler(struct irq_desc *desc, irq_flow_handler_t handle,
		     int is_chained, const char *name)
{
	if (!handle) {
		handle = handle_bad_irq;
	} else {
		struct irq_data *irq_data = &desc->irq_data;
#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY
		/*
		 * With hierarchical domains we might run into a
		 * situation where the outermost chip is not yet set
		 * up, but the inner chips are there.  Instead of
		 * bailing we install the handler, but obviously we
		 * cannot enable/startup the interrupt at this point.
		 */
		while (irq_data) {
			if (irq_data->chip != &no_irq_chip)
				break;
			/*
			 * Bail out if the outer chip is not set up
			 * and the interrupt supposed to be started
			 * right away.
			 */
			if (WARN_ON(is_chained))
				return;
			/* Try the parent */
			irq_data = irq_data->parent_data;
		}
#endif
		if (WARN_ON(!irq_data || irq_data->chip == &no_irq_chip))
			return;
	}
 
	/* Uninstall? */
	if (handle == handle_bad_irq) {
		if (desc->irq_data.chip != &no_irq_chip)
			mask_ack_irq(desc);
		irq_state_set_disabled(desc);
		if (is_chained)
			desc->action = NULL;
		desc->depth = 1;
	}
	desc->handle_irq = handle;
	desc->name = name;
 
	if (handle != handle_bad_irq && is_chained) {
		unsigned int type = irqd_get_trigger_type(&desc->irq_data);
 
		/*
		 * We're about to start this interrupt immediately,
		 * hence the need to set the trigger configuration.
		 * But the .set_type callback may have overridden the
		 * flow handler, ignoring that we're dealing with a
		 * chained interrupt. Reset it immediately because we
		 * do know better.
		 */
		if (type != IRQ_TYPE_NONE) {
			__irq_set_trigger(desc, type);
			desc->handle_irq = handle;
		}
 
		irq_settings_set_noprobe(desc);
		irq_settings_set_norequest(desc);
		irq_settings_set_nothread(desc);
		desc->action = &chained_action;
		irq_activate_and_startup(desc, IRQ_RESEND);
	}
}

Here, take handle_edge_irq as an example,

/kernel/irq/chip.c

In this function, you will find a very important function handle_irq_event(),

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

/kernel/irq/handle.c

The main statement executed by this function is handle_irq_event_percpu,

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

Enter the function handle_irq_event_percpu, which implements the execution of all interrupt service routines on the interrupt line in turn through the macro for_each_action_of_desc. the first to execute the interrupt service routine is "res = action - > handler (IRQ, action - > dev_id); "In this sentence, the corresponding return value is given to res. you can see in the switch statement later that the two parameters of res are" IRQ_WAKE_THREAD: "and" IRQ_HANDLED ", let's take a look at the macro for_each_action_of_desc

/kernel/irq/handle.c

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;
 
		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);
 
			/* Fall through - to add to randomness */
		case IRQ_HANDLED:
			*flags |= action->flags;
			break;
 
		default:
			break;
		}
 
		retval |= res;
	}
 
	return retval;
}

The macro for_each_action_of_desc is located in / kernel / IRQ / internal. H. you can clearly see that this is actually a for loop to traverse. The action in desc is the next action

#define for_each_action_of_desc(desc, act)			\
	for (act = desc->action; act; act = act->next)

Make a summary of the process just mentioned:

First, enter the do_IRQ function (call do_IRQ) in the assembly code

The following corresponds to a 32-bit system

The following corresponds to a 64 bit system

Next, execute do_irq and handle_irq

Finally, the result of executing this function is to execute handle_irq, which corresponds to the following handle_irq function

After that, the function here will call back the previous function. Here, take handle_edge_irq as an example. After calling layer by layer, the interrupt service routine is executed after calling.

This is our whole process


**If the above content is useful to you, please like, pay attention to or collect ~**

Tags: Linux Operation & Maintenance server

Posted on Tue, 07 Dec 2021 01:23:14 -0500 by jeppers