Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Function parameter passing in a Linux kernel interrupt handler (from asm to C)

When I read the Linux kernel source, I came across this piece of code:

__visible void __irq_entry smp_apic_timer_interrupt(struct pt_regs *regs)   
{
    struct pt_regs *old_regs = set_irq_regs(regs);

    entering_ack_irq();
    local_apic_timer_interrupt();
    exiting_irq();

    set_irq_regs(old_regs);
}

The function smp_apic_timer_interrupt() takes one parameter. The calling of this function is by a piece of assembly language code:

ENTRY(apic_timer_interrupt)
     RING0_INT_FRAME;     
     ASM_CLAC;           
     pushl_cfi $~(0xef);
     SAVE_ALL;         
     TRACE_IRQS_OFF
     movl %esp,%eax;
     call smp_apic_timer_interrupt; // <------call high level C function       
     jmp ret_from_intr;      
     CFI_ENDPROC;           
ENDPROC(apic_timer_interrupt)

I cannot figure out how the high level C function smp_apic_timer_interrupt() get its parameter (by which register)?

like image 745
B.S.Gao Avatar asked Nov 26 '15 07:11

B.S.Gao


People also ask

How does interrupt handler work in Linux?

An interrupt is simply a signal that the hardware can send when it wants the processor's attention. Linux handles interrupts in much the same way that it handles signals in user space. For the most part, a driver need only register a handler for its device's interrupts, and handle them properly when they arrive.

Can interrupt handler be interrupted?

However, such kernel control paths may be arbitrarily nested; an interrupt handler may be interrupted by another interrupt handler, thus giving raise to a nested execution of kernel threads.

What are the data structures used to obtain the interrupt handler of an interrupt?

Each irqaction data structure contains information about the handler for this interrupt, including the address of the interrupt handling routine. As the number of interrupts and how they are handled varies between architectures and, sometimes, between systems, the Linux interrupt handling code is architecture specific.


2 Answers

Your are probably thinking normal calling convention (arguments on the stack). Modern Linux kernels (32-bit variants) pass the first 3 parameters in registers (EAX, ECX, EDX) as an optimization. Depending on the kernel this convention is specified as an attribute modifier on the functions using __attribute__(regparm(3)), or modern versions of the kernel pass -mregparm=3 option to GCC on the command line. The GCC documentation says this about that option/attribute:

regparm (number)

On the Intel 386, the regparm attribute causes the compiler to pass up to
number integer arguments in registers EAX, EDX, and ECX instead of on the
stack. Functions that take a variable number of arguments will continue to
be passed all of their arguments on the stack. 

In ancient kernels the normal 32-bit ABI (and convention of arguments on the stack) was the norm. Eventually the kernel configuration supported arguments in registers OR the normal stack convention via the CONFIG_REGPARM setting in the kernel build configuration:

config REGPARM
    bool "Use register arguments"
    default y
    help
    Compile the kernel with -mregparm=3. This instructs gcc to use
    a more efficient function call ABI which passes the first three
    arguments of a function call via registers, which results in denser
    and faster code.

    If this option is disabled, then the default ABI of passing
    arguments via the stack is used.

    If unsure, say Y.

The Linux kernel maintainers got rid of this option in 2006 with this kernel commit:

-mregparm=3 has been enabled by default for some time on i386, and AFAIK
there aren't any problems with it left.

This patch removes the REGPARM config option and sets -mregparm=3
unconditionally.

Based on this knowledge one can look at the code you have presented and assume we are on a kernel where it has defaulted to the first 3 parameters being passed in registers. In your case:

 __visible void __irq_entry smp_apic_timer_interrupt(struct pt_regs *regs)

has one parameter so it is passed in EAX . The code that called smp_apic_timer_interrupt looked like this:

ENTRY(apic_timer_interrupt)
     RING0_INT_FRAME;     
     ASM_CLAC;           
     pushl_cfi $~(0xef);
     SAVE_ALL;         
     TRACE_IRQS_OFF
     movl %esp,%eax;
     call smp_apic_timer_interrupt; // <------call high level C function       
     jmp ret_from_intr;      
     CFI_ENDPROC;           
ENDPROC(apic_timer_interrupt)

The important part is that the SAVE_ALL macro call pushes all the required registers on the stack. It will vary from version to version of the kernel, but the main effect of pushing registers on the stack is similar (I've removed the DWARF entries for brevity):

.macro SAVE_ALL
         cld
         PUSH_GS
         pushl_cfi %fs
         pushl_cfi %es
         pushl_cfi %ds
         pushl_cfi %eax
         pushl_cfi %ebp
         pushl_cfi %edi
         pushl_cfi %esi
         pushl_cfi %edx
         pushl_cfi %ecx
         pushl_cfi %ebx
         movl $(__USER_DS), %edx
         movl %edx, %ds
         movl %edx, %es
         movl $(__KERNEL_PERCPU), %edx
         movl %edx, %fs
         SET_KERNEL_GS %edx
.endm

When completed ESP will point to the location where the last register was pushed. That address is copied to EAX with movl %esp,%eax, and EAX becomes the pointer for struct pt_regs *regs . All the pushed registers on the stack become the actual pt_regs data structure, and EAX now points to it.

The asmlinkage macro will be found in the kernel for those functions that require arguments to be passed on the stack the conventional way. It is defined as something like:

#define asmlinkage CPP_ASMLINKAGE __attribute__((regparm(0)))

Where regparm(0) says that no parameters will be passed via registers.

One really has to know what the build options are, and the version of the kernel being used to make an accurate assessment of the convention being used.

like image 85
Michael Petch Avatar answered Nov 11 '22 13:11

Michael Petch


Quoting from https://www.safaribooksonline.com/library/view/understanding-the-linux/0596005652/ch04s06.html

The SAVE_ALL macro expands to the following fragment:

cld
push %es
push %ds
pushl %eax
pushl %ebp
pushl %edi
pushl %esi
pushl %edx
pushl %ecx
pushl %ebx
movl $ _ _USER_DS,%edx
movl %edx,%ds
movl %edx,%es

After saving the registers, the address of the current top stack location is saved in the eax register [with movl %esp,%eax, so that ] eax points to the stack location containing the last register value pushed on by SAVE_ALL

So the eax register is the register through which smp_apic_timer_interrupt receives the pt_regs pointer.

like image 45
Michael Avatar answered Nov 11 '22 12:11

Michael