mov eax , cr0
or eax , 0x01
mov cr0 , eax



back to months list

Project : The "Microkernel" Operating System

Journal Entry Date : 2024.11.27

Today I changed the interrupt routine system (finally!) I added the architecture-dependent interrupt wrapper to all the general interrupts, now making the entire interrupt routine fully independent of the architecture.

The kernel now has 256 pre-declared interrupt handlers that work as the pre-declared entry point for all the registered interrupt numbers. If we make the pre-declared interrupt wrappers to call architecture-specific wrapping function, we can finally "generalize" the interrupt routine across the architectures.

/* In kernel/src/interrupt/predeclared_interrupt_handlers.cpp */

interrupt_handler_t interrupt::handler::get_general_int_wrapper(int index) {
    INTERRUPT_GENERAL_INT_WRAPPER_ARRAY
    if(index >= INTERRUPT_GENERAL_INT_WRAPPER_MAXCOUNT) return 0x00;
    return general_int_wrapper_array[index];
}

INTERRUPT_GENERAL_INT_WRAPPER_HANDLER_FUNCTION(0)
INTERRUPT_GENERAL_INT_WRAPPER_HANDLER_FUNCTION(1)
INTERRUPT_GENERAL_INT_WRAPPER_HANDLER_FUNCTION(2)
INTERRUPT_GENERAL_INT_WRAPPER_HANDLER_FUNCTION(3)
INTERRUPT_GENERAL_INT_WRAPPER_HANDLER_FUNCTION(4)
...
INTERRUPT_GENERAL_INT_WRAPPER_HANDLER_FUNCTION(255)

You can also see that I placed the declaration of interrupt handler's wrapper function on the architecture-dependent side of project:

/* In arch/x86_64/include/x86_64/interrupt_wrapper.hpp */ 
#ifndef _INTERRUPT_WRAPPER_HPP_
#define _INTERRUPT_WRAPPER_HPP_

#include <kernel/types.hpp>
#include <kernel/interrupt/interrupt.hpp>
#include <kernel/interrupt/predeclared_interrupt_handlers.hpp>

#define INTERRUPT_START \
/* architecture-specific implementation of the start of interrupt */

#define INTERRUPT_END \
/* architecture-specific implementation of the end of interrupt */

#define INTERRUPT_GENERAL_INT_WRAPPER_HANDLER_FUNCTION(handler_num) \
/* architecture-specific implementation of interrupt wrapper function */
/* purpose: to call the actual interrupt handler stored in the interrupt manager */

#define INTERRUPT_HARDWARE_SPECIFIED_WRAPPER_HANDLER_FUNCTION(handler_num) \
/* architecture-specific implementation of interrupt wrapper function */
/* purpose: to call the actual interrupt handler stored in the interrupt manager */

#endif 

This is the very general form of it, but practical implementation quickly becomes nasty.

The kernel requires only the implementation of INTERRUPT_GENERAL_INT_WRAPPER_HANDLER_FUNCTION and INTERRUPT_HARDWARE_SPECIFIED_WRAPPER_HANDLER_FUNCTION. (INTERRUPT_START and INTERRUPT_END macros are just there to make the codes more concise and simplified.)

INTERRUPT_GENERAL_INT_WRAPPER_HANDLER_FUNCTION

& INTERRUPT_HARDWARE_SPECIFIED_WRAPPER_HANDLER_FUNCTION

(Note that those two functions must be naked/interrupt-specific functions.)

Objective (Sequentially) :

  1. Save all the registers into the stack
  2. pass the saved registers to the real interrupt handler function (make sure to pass it as pointer so that the main handler can change the values of interrupts for context switching)
  3. Fetch the real interrupt handler function and call it
  4. Send the interrupt controller the EOI(End-Of-Interrupt) signal
  5. Load all the register data from the stack into the registers and exit the interrupt

It's very straightforward. The only problem for me was that I had to tinker manually with stack addresses, which I'm not that bright at. Either way, I somehow managed to implement this entire process! It only took like 200 lines.. haha. Because it was a naked function, the C++ variables that are declared were all naked registers not the stacks, so I had bit of problem dealing with local variables without mangling the original register values...

#define INTERRUPT_GENERAL_INT_WRAPPER_HANDLER_FUNCTION(handler_num) \
__attribute__ ((naked)) void interrupt::handler::general_wrapper##handler_num(void) {\
    INTERRUPT_START \
    interrupt_handler_t handler;\
    if((handler = interrupt::general::get_interrupt_handler(handler_num)) == 0x00) {\
        debug::out::printf("Unhandled interrupt %d\n" , handler_num);\
    }\
    else { \
        qword regs_ptr; \
        __asm__ ("mov %0 , rsi":"=r"(regs_ptr)); \
        handler((struct Registers *)regs_ptr); \
    } \
    interrupt::controller::interrupt_received(handler_num);\
    INTERRUPT_END \
}

I'll walk you through the designs. First, you obviously call the INTERRUPT_START macro(which I will explain below.)

  1. INTERRUPT_START initializes the interrupt routine, saves all the registers(including CS, RIP, RFlags, RSP that are stored in IST) into stack, and temporarily push the general registers into stack, in case they are used on the main interrupt routine.
  2. After calling INTERRUPT_START, we use interrupt manager to fetch the real interrupt handler function that needs to be called.
  3. And after that, things get bit interesting. Prior in the INTERRUPT_START macro, it stored the pointer to the entire register structure in the RSI register. Thus, we declare one local variable, store the pointer of register structure, and pass it onto the handler function. Simple!
  4. Finally, we now call the interrupt controller for EOI signal,
  5. And end the interrupt routine.
#define INTERRUPT_START \
/* qword regs_ptr; */ /* use RSI register for regs_ptr */  \
IA ("push rax"); \
IA ("mov rax , [rsp+(8*1)]"); /* Save RIP*/ \
IA ("mov [rsp+%c0] , rax"::"i"(-sizeof(struct Registers)+offsetof(struct Registers , rip))); \
IA ("mov rax , [rsp+(8*2)]"); /* Save CS */ \
IA ("mov [rsp+%c0] , rax"::"i"(-sizeof(struct Registers)+offsetof(struct Registers , cs))); \
IA ("mov rax , [rsp+(8*3)]"); /* Save RFlags */ \
IA ("mov [rsp+%c0] , rax"::"i"(-sizeof(struct Registers)+offsetof(struct Registers , rflags))); \
IA ("mov rax , [rsp+(8*4)]"); /* Save the original RSP */ \
IA ("mov [rsp+%c0] , rax"::"i"(-sizeof(struct Registers)+offsetof(struct Registers , rsp))); \
IA ("sub rsp , %c0"::"i"(sizeof(struct Registers))); \
IA ("mov [rsp+%c0] , rbx"::"i"(offsetof(struct Registers , rbx))); \
IA ("mov [rsp+%c0] , rcx"::"i"(offsetof(struct Registers , rcx))); \
IA ("mov [rsp+%c0] , rdx"::"i"(offsetof(struct Registers , rdx))); \
IA ("mov [rsp+%c0] , rdi"::"i"(offsetof(struct Registers , rdi))); \
IA ("mov [rsp+%c0] , rsi"::"i"(offsetof(struct Registers , rsi))); \
IA ("mov [rsp+%c0] , r8"::"i"(offsetof(struct Registers , r8))); \
IA ("mov [rsp+%c0] , r9"::"i"(offsetof(struct Registers , r9))); \
IA ("mov [rsp+%c0] , r10"::"i"(offsetof(struct Registers , r10))); \
IA ("mov [rsp+%c0] , r11"::"i"(offsetof(struct Registers , r11))); \
IA ("mov [rsp+%c0] , r12"::"i"(offsetof(struct Registers , r12))); \
IA ("mov [rsp+%c0] , r13"::"i"(offsetof(struct Registers , r13))); \
IA ("mov [rsp+%c0] , r14"::"i"(offsetof(struct Registers , r14))); \
IA ("mov [rsp+%c0] , r15"::"i"(offsetof(struct Registers , r15))); \
IA ("mov [rsp+%c0] , rbp"::"i"(offsetof(struct Registers , rbp))); \
IA ("mov rax , ss"); \
IA ("mov [rsp+%c0] , rax"::"i"(offsetof(struct Registers , ss))); \
IA ("mov rax , ds"); \
IA ("mov [rsp+%c0] , rax"::"i"(offsetof(struct Registers , ds))); \
IA ("mov rax , es"); \
IA ("mov [rsp+%c0] , rax"::"i"(offsetof(struct Registers , es))); \
IA ("mov rax , fs"); \
IA ("mov [rsp+%c0] , rax"::"i"(offsetof(struct Registers , fs))); \
IA ("mov rax , gs"); \
IA ("mov [rsp+%c0] , rax"::"i"(offsetof(struct Registers , gs))); \
IA ("add rsp , %c0"::"i"(sizeof(struct Registers))); \
IA ("pop rax"); \
IA ("mov [rsp-8+%c0] , rax"::"i"(-sizeof(struct Registers)+offsetof(struct Registers , rax))); \
/* use RSI register for regs_ptr */ \
IA ("mov rsi , rsp"); \
IA ("sub rsi , %c0"::"i"((sizeof(struct Registers)+8))); \
IA ("sub rsp , %c0"::"i"(sizeof(struct Registers)+8)); \
PUSH_REGISTERS

... and I'll be explaining this gigantic monstrosity now..

  1. Because we need to use an extra register for transfering the register values into right position, we push the RAX register into the stack prior to everything we'll be doing.
  2. Once that's done, we need to take care of the special registers that are stored in the IST(Interrupt Stack Table): RIP, CS, RFlags, and RSP.
    When the interrupt transpires and IST feature is enabled, the CPU changes the stack into the location of IST(which is stored in TSS) and pushes the original RSP, RFlags, CS, and RIP in order. (Check out this link) Since we already pushed RAX, making RSP register point RAX register, we need to increment RSP to 8 to access the saved RIP register, increment 16 to access CS, and so on.
  3. Because we're going to save all the registers into stack, we need to make a space in the stack, which is why we're decrementing the value of RSP into the size of the "Register" structure.
  4. By using offsetof——a super convenient macro that returns the offset of the member in structure, we get each member from the "Register" structure to store the corresponding register into the member.
  5. As we'll be using registers for local variables, we have to briefly push the registers into the stack and later pop back at the end of the interrupt routine.

If you're interested, here's the implementation of offsetof macro. Basically, you set the physical location of the temporary structure to 0x00 and simply return the member's location. Very creative and simple! (It's not my idea)

#define offsetof(s, m) ((max_t)&(((s *)0)->m))

Now that all the interrupts are properly working, it's finally time to really make the ps/2 driver.