GSoC 2013 - RTEMS Virtualization Layer Project

Progress, Issues, Design

POK Hardware Interrupt Handling

I finished a first working version of the new interrupt handling for the hardware interrupts on POK. I did not change the handling of system-calls or exceptions.

In this post I will describe the old handling system, the dedicated callback functions for the IDT, a general ISR handler, the meta handler structure and the control flow for registering a handler and an interruption.

The old handler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#define INTERRUPT_HANDLER(name)                        \
void name (void);                            \
void name##_handler(interrupt_frame* frame);             \
  asm (                                              \
      ".global "#name "           \n"                \
      "\t.type "#name",@function  \n"                \
      #name":               \n"                \
      "cli          \n"                \
      "subl $4, %esp            \n"                \
      "pusha                \n"                \
      "push %ds             \n"                \
      "push %es             \n"                \
      "push %esp            \n"                \
      "mov $0x10, %ax           \n"                \
      "mov %ax, %ds         \n"                \
      "mov %ax, %es         \n"                \
      "call " #name"_handler      \n"                \
      "call update_tss          \n"                \
      "addl $4, %esp            \n"                \
      "pop %es              \n"                \
      "pop %ds              \n"                \
      "popa             \n"                \
      "addl $4, %esp            \n"                \
      "sti          \n"                \
      "iret             \n"                \
      );                             \
void name##_handler(interrupt_frame* frame)

/* --------------------------------- */

INTERRUPT_HANDLER(pit_interrupt)
{
   (void) frame;
   pok_pic_eoi (PIT_IRQ);
   CLOCK_HANDLER
}

For the non ASM gurus: The code at the bottom is invoking the define at the top. The define then replaces INTERRUPT_HANDLER(..). The name – in this case pit_interrupt – is used to build new functions: pit_interrupt(void) and pit_interrupt_handler(..).

The ASM code describes the pit_interrupt function. When an interrupt occurs the CPU pushes several registers on the stack, then the code above takes over and pushes several registers on the stack too. At the end, the current stack-pointer is pushed and pit_interrupt_handler is called.

Per definition pit_interrupt_handler takes a interrupt_frame pointer as an argument. This pointer is passed on the stack, so “push %esp” is the “interrupt_frame*”. Now the function body at the bottom is executed and afterwards control jumps back to “call update_tss”.

Afterwards the stack is cleaned up and iret jumps back to the point of execution, when the interrupt occurred.

Dedicated prologue functions

The IDT won’t tell you the interrupt line number of the current interrupt, so the callback function registered with the IDT must know the interrupt number. To generate these functions for the 16 IRQ lines I had a look at RTEMS.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#define DISTINCT_INTERRUPT_ENTRY(_vector)    \
 .p2align 4              ; \
 .globl pok_irq_prologue_ ## _vector     ; \
pok_irq_prologue_ ## _vector:          \
 cli               ; \
 subl  $4, %esp            ; \
 pusha                 ; \
 push  %ds             ; \
 push  %es             ; \
        push  %esp             ; \
 mov   $0x10, %ax          ; \
        mov   %ax, %ds             ; \
        mov   %ax, %es             ; \
 movl  $ _vector, %eax         ; \
 jmp   _ISR_Handler        ; \


DISTINCT_INTERRUPT_ENTRY(0)

This code generates a function for e.g. vector 0 and does exactly the same as the old code – building the interrupt_frame – before it jumps to ISR_Handler. The ISR handler then pushes the vector argument in %eax to the stack and passes control to C_isr_handler.

After the C handler is done the vector number is removed from the stack and update_tss is called. From there on the code is equal to the old handler code above – just a bit better documented.

Meta Handler Structure

The _C_isr_handler is a lookup function, indexing a table with the vector number and if registered invoking the handler for the vector of the current partition.

1
2
3
4
5
void _C_isr_handler( unsigned vector, interrupt_frame *frame );
{
  if( handler_table[vector].handler[POK_SCHED_CURRENT_PARTITION] != NULL )
    handler_table[vector].handler[POK_SCHED_CURRENT_PARTITION](vector, (void*)frame);
}

At the current point in time there is no counting of interrupts for partitions not currently executing. For them the interrupt never occurred. This will change in the future.

1
2
3
4
5
6
struct meta_handler
{
  unsigned vector;
  void (*handler[POK_CONFIG_NB_PARTITIONS])(unsigned, void*);
};
typedef struct meta_handler meta_handler;

The meta handler is a struct consisting of the vector number and an array of handlers for each partition. The meta handler is initialized at startup and space for the tables is allocated.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
pok_ret_t pok_meta_handler_init( void )
{
  int i, j;
  for( i = 0; i < 16; i++ )
  {
    meta_handler init;
    init.vector = 0xFFF; /* magic number > IDT_SIZE */
    for( j = 0; j < POK_CONFIG_NB_PARTITIONS; j++ )
      init.handler[j] = NULL; /* No handlers present */
    handler_table[i] = init;
  }

  return (POK_ERRNO_OK);
}

I would love to have the ability for function overloading, but it’s plain C and not C++.

Hereby I introduce a new function to register the hardware interrupts. It checks if the irq number is in range, registers the handler, sets the meta_handlers vector number and registers the event. The offset of 32 is necessary, as the first 32 interrupts are reserved by Intel.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
pok_ret_t pok_bsp_irq_register_hw (uint8_t   irq,
                 void      (*irq_handler)(unsigned, void*))
{
  if( irq > 15 )
    return (POK_ERRNO_EINVAL);

  pok_pic_unmask (irq);

  /* magic number: set in pok_meta_handler_init()
   * meaning: vector not yet used */
  if( handler_table[irq].vector == 0xFFF )
    handler_table[irq].vector = irq;

  handler_table[irq].handler[POK_SCHED_CURRENT_PARTITION] = irq_handler;

  pok_arch_event_register (32 + irq, NULL);

  return (POK_ERRNO_OK);
}

Registering an Interrupt Handler

To register a new handler for on of the 16 hardware interrupts, you need to write a handler function as before, but without the macro.

1
2
3
4
5
6
7
void pit_ISR (unsigned vector, void *frame)
{
   frame = (interrupt_frame*) frame;
   (void) vector;
   pok_pic_eoi (PIT_IRQ);
   CLOCK_HANDLER
}

It has to have two arguments for the vector and the interrupt_frame pointer. This is a void pointer due to dependencies in bsp.h file. But as the old handler macro was a bit of magic, too, I guess this is okay and documented.

To register this interrupt handler you invoke:

1
pok_bsp_irq_register_hw (PIT_IRQ, pit_ISR);

with PIT_IRQ being 0x0 and pit_ISR – guess it or look above.

The rest is stated in the previous sections of this post.

Occurrence of an Interrupt

Quick and easy.

  • IRQ 0 occurs.
  • IDT lookup –> pok_interrupt_prologue_0
  • _ISR_Handler
  • _C_isr_handler lookup –> Meta_handler[0].handler[POK_CURRENT_PARTITION]
  • current partition handler
  • Update_tss
  • stack cleanup
  • iret

So that’s it. And the only magic going on here is the cast to void and cast back to interrupt_frame in the handler function.

If you like you can have a look at the source code on github