OSDev.org

The Place to Start for Operating System Developers
It is currently Wed Dec 13, 2017 8:08 pm

All times are UTC - 6 hours




Post new topic Reply to topic  [ 7 posts ] 
Author Message
 Post subject: QEMU resetting problems with interrupts.
PostPosted: Sat Nov 04, 2017 9:24 pm 
Offline

Joined: Sat Nov 04, 2017 8:18 pm
Posts: 3
I'm on Linux btw, all the code is compiled on GCC 6.1
gdt.c
Code:
/*
*gdt.c by
*This file contains everything related to GDT and stuff.
*
* The GDT is a table, or rather an array of elements
*called "segment descriptors". Segment descriptors are basically structs which contain information about the segments itself(where it
*begins, its size, whether its available for use etc), and the permissions (kernel level, or userlevel) etc.
*What we need to do is call upon the holy instruction "lgdtr DWORD PTR address_of_the_first_element_in_the_array", then we'll be fine.
*Before we proceed though, let's see what a segment is made up of-
*=========================================================================================
*|  Base  | G | size | R | avl | Seg. Lim | Present | Privilege | DType | SType |  Base  |
*| 31:24  |   |      |   |     |  19:16   |         |           |       |       | 23:16  |
*=========================================================================================
*^        ^   ^      ^   ^     ^          ^         ^           ^       ^       ^       ^
*63       55  54     53  52   51          47        46          44      43      39      32
*
*========================
*|  Base addr | Seg. Lim|
*|    15:00   |  15:00  |
*========================
*^            ^        ^
*31           15       0
*Base/Base addr -> The base of the segment
*G -> Granularity (0 => 1 byte, 1 => 4 KiB)
*size -> Default size of operation (0 => 16-bit segment, 1 =>32-bit segment)
*R -> Reserved (Available for 64 bit OSes, look it up in the intel manual Volume 3), set to 0
*avl -> Whether the segment is available for use
*Seg. Lim -> The Limit of the Segment (It's size - 1 byte)
*Present -> Whether the segment is in memory (do you have some hardware mapped to that address space, or is it empty?)
*Previlege -> Kernel level code execution or User level execution
*DType -> Type of descriptor (0 => Segment, 1 => Code/Data)
*SType -> Type of Segment
**/

#include "descriptors.h"
#include <stdint.h>

#define KERNEL_LEVEL 0b00
#define USER_LEVEL 0b11

extern void lgdt(void* gdt_ptr);//This is the function which will actually load our GDT, it's an asm only instruction. I find asm() too ugly, so I'll define it in a separate .s file
extern void jump_gdt();//Use this to far jump to the gdt Code segement

struct G_descriptor_t{
  uint16_t seg_lim_low;//The lower 16 bits of the segment limit
  uint16_t base_low;//The lower 16 bits of the base address
  uint8_t base_mid;//Bits 16:23 of the base address
  uint8_t flags;//Refer to the block diagram
  /*uint8_t lim_high : 4;//The higher 4 bits of the segment limit
  uint8_t avl : 1;//The availability flags
  uint8_t R : 1;//Reserved
  uint8_t size : 1;//The default size of operations*/
  uint8_t gran;
  uint8_t base_high;//The highest 8 bits of the base address
}__attribute__((packed));
typedef struct G_descriptor_t G_descriptor;
struct gdt_ptr_t{
  uint16_t limit;
  uint32_t base;
}__attribute((packed));
typedef struct gdt_ptr_t gdt_ptr;
//We don't have a memory manager on us right now, so forget about malloc-ing for dynamic memory. But this will change once a memory manager has been implemented
//TODO: Change to dynamic memory model.
G_descriptor G_descriptors[4];
gdt_ptr gdt_pointer;

uint8_t make_flags(uint8_t present, uint8_t privilege, uint8_t DType, uint8_t SType){
  return (present << 7) | (privilege << 5) | (DType << 4) | (SType);
}



void init_gdt(){
  asm volatile("cli");
  G_descriptors[0] = (G_descriptor){0, 0, 0, 0, 0, /*0, 0, 0, */0};//Null descriptor. Usefule for debugging
  G_descriptors[1] = (G_descriptor){0xFFFF, 0x00, 0x00, make_flags(1, KERNEL_LEVEL, 1, 0xA), /*0xF, 1, 0, 1*/0xCF, 0x00};//Code segment
  G_descriptors[2] = (G_descriptor){0xFFFF, 0x00, 0x00, make_flags(1, KERNEL_LEVEL, 1, 0x2), /*0xF, 1, 0, 1*/0xCF, 0x00};//Text segment
  G_descriptors[3] = (G_descriptor){0, 0, 0, 0, 0, 0, /*0, 0, 0*/};//Null descriptor 'cuz this where the TSS wil go. TODO:Implement TSS
  gdt_pointer = (gdt_ptr){sizeof(G_descriptor)*4 - 1, G_descriptors};
  lgdt(&gdt_pointer);
  asm volatile("sti");
  //jump_gdt();
}

Code:
;;;    gdt.s
;;;    The function lgdt and jump_gdt is defined here.


   global lgdt
   section .text
lgdt:
   push ebp
   mov ebp, esp
;   lgdt [ebp + 8]
;   pop ebp
;   ret
   mov eax, [ebp + 8]  ; Get the pointer to the GDT, passed as a parameter.
   lgdt [eax]        ; Load the new GDT pointer

   mov ax, 0x10      ; 0x10 is the offset in the GDT to our data segment
   mov ds, ax        ; Load all data segment selectors
   mov es, ax
   mov fs, ax
   mov gs, ax
   mov ss, ax
   jmp 0x08:.flush   ; 0x08 is the offset to our code segment: Far jump!
.flush:
   pop ebp
   ret


   global jump_gdt
jump_gdt:
   mov ax, 0x10
   mov ds, ax
   mov es, ax
   mov fs, ax
   mov gs, ax
   mov ss, ax
   jmp 0x08:.flush
.flush:
   ret

Code:
/**
  idt.c
  This file contains all the information needed to fill the IDT.
  */


#include "descriptors.h"
#include <stddef.h>
#include <stdint.h>
#include <common.h>
#include <libc/string/string.h>

extern void lidt(void* idt_ptr);
//Interrupts
extern void reserved_handler();
extern void divide_error_handler();
extern void debug_exception_handler();
extern void nmi_handler();
extern void breakpoint_handler();
extern void overflow_handler();
extern void bound_exceeded_handler();
extern void undefined_opcode_handler();
extern void device_NA_handler();
extern void double_fault_handler();
extern void coprocessor_segment_overrun_handler();
extern void invalid_tss_handler();
extern void segment_NA_handler();
extern void stack_segment_fault_handler();
extern void general_protection_fault_handler();
extern void page_fault_handler();
extern void math_fault_handler();
extern void machine_check_abort_handler();
extern void alignment_check_handler();
extern void simd_exception_handler();

typedef struct I_descriptor_T{
  uint16_t address_low;//The lower word of the address to the entry point of the interrupt
  uint16_t selector;//Points to a valid selector in the GDT
  uint8_t zero;//Must always be zero
  uint8_t type_attrib;//See above
  uint16_t address_high;//Higher word of the address to the entry point of tyhe ISR
}__attribute__((packed)) I_descriptor;

typedef struct idt_ptr_T{
  uint32_t base_address;//Base address of the IDT
  uint16_t limit;//Limit of the IDT
}__attribute__((packed)) idt_ptr;

I_descriptor idt_entries[256];
idt_ptr idt_pointer;

static uint8_t make_flags(uint8_t present, uint8_t dpl, uint8_t isInterrupt, uint8_t size){
  return (present << 7) | (dpl << 5) | (isInterrupt?(0b110):(0b111)) | ((size & 0x01) << 3) ;
}

I_descriptor make_interrupt_gate(uint32_t interrupt_handler, uint16_t segment, uint8_t type_attrib){
  return (I_descriptor){interrupt_handler & 0xFFFF, segment, 0, type_attrib, ((interrupt_handler & (0xFFFF0000)) >> 16)};
}

I_descriptor make_null_gate(){
  //TODO Make a null interrupt handler
  return make_interrupt_gate(0, 0, 0);
}

void init_idt(){
  asm volatile("cli");
  idt_pointer.base_address = idt_entries;
  idt_pointer.limit = sizeof(I_descriptor)*256 - 1;
  //strlen("Hello!");
  memset(idt_entries, 0, sizeof(idt_entries));
  unsigned int i = 0;
  idt_entries[i++] = make_interrupt_gate((uint32_t)divide_error_handler, 0x08, make_flags(1, 0, 1, 1));
  idt_entries[i++] = make_interrupt_gate((uint32_t)debug_exception_handler, 0x08, make_flags(1, 0, 1, 1));
  idt_entries[i++] = make_interrupt_gate((uint32_t)nmi_handler, 0x08, make_flags(1, 0, 1, 1));
  idt_entries[i++] = make_interrupt_gate((uint32_t)breakpoint_handler, 0x08, make_flags(1, 0, 1, 1));
  idt_entries[i++] = make_interrupt_gate((uint32_t)overflow_handler, 0x08, make_flags(1, 0, 1, 1));
  idt_entries[i++] = make_interrupt_gate((uint32_t)bound_exceeded_handler, 0x08, make_flags(1, 0, 1, 1));
  idt_entries[i++] = make_interrupt_gate((uint32_t)undefined_opcode_handler, 0x08, make_flags(1, 0, 1, 1));
  idt_entries[i++] = make_interrupt_gate((uint32_t)device_NA_handler, 0x08, make_flags(1, 0, 1, 1));
  idt_entries[i++] = make_interrupt_gate((uint32_t)double_fault_handler, 0x08, make_flags(1, 0, 1, 1));
  idt_entries[i++] = make_interrupt_gate((uint32_t)coprocessor_segment_overrun_handler, 0x08, make_flags(1, 0, 1, 1));
  idt_entries[i++] = make_interrupt_gate((uint32_t)invalid_tss_handler, 0x08, make_flags(1, 0, 1, 1));
  idt_entries[i++] = make_interrupt_gate((uint32_t)segment_NA_handler, 0x08, make_flags(1, 0, 1, 1));
  idt_entries[i++] = make_interrupt_gate((uint32_t)stack_segment_fault_handler, 0x08, make_flags(1, 0, 1, 1));
  idt_entries[i++] = make_interrupt_gate((uint32_t)general_protection_fault_handler, 0x08, make_flags(1, 0, 1, 1));
  idt_entries[i++] = make_interrupt_gate((uint32_t)page_fault_handler, 0x08, make_flags(1, 0, 1, 1));
  idt_entries[i++] = make_interrupt_gate((uint32_t)reserved_handler, 0x08, make_flags(1, 0, 1, 1));
  idt_entries[i++] = make_interrupt_gate((uint32_t)math_fault_handler, 0x08, make_flags(1, 0, 1, 1));
  idt_entries[i++] = make_interrupt_gate((uint32_t)machine_check_abort_handler, 0x08, make_flags(1, 0, 1, 1));
  idt_entries[i++] = make_interrupt_gate((uint32_t)alignment_check_handler, 0x08, make_flags(1, 0, 1, 1));
  idt_entries[i++] = make_interrupt_gate((uint32_t)simd_exception_handler, 0x08, make_flags(1, 0, 1, 1));
  idt_entries[i++] = make_interrupt_gate((uint32_t)reserved_handler, 0x08, make_flags(1, 0, 1, 1));
  idt_entries[i++] = make_interrupt_gate((uint32_t)reserved_handler, 0x08, make_flags(1, 0, 1, 1));
  idt_entries[i++] = make_interrupt_gate((uint32_t)reserved_handler, 0x08, make_flags(1, 0, 1, 1));
  idt_entries[i++] = make_interrupt_gate((uint32_t)reserved_handler, 0x08, make_flags(1, 0, 1, 1));
  idt_entries[i++] = make_interrupt_gate((uint32_t)reserved_handler, 0x08, make_flags(1, 0, 1, 1));
  idt_entries[i++] = make_interrupt_gate((uint32_t)reserved_handler, 0x08, make_flags(1, 0, 1, 1));
  idt_entries[i++] = make_interrupt_gate((uint32_t)reserved_handler, 0x08, make_flags(1, 0, 1, 1));
  idt_entries[i++] = make_interrupt_gate((uint32_t)reserved_handler, 0x08, make_flags(1, 0, 1, 1));
  idt_entries[i++] = make_interrupt_gate((uint32_t)reserved_handler, 0x08, make_flags(1, 0, 1, 1));
  idt_entries[i++] = make_interrupt_gate((uint32_t)reserved_handler, 0x08, make_flags(1, 0, 1, 1));
  idt_entries[i++] = make_interrupt_gate((uint32_t)reserved_handler, 0x08, make_flags(1, 0, 1, 1));
  idt_entries[i++] = make_interrupt_gate((uint32_t)reserved_handler, 0x08, make_flags(1, 0, 1, 1));
  asm volatile("sti");
  lidt(&idt_pointer);
}

void test_idt(){
  asm volatile("int $0x3");
  asm volatile("cli");//NOTE:DEBUG ONLY
}


interrupt.c
Code:
#include <stddef.h>
#include <stdint.h>
#include <common.h>
#include <vga.h>

void null_isr_handler(uint8_t error_code){
  WARN("ISR handler run - With error code");
}

void null_isr_handler_no_error(){
  WARN("ISR handler run - With error code");
}

interrupts.s
Code:
ALIGN 4

section .data
message:
  db "Recieved interrupt", 0x00



section .text
extern null_isr_handler
extern term_print
extern null_isr_handler_no_error
%macro NULL_ISR_NOERROR 1
global %1
%1:
  ; pushad
  ; cld
  ; push message
  ; call term_print
  ; popad
  ; iret
  cli
  push byte 0
  push byte 0
  jmp isr_common_stub
%endmacro

%macro NULL_ISR_ERROR 1
global %1
%1:
  ; pushad
  ; cld
  ; push message
  ; call term_print
  ; popad
  ; iret
  cli
  push byte 0
  jmp isr_common_stub
%endmacro

%macro ISR_HANDLER_NOERROR 1
global %1
%1:
  call [%1]_c
  iret
%endmacro
isr_common_stub:
   pusha                 ; Pushes edi,esi,ebp,esp,ebx,edx,ecx,eax

   mov ax, ds               ; Lower 16-bits of eax = ds.
   push eax                 ; save the data segment descriptor

   mov ax, 0x10  ; load the kernel data segment descriptor
   mov ds, ax
   mov es, ax
   mov fs, ax
   mov gs, ax

   call null_isr_handler_no_error

   pop eax        ; reload the original data segment descriptor
   mov ds, ax
   mov es, ax
   mov fs, ax
   mov gs, ax

   popa           ; Pops edi,esi,ebp...
   add esp, 8     ; Cleans up the pushed error code and pushed ISR number
   sti
   iret           ; pops 5 things at once: CS, EIP, EFLAGS, SS, and ESP
NULL_ISR_NOERROR divide_error_handler
NULL_ISR_NOERROR debug_exception_handler
NULL_ISR_NOERROR nmi_handler
NULL_ISR_NOERROR breakpoint_handler
NULL_ISR_NOERROR overflow_handler
NULL_ISR_NOERROR bound_exceeded_handler
NULL_ISR_NOERROR undefined_opcode_handler
NULL_ISR_NOERROR device_NA_handler
NULL_ISR_ERROR double_fault_handler
NULL_ISR_NOERROR coprocessor_segment_overrun_handler
NULL_ISR_ERROR invalid_tss_handler
NULL_ISR_ERROR segment_NA_handler
NULL_ISR_ERROR stack_segment_fault_handler
NULL_ISR_ERROR general_protection_fault_handler
NULL_ISR_ERROR page_fault_handler
NULL_ISR_NOERROR math_fault_handler
NULL_ISR_NOERROR machine_check_abort_handler
NULL_ISR_ERROR alignment_check_handler
NULL_ISR_NOERROR simd_exception_handler
NULL_ISR_NOERROR reserved_handler


That I believe is all the relevant code. The code runs fine till I execute test_idt, after which it seems to reset. There are few glaring issues with the code I know, like not dealing with the error code, but int 3 doesn't deal with that stuff, and I simply want to get this stuff working before I move on to that stuff. If anybody has any idea as to why it crashes, please help me out.
WARN btw, is nothing more than a macro which sets colors, prints the parameter, and then resets the colors to their original values. It's enclosed in curly braces, so it runs in its own scope. In the last piece of code, I've tried replacing popa/pusha with popad/pushad, but it doesn't seem to work. My interrupt asm code didn't work, so the current one is a modified version of James Molloy's tutorial. The stuff that's commented out in the nasm macros are the original ISRs, written by me, btw.
Thanks


Top
 Profile  
 
 Post subject: Re: QEMU resetting problems with interrupts.
PostPosted: Sat Nov 04, 2017 11:32 pm 
Online
Member
Member

Joined: Fri Aug 26, 2016 1:41 pm
Posts: 111
It would be more helpful if you made your project available on something like Github. You don't show us everything which includes things like the function lidt nor did you show the code for the exception handler for int 3.


Top
 Profile  
 
 Post subject: Re: QEMU resetting problems with interrupts.
PostPosted: Sun Nov 05, 2017 12:24 am 
Online
Member
Member

Joined: Fri Aug 26, 2016 1:41 pm
Posts: 111
thatoneguy1233 wrote:
%macro NULL_ISR_NOERROR 1
global %1
%1:
; pushad
; cld
; push message
; call term_print
; popad
; iret
cli
push byte 0
push byte 0
jmp isr_common_stub
%endmacro

%macro NULL_ISR_ERROR 1
global %1
%1:
; pushad
; cld
; push message
; call term_print
; popad
; iret
cli
push byte 0
jmp isr_common_stub
%endmacro
I did notice in what you say was your original code that the _ERROR version doesn't actually add 4 to ESP before doing the iretd to skip the error code that the processor pushed after the return address was placed on the stack. Although I see you already know that no that I reread your question

As well you push message but you don't actually remove the parameter from the stack with something like add esp, 4 right after the call (or you could do a pop eax for the same effect). Failure to do that would also make the popad restore improperly.


Top
 Profile  
 
 Post subject: Re: QEMU resetting problems with interrupts.
PostPosted: Sun Nov 05, 2017 2:19 am 
Offline

Joined: Sat Nov 04, 2017 8:18 pm
Posts: 3
Thanks. Here's the link to the github repo - https://github.com/animefreak1233/potential-broccoli
Excuse the name, I used the random generator

But that aside, I tried what you said, nothing happened (Well, it resets again, and since it's an interrupt, I can't quite debug it using Qemu. But I did forget one thing, the OS never resets when stepping through the code in Qemu) but the IDT and GDT structures are all valid, that part can't be wrong unless I got some major misunderstanding as to how the GDT and IDT are structured.)


Top
 Profile  
 
 Post subject: Re: QEMU resetting problems with interrupts.
PostPosted: Sun Nov 05, 2017 4:04 am 
Online
Member
Member

Joined: Fri Aug 26, 2016 1:41 pm
Posts: 111
Although not a fix for all your issues the obvious problem is that in init_gdt you turn on interrupts after you set the GDT. Problem is that you haven't set up the IDT at all, and you can't actually do an STI until after you do an LIDT. You should remove the STI from init_gdt and in init_idt you should move the STI after the call to LIDT.

That's just the start of the issues. Seems to also be a problem with your IDT entries as well. Will look at it a bit later when I have more time.

For GDT and IDT issues I recommend using grub-mkrescue to create a bootable ISO and using BOCHs to debug. Bochs can debug GDT and IDT issues much easier than QEMU.


Last edited by MichaelPetch on Mon Nov 06, 2017 1:49 pm, edited 2 times in total.

Top
 Profile  
 
 Post subject: Re: QEMU resetting problems with interrupts.
PostPosted: Sun Nov 05, 2017 4:21 am 
Online
Member
Member

Joined: Fri Aug 26, 2016 1:41 pm
Posts: 111
I also noticed this problem:
Code:
typedef struct idt_ptr_T{
  uint32_t base_address;//Base address of the IDT
  uint16_t limit;//Limit of the IDT
}__attribute__((packed)) idt_ptr;
The problem here is that you have limit and base swapped around. limit is first and then the base. It should look like:
Code:
typedef struct idt_ptr_T{
  uint16_t limit;//Limit of the IDT
  uint32_t base_address;//Base address of the IDT
}__attribute__((packed)) idt_ptr;
You also have a bug in your lidt assembly function:
Code:
lidt:
  lidt [esp + 4]
  ret
The problem is that it is using the address [esp + 4] as the location of the idt_ptr structure. The problem is that you want to get the address at [esp + 4] and use it as the memory operand to lidt. It could look like this:
Code:
lidt:
  mov eax, [esp + 4]
  lidt [eax]
  ret

Originally I didn't look closely enough but you don't actually set up IRQ interrupts in your IDT. Until you do that you shouldn't enable external interrupts with STI. You can still do things like int $0x3 with interrupts off as software interrupts are not affected by the interrupt flag status. Before you set up your IRQ handlers in your IDT you should read the OS Dev wiki regarding configuring the PICs. You'll need to remap the IRQs so that they don't overlap with the Intel processors exceptions. You can also tell the PICs which interrupts you wish to be sent to the processor.


Top
 Profile  
 
 Post subject: Re: QEMU resetting problems with interrupts.
PostPosted: Sun Nov 05, 2017 10:13 am 
Offline

Joined: Sat Nov 04, 2017 8:18 pm
Posts: 3
Oh man, I feel stupid to not have noticed that. But thanks, it finally runs, and bochs is kinda amazing for debugging.


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 7 posts ] 

All times are UTC - 6 hours


Who is online

Users browsing this forum: Google [Bot], MichaelPetch, Yahoo [Bot] and 15 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group