; -*- fundamental -*- ; Low level interrupt/thread handling code for GeekOS. ; Copyright (c) 2001,2003,2004 David H. Hovemeyer ; Copyright (c) 2003, Jeffrey K. Hollingsworth ; $Revision: 1.8 $ ; This is free software. You are permitted to use, ; redistribute, and modify it as specified in the file "COPYING". ; This is 32 bit code to be linked into the kernel. ; It defines low level interrupt handler entry points that we'll use ; to populate the IDT. It also contains the interrupt handling ; and thread context switch code. %include "defs.asm" %include "symbol.asm" %include "util.asm" [BITS 32] ; ---------------------------------------------------------------------- ; Definitions ; ---------------------------------------------------------------------- ; This is the size of the Interrupt_State struct in int.h INTERRUPT_STATE_SIZE equ 64 ; Save registers prior to calling a handler function. ; This must be kept up to date with: ; - Interrupt_State struct in int.h ; - Setup_Initial_Thread_Context() in kthread.c %macro Save_Registers 0 push eax push ebx push ecx push edx push esi push edi push ebp push ds push es push fs push gs %endmacro ; Restore registers and clean up the stack after calling a handler function ; (i.e., just before we return from the interrupt via an iret instruction). %macro Restore_Registers 0 pop gs pop fs pop es pop ds pop ebp pop edi pop esi pop edx pop ecx pop ebx pop eax add esp, 8 ; skip int num and error code %endmacro ; Code to activate a new user context (if necessary), before returning ; to executing a thread. Should be called just before restoring ; registers (because the interrupt context is used). %macro Activate_User_Context 0 ; If the new thread has a user context which is not the current ; one, activate it. push esp ; Interrupt_State pointer push dword [g_currentThread] ; Kernel_Thread pointer ; call Switch_To_User_Context add esp, 8 ; clear 2 arguments %endmacro ; Number of bytes between the top of the stack and ; the interrupt number after the general-purpose and segment ; registers have been saved. REG_SKIP equ (11*4) ; Template for entry point code for interrupts that have ; an explicit processor-generated error code. ; The argument is the interrupt number. %macro Int_With_Err 1 align 8 push dword %1 ; push interrupt number jmp Handle_Interrupt ; jump to common handler %endmacro ; Template for entry point code for interrupts that do not ; generate an explicit error code. We push a dummy error ; code on the stack, so the stack layout is the same ; for all interrupts. %macro Int_No_Err 1 align 8 push dword 0 ; fake error code push dword %1 ; push interrupt number jmp Handle_Interrupt ; jump to common handler %endmacro ; ---------------------------------------------------------------------- ; Symbol imports and exports ; ---------------------------------------------------------------------- ; This symbol is defined in idt.c, and is a table of addresses ; of C handler functions for interrupts. IMPORT g_interruptTable ; Global variable pointing to context struct for current thread. IMPORT g_currentThread ; Set to non-zero when we need to choose a new thread ; in the interrupt return code. IMPORT g_needReschedule ; Set to non-zero when preemption is disabled. IMPORT g_preemptionDisabled ; This is the function that returns the next runnable thread. IMPORT Get_Next_Runnable ; Function to put a thread on the run queue. IMPORT Make_Runnable ; Function to activate a new user context (if needed). IMPORT Switch_To_User_Context ; Debug functions IMPORT SerialPrintHex IMPORT SerialPutChar ; Sizes of interrupt handler entry points for interrupts with ; and without error codes. The code in idt.c uses this ; information to infer the layout of the table of interrupt ; handler entry points, without needing a separate linker ; symbol for each one (which is quite tedious to type :-) EXPORT g_handlerSizeNoErr EXPORT g_handlerSizeErr ; Simple functions to load the IDTR, GDTR, and LDTR. EXPORT Load_IDTR EXPORT Load_GDTR EXPORT Load_LDTR ; Beginning and end of the table of interrupt entry points. EXPORT g_entryPointTableStart EXPORT g_entryPointTableEnd ; Thread context switch function. EXPORT Switch_To_Thread ; Return current value of eflags register. EXPORT Get_Current_EFLAGS ; Return the return address EXPORT Get_EIP ; ESP EXPORT Get_ESP ; EBP EXPORT Get_EBP ; Halt machine EXPORT Halt ; Virtual memory support. EXPORT Enable_Paging EXPORT Set_PDBR EXPORT Get_PDBR EXPORT Flush_TLB ; CPUID functions ;EXPORT cpuid_ecx ;EXPORT cpuid_eax ;EXPORT cpuid_edx ; Utility Functions ;EXPORT Set_MSR ;EXPORT Get_MSR EXPORT Get_CR2 EXPORT Get_CR3 EXPORT Proc_test ; ---------------------------------------------------------------------- ; Code ; ---------------------------------------------------------------------- [SECTION .text] ; Load IDTR with 6-byte pointer whose address is passed as ; the parameter. align 8 Load_IDTR: mov eax, [esp+4] lidt [eax] ret ; Load the GDTR with 6-byte pointer whose address is ; passed as the parameter. Assumes that interrupts ; are disabled. align 8 Load_GDTR: mov eax, [esp+4] lgdt [eax] ; Reload segment registers mov ax, KERNEL_DS mov ds, ax mov es, ax mov fs, ax mov gs, ax mov ss, ax jmp KERNEL_CS:.here .here: ret ; Load the LDT whose selector is passed as a parameter. align 8 Load_LDTR: mov eax, [esp+4] lldt ax ret ; ; Start paging ; load crt3 with the passed page directory pointer ; enable paging bit in cr2 align 8 Enable_Paging: push ebp mov ebp, esp push eax push ebx mov eax, [ebp+8] mov cr3, eax mov eax, cr3 mov cr3, eax mov ebx, cr0 or ebx, 0x80000000 mov cr0, ebx pop ebx pop eax pop ebp ret ; ; Change PDBR ; load cr3 with the passed page directory pointer align 8 Set_PDBR: mov eax, [esp+4] mov cr3, eax ret ; ; Get the current PDBR. ; This is useful for lazily switching address spaces; ; only switch if the new thread has a different address space. ; align 8 Get_PDBR: mov eax, cr3 ret ; ; Flush TLB - just need to re-load cr3 to force this to happen ; align 8 Flush_TLB: mov eax, cr3 mov cr3, eax ret ; ; cpuid_edx - return the edx register from cpuid ; align 8 cpuid_edx: push ebp mov ebp, esp push edx push ecx push ebx mov eax, [ebp + 8] cpuid mov eax, edx pop ebx pop ecx pop edx pop ebp ret ; ; cpuid_ecx - return the ecx register from cpuid ; align 8 cpuid_ecx: push ebp mov ebp, esp push edx push ecx push ebx mov eax, [ebp + 8] cpuid mov eax, ecx pop ebx pop ecx pop edx pop ebp ret ; ; cpuid_eax - return the eax register from cpuid ; align 8 cpuid_eax: push ebp mov ebp, esp push edx push ecx push ebx mov eax, [esp+4] cpuid pop ebx pop ecx pop edx pop ebp ret ; ; Set_MSR - Set the value of a given MSR ; align 8 Set_MSR: push ebp mov ebp, esp pusha mov eax, [ebp+16] mov edx, [ebp+12] mov ecx, [ebp+8] wrmsr popa pop ebp ret ; ; Get_MSR - Get the value of a given MSR ; void Get_MSR(int MSR, void * high_byte, void * low_byte); ; align 8 Get_MSR: push ebp mov ebp, esp pusha mov ecx, [ebp+8] rdmsr mov ebx, [ebp+12] mov [ebx], edx mov ebx, [ebp+16] mov [ebx], eax popa pop ebp ret align 8 Halt: hlt align 8 Get_CR2: mov eax, cr2 ret align 8 Get_CR3: mov eax, cr3 ret align 8 Proc_test: push ebp mov ebp, esp push ebx mov ebx, [ebp + 8] mov eax, [ebp + 12] add eax, ebx pop ebx pop ebp ret ; Common interrupt handling code. ; Save registers, call C handler function, ; possibly choose a new thread to run, restore ; registers, return from the interrupt. align 8 Handle_Interrupt: ; Save registers (general purpose and segment) Save_Registers ; Ensure that we're using the kernel data segment mov ax, KERNEL_DS mov ds, ax mov es, ax ; Get the address of the C handler function from the ; table of handler functions. mov eax, g_interruptTable ; get address of handler table mov esi, [esp+REG_SKIP] ; get interrupt number mov ebx, [eax+esi*4] ; get address of handler function ; push esi ; call SerialPrintHex ; pop esi ; push eax ; mov eax, 0xa ; push eax ; call SerialPutChar ; pop eax ; pop eax ; Call the handler. ; The argument passed is a pointer to an Interrupt_State struct, ; which describes the stack layout for all interrupts. push esp call ebx add esp, 4 ; clear 1 argument ; If preemption is disabled, then the current thread ; keeps running. cmp [g_preemptionDisabled], dword 0 jne .restore ; See if we need to choose a new thread to run. cmp [g_needReschedule], dword 0 je .restore ; Put current thread back on the run queue push dword [g_currentThread] call Make_Runnable add esp, 4 ; clear 1 argument ; Save stack pointer in current thread context, and ; clear numTicks field. mov eax, [g_currentThread] mov [eax+0], esp ; esp field mov [eax+4], dword 0 ; numTicks field ; Pick a new thread to run, and switch to its stack call Get_Next_Runnable mov [g_currentThread], eax mov esp, [eax+0] ; esp field ; Clear "need reschedule" flag mov [g_needReschedule], dword 0 .restore: ; push ebp ; mov ebp, esp ; pusha ; ; mov eax, 0xa ; push eax ; call SerialPutChar ; pop eax ; mov eax, 0xa ; push eax ; call SerialPutChar ; pop eax ; ; mov ecx, 4 ; mov edx, 24 ; ;.loop: ; mov eax, [ebp + ecx] ; ; push eax ; call SerialPrintHex ; pop eax ; ; mov eax, 0xa ; push eax ; call SerialPutChar ; pop eax ; ; add ecx, 4 ; dec edx ; jnz .loop ; ; ; popa ; pop ebp ; Activate the user context, if necessary. Activate_User_Context ; Restore registers Restore_Registers ; pusha ; mov eax, 0xaa ; push eax ; call SerialPrintHex ; pop eax ; mov eax, 0xa ; push eax ; call SerialPutChar ; pop eax ; popa ; Return from the interrupt. iret ; ---------------------------------------------------------------------- ; Switch_To_Thread() ; Save context of currently executing thread, and activate ; the thread whose context object is passed as a parameter. ; ; Parameter: ; - ptr to Kernel_Thread whose state should be restored and made active ; ; Notes: ; Called with interrupts disabled. ; This must be kept up to date with definition of Kernel_Thread ; struct, in kthread.h. ; ---------------------------------------------------------------------- align 16 Switch_To_Thread: ; Modify the stack to allow a later return via an iret instruction. ; We start with a stack that looks like this: ; ; thread_ptr ; esp --> return addr ; ; We change it to look like this: ; ; thread_ptr ; eflags ; cs ; esp --> return addr push eax ; save eax mov eax, [esp+4] ; get return address mov [esp-4], eax ; move return addr down 8 bytes from orig loc add esp, 8 ; move stack ptr up pushfd ; put eflags where return address was mov eax, [esp-4] ; restore saved value of eax push dword KERNEL_CS ; push cs selector sub esp, 4 ; point stack ptr at return address ; Push fake error code and interrupt number push dword 0 push dword 0 ; Save general purpose registers. Save_Registers ; Save stack pointer in the thread context struct (at offset 0). mov eax, [g_currentThread] mov [eax+0], esp ; Clear numTicks field in thread context, since this ; thread is being suspended. mov [eax+4], dword 0 ; Load the pointer to the new thread context into eax. ; We skip over the Interrupt_State struct on the stack to ; get the parameter. mov eax, [esp+INTERRUPT_STATE_SIZE] ; Make the new thread current, and switch to its stack. mov [g_currentThread], eax mov esp, [eax+0] ; Activate the user context, if necessary. Activate_User_Context ; Restore general purpose and segment registers, and clear interrupt ; number and error code. Restore_Registers ; We'll return to the place where the thread was ; executing last. iret ; Return current contents of eflags register. align 16 Get_Current_EFLAGS: pushfd ; push eflags pop eax ; pop contents into eax ret ; Return the eip of the next instruction after the caller align 16 Get_EIP: mov eax, [esp] ret ; Return the current esp in the procedure align 16 Get_ESP: mov eax, esp ret ; Return the current ebp in the procedure align 16 Get_EBP: mov eax, ebp ret ; ---------------------------------------------------------------------- ; Generate interrupt-specific entry points for all interrupts. ; We also define symbols to indicate the extend of the table ; of entry points, and the size of individual entry points. ; ---------------------------------------------------------------------- align 8 g_entryPointTableStart: ; Handlers for processor-generated exceptions, as defined by ; Intel 486 manual. Int_No_Err 0 align 8 Before_No_Err: Int_No_Err 1 align 8 After_No_Err: Int_No_Err 2 ; FIXME: not described in 486 manual Int_No_Err 3 Int_No_Err 4 Int_No_Err 5 Int_No_Err 6 Int_No_Err 7 align 8 Before_Err: Int_With_Err 8 align 8 After_Err: Int_No_Err 9 ; FIXME: not described in 486 manual Int_With_Err 10 Int_With_Err 11 Int_With_Err 12 Int_With_Err 13 Int_With_Err 14 Int_No_Err 15 ; FIXME: not described in 486 manual Int_No_Err 16 Int_With_Err 17 ; The remaining interrupts (18 - 255) do not have error codes. ; We can generate them all in one go with nasm's %rep construct. %assign intNum 18 %rep (256 - 18) Int_No_Err intNum %assign intNum intNum+1 %endrep align 8 g_entryPointTableEnd: [SECTION .data] ; Exported symbols defining the size of handler entry points ; (both with and without error codes). align 4 g_handlerSizeNoErr: dd (After_No_Err - Before_No_Err) align 4 g_handlerSizeErr: dd (After_Err - Before_Err)