Download presentation
Presentation is loading. Please wait.
Published byLynn Lucas Modified over 9 years ago
1
6. HAL and IDT ENGI 3655 Lab Sessions
2
Richard Khoury2 Textbook Readings Interrupts ◦ Section 13.2.2 Hardware Abstraction Layer ◦ Section 22.3.1
3
Richard Khoury3 Hardware Abstraction Layer Last week we created an interface to avoid using “int” and “char” because they might be too platform-dependant ◦ We want our OS to work on any hardware But our OS does need to work with the hardware ◦ We can’t hide technical details and specificities forever
4
Richard Khoury4 Hardware Abstraction Layer We’ll write declarations for the functions we need that are not system-specific ◦ Using our interface data types ◦ Put them all in header files We’ll write system-specific function implementations ◦ One version for each system we want to support ◦ They all implement the same header file We’ll pick the one we need ◦ Dynamically at run time, by loading the right module ◦ At linking time, picking the right object file That is a hardware abstraction layer (HAL) ◦ Our data type definitions are part of it
5
Richard Khoury5 Hardware Abstraction Layer Our initial HAL will have few parts HAL ◦ CPU Registers GDT IDT ◦ Basic memory-handling functions
6
Richard Khoury6 Hardware Abstraction Layer HAL.h ◦ Included in our main file ◦ Defines the HAL initialization and shutdown functions extern uint32_t hal_initialize(); extern uint32_t hal_shutdown(); ◦ The initialize function is the only one the kernel calls; it will initialize everything else
7
Richard Khoury7 HAL CPU The initialize and shutdown functions activate and deactivate the hardware ◦ For now, our only hardware is the CPU CPU.h extern uint32_t cpu_initialize (); extern void cpu_shutdown (); Registers.h struct _R16BIT { uint16_t ax, bx, cx, dx, si, di, bp, sp, es, cs, ss, ds, flags; uint8_t cflag; };
8
Richard Khoury8 HAL GDT The CPU initialization will create a new GDT ◦ To put the GDT under the control of our OS rather than the bootloader ◦ For now, just the same GDT as before, but written with C structures Recall: the GDT descriptors are 8-byte structures, and we had five of them loaded into the processor ◦ Also, specific to Intel processor; not part of the interface, so included in i86.c, an implementation of CPU.h
9
Richard Khoury9 HAL GDT We’ll create a GDT structure to store the information struct gdt_descriptor { uint16_tlimit; uint16_tbaseLo; uint8_tbaseMid; uint8_taccess; uint8_tgran; uint8_tbaseHi; } __attribute__((packed)); We can create each of our five descriptors with these But then we need a place to store them static struct gdt_descriptor _gdt [MAX_DESCRIPTORS];
10
Richard Khoury10 HAL GDT Next we can define the descriptors We can even define constants to make the code more readable /*Bits 5-6: Privilege*/ #define I86_GDT_DESC_RING00x0000 //_00_____ #define I86_GDT_DESC_RING10x0020 //_01_____ #define I86_GDT_DESC_RING20x0040 //_10_____ #define I86_GDT_DESC_RING30x0060 //_11_____ And we create each descriptor gdt_set_descriptor(uint32_t i, uint64_t base, uint64_t limit, uint8_t access, uint8_t gran) ; code descriptor dw 0FFFFh ; limit low dw 0 ; base low db 0 ; base middle db 10011010b ; access db 11001111b ; granularity db 0 ; base high
11
Richard Khoury11 HAL GDT Recall that loading the GDT into the CPU requires two things A special structure representing the start and size of the GDT toc: dw end_of_gdt - gdt_data - 1 dd gdt_data A special Assembly instruction lgdt [toc] We’ll need to do that from C
12
Richard Khoury12 HAL GDT We’ll start by defining the GDT structure struct gdtr { uint16_tm_limit; uint32_tm_base; } __attribute__((packed)); static struct gdtr _gdtr; _gdtr.m_limit = (sizeof (struct gdt_descriptor) * MAX_DESCRIPTORS)-1; _gdtr.m_base = (uint32_t)&_gdt[0]; We’ll need an external Assembly function to load the GDT extern void gdt_install ();
13
Richard Khoury13 HAL GDT Next, we create a new Assembly include file ◦ “GlobalCFunctions.inc” ◦ Include it in our Kernel Needs to work with our C function ◦ Define the function to load the GDT global _gdt_install ◦ Use the GDT data structure extern __gdtr ◦ Note the extra _ character
14
Richard Khoury14 HAL GDT Then write the function in Assembly _gdt_install: ◦ Load the GDT into its special register using the special instruction lgdt [__gdtr] ◦ Put the offset of the data segment in the registers mov ax, 0x10 mov ds, ax mov es, ax mov fs, ax mov gs, ax mov ss, ax ◦ Far-jump to the offset of the code segment jmp 0x08:endgdt endgdt: ret
15
Richard Khoury15 Hardware Abstraction Layer HAL.h i86.cHAL.c CPU.h main.c Intel 80x86 processor with VGA-compatible video card AMD-specific implementation files System-specific implementation files Hardware- independent interface Kernel ENGI3655 computers GDT.h/c IDT.h/c Mode7.c Mode7.h Data types (stdint.h) Registers.h
16
Richard Khoury16 Interrupts OS needs to interact with the I/O hardware ◦ We already did some of that, making the bootloader interact with the screen (int 10h) and the disk (int 13h) ◦ For optimal performance, OS should be informed immediately when an I/O device needs attention ◦ When ready, run special I/O code OS needs to be immediately informed of exceptions in programs ◦ Illegal code: needs to substitute special exception- handling code
17
Richard Khoury17 Interrupts Different functions, but both require the same kind of action ◦ Interrupt current process’s execution ◦ Jump to special code and execute ◦ Return if possible Interrupts ◦ Hardware Interrupts ◦ Software Interrupts (traps)
18
Richard Khoury18 Hardware Interrupts I/O Controller Input or output ready, or error Translate IRQ into Interrupt no. Send IRQ Signal CPU’s Interrupt Request line Execute instruction Check IR line Execute instruction Check IR line Jump to location pointed in IDT Save state information Interrupt Handler Routine Restore state information Perform regular I/O functions Programmable Interrupt Controller (PIC) CPU OS Execute IHR
19
Richard Khoury19 Software Interrupts (Traps) Execute instruction Exception! Generate Interrupt number Jump to location pointed in IDT Save pointer to instruction Interrupt Handler Routine Return if possible CPU OS Execute IHR
20
Richard Khoury20 Handling Interrupts Our OS doesn’t handle interrupts yet Try it: add an interrupt in the C kernel using inline Assembly code asm volatile ("int $0x3"); What happens?
21
Richard Khoury21 For This Lab... We will handle interrupts ◦ Build an interrupt descriptor table (IDT) ◦ Write interrupt handlers
22
Richard Khoury22 Interrupt Vector Table A table of 256 entries ◦ Each one represents a kind of event that requires interrupting the CPU ◦ Each one is 4 bytes large: it consists of a memory address (2 bytes offset + 2 bytes segment) When an interrupt vector (0x10 for example) is generated, the CPU looks up the entry (0x10 * 4 in the IVT) and jumps to that address in memory ◦ That address should contain executable code to handle whatever generated that interrupt
23
Richard Khoury23 Interrupt Vector Table But wait! Recall: before switching to protected mode, our second-state bootloader disabled interrupts And we never enabled them again afterwards IVT is only accessible from Real Mode If we want to use interrupts from Protected Mode, we’ll need to set up an Interrupt Descriptor Table
24
Richard Khoury24 Interrupt Descriptor Table Like the IVT, the IDT has 256 entries Each one is 8 bytes long Three different descriptor types ◦ Interrupt Gates: For I/O interrupts ◦ Trap Gates: For exception interrupts They are almost identical, except that interrupts are disabled by interrupt gate and not by trap gate, so IG is better for hardware interrupts ◦ Task Gates: For task-switching interrupts
25
Richard Khoury25 Descriptor Structure BitsInterrupt GateTrap GateTask Gate 0-15Interrupt Handler Routine address (0-15) Not used 16-31Code Segment SelectorTask State Segment (TSS) Selector 32-35Not Used 36-39Reserved (set to zero)Not used
26
Richard Khoury26 Descriptor Structure BitsInterrupt GateTrap GateTask Gate 40-44 Type 01110: 32 bits 00110: 16 bits 01111: 32 bits 00111: 16 bits 00101 45-46Privilege Ring Level (00 highest – 11 lowest) 470/1 = Segment is absent/present 48-63Interrupt Handler Routine address (16-31) Not used
27
Richard Khoury27 Descriptor Structure Easy to represent as a C structure ◦ Remember our C data type interface from last week? struct idt_descriptor { uint16_tbaseLo; uint16_tsel; uint8_treserved; uint8_tflags; uint16_tbaseHi; };
28
Richard Khoury28 Interrupt Descriptor Table 256 Interrupts defined by Intel for the 80x86 0: Divide by 0 1: Debugger single step 2: Non-maskable interrupt 3: Debugger breakpoint 4: Overflow 5: Out of bounds 6: Undefined Operation Code (OPCode) instruction 7: No coprocessor 8: Double fault 9: Coprocessor segment overrun 10: Invalid Task State Segment (TSS) 11: Segment not present 12: Stack fault 13: General protection fault 14: Page fault 15 is unassigned 16: Coprocessor error 17: Alignment check (486+ Only) 18: Machine check (Pentium/586+ Only) 19-31 are reserved by Intel 32-255 are free for software use
29
Richard Khoury29 Interrupt Descriptor Table The IDT is a table of 256 interrupt descriptors ◦ We’ve already seen the idt_descriptor data structure ◦ An array of them is simply: static struct idt_descriptor_idt[i86_MAX_INTERRUPTS]; We’ll create two interface functions to the IDT ◦ i86_install_ir : to modify an interrupt descriptor in our table ◦ i86_idt_initialize : to initialize the table with the default 256 descriptors Each descriptor includes the address of the interrupt handler routine
30
Richard Khoury30 Interrupt Handler Routine Sometimes called Interrupt Service Routine (ISR) The function that dictates what to do when a given interrupt occurs There are 256 interrupts ◦ 32-255 are free for software use – we won’t write complex functions for them ◦ We’ll need functions for int 0 to 31 ◦ For now, we’ll simply make a default handler function that displays an error message and runs an endless loop i86_default_handler()
31
Richard Khoury31 Installing a Handler Handler routine address is bits 0-15 and 48-63 of interrupt descriptor structure for (i=0; i<I86_MAX_INTERRUPTS; i++) i86_install_ir ((I86_IRQ_HANDLER)i86_default_handler); i86_install_ir (I86_IRQ_HANDLER irq) { uint64_t uiBase =(uint64_t)&(*irq); _idt[i].baseLo =(uiBase & 0xffff); _idt[i].baseHi =((uiBase >> 16) & 0xffff);}
32
Richard Khoury32 Installing a Handler The i86_install_ir function also needs you to specify the interrupt number the function is for, the code segment (0x8) and the descriptor attributes ◦ Bits 40-44: Descriptor type ◦ Bits 45-46: Privilege ring ◦ Bits 47: Descriptor present/abscent ◦ Values defined as constants in idt.h #define I86_IDT_DESC_RING00x00//_00_____ #define I86_IDT_DESC_RING30x60//_11_____
33
Richard Khoury33 Loading the IDT Like the GDT, the IDT is simply installed by loading the base address and size into a special CPU register We’ll create a C structure struct idtr { uint16_tlimit; uint32_tbase; } __attribute__((packed)); struct idtr _idtr; Set them to the right values _idtr.limit= sizeof(struct idt_descriptor)*I86_MAX_INTERRUPTS-1; _idtr.base= (uint32_t)&_idt[0]; And define an extern Assembly function extern void idt_install();
34
Richard Khoury34 Loading the IDT The Assembly function is even simpler than the one to load the GDT global _idt_install extern __idtr _idt_install: lidt [__idtr] ret
35
Richard Khoury35 Loading the IDT Once it’s done, test it We already added an interrupt in the C kernel asm volatile ("int $0x3"); What happens now?
36
Richard Khoury36 Interrupt Handler Routine Now we only have one common handler routine that does nothing We’ll build better routines for our first 32 interrupts ◦ Save the stack ◦ Display a personalized message ◦ Reload the stack Clearly, we’ll need a good mix of Assembly and C functions
37
Richard Khoury37 Interrupt Handler Routine The routine called by an interrupt will be divided in three parts Low Part ◦ Push error code on stack (if not done automatically) ◦ Push interrupt number on stack ◦ Jump to common part Common Part ◦ Push all registers on stack ◦ Call top part Top Part ◦ Take action specific to interrupt Low and Common are in Assembly, top in C
38
Six interrupts automatically push an error code before the Low Part Interrupt Handler Routine Richard Khoury38 0: Divide by 0 1: Debugger single step 2: Non-maskable interrupt 3: Debugger breakpoint 4: Overflow 5: Out of bounds 6: Undefined Operation Code (OPCode) instruction 7: No coprocessor 8: Double fault 9: Coprocessor segment overrun 10: Invalid Task State Segment (TSS) 11: Segment not present 12: Stack fault 13: General protection fault 14: Page fault 15 is unassigned 16: Coprocessor error 17: Alignment check (486+ Only) 18: Machine check (Pentium/586+ Only) 19-31 are reserved by Intel 32-255 are free for software use
39
Richard Khoury39 Interrupt Handler Routine (low part) Structure of Low Part (for interrupt #) global _isr# _isr#: cli push byte 0 (if not done automatically) push byte # jmp isr_common_stub The 0 byte is a dummy error code ◦ Not needed for traps
40
Richard Khoury40 Interrupt Handler Routine (common) Structure of Common Part (for all int) isr_common_stub: ◦ Push all registers pusha push (ds, es, fs, gs) ◦ Load the kernel data segment descriptor mov ax, 0x10 mov (ds, es, fs, gs), ax ◦ Call the function mov eax, esp push eax mov eax, _fault_handler call eax ◦ Pop all registers reverse order and far-return pop … add esp, 8 iret
41
Richard Khoury41 Interrupt Handler Routine (top part) Top Part is a C function void fault_handler() { ◦ Do something specific to each interrupt } Declared as “extern” in the Assembly code Problem: how does the function know which interrupt number has been called?
42
Richard Khoury42 Interrupt Handler Routine Answer: It has been pushed on the stack by the Low Part We can access it in C with a register data structure (Registers.h) struct isrregs { uint32_t gs, fs, es, ds; uint32_t edi, esi, ebp, esp, ebx, edx, ecx, eax; uint32_t int_no, err_code; uint32_t eip, cs, eflags, useresp, ss; }; eax ------------- gs, fs, es, ds ------------- pusha (edi, esi, ebp, esp, ebx, edx, ecx, eax) ------------- Interrupt number ------------- Error code ------------- eip, cs, eflags, useresp, ss Top of stack These were pushed automatically by the CPU before our Low Part
43
Richard Khoury43 Interrupt Handler Routine (top part) Now we can pass it to our Top Part and use it void fault_handler(struct isrregs *r) { ◦ Do something specific to each r->int_no } But what to do? ◦ We need a generic default behaviour (displaying a message) ◦ We want personalized default messages for the first 32 interrupts ◦ We want to be able to easily “plug in” new custom interrupt handlers for the first 32 interrupts
44
How to plug in new ISR? ◦ An ISR is a function – it has an address ◦ So we’ll keep track of ISR addresses void *idt_routines[32]; ◦ Then we can plug in an ISR by putting in the function’s address at the entry in the arrat void i86_install_handler(uint32_t idt, void (*handler)(struct isrregs *r)) { idt_routines[idt] = handler; } ◦ We can also unplug an ISR by setting that entry in the array to 0 void i86_uninstall_handler(uint32_t idt) {idt_routines[idt] = 0;} Interrupt Handler Routine (plug in) Richard Khoury44
45
Our top part can check if there is a custom function installed for an interrupt by checking the array void (*handler)(struct isrregs *r); handler = idt_routines[r->int_no]; if ( (uint32_t)handler != 0 ) { handler(r);} else { default behaviour } So don’t forget to initialize all entries to 0! Interrupt Handler Routine (plug in) Richard Khoury45
46
Richard Khoury46 Lab Assignment Add the HAL to your kernel Write the three-part Interrupt Handler Routines for the first 32 interrupts ◦ Don’t forget to install your new handlers in the IDT instead of the default handler! Test with inline Assembly interrupts and make sure it displays the right message! ◦ When you compile the IDT you will get a “cast from pointer to integer” warning – that’s normal
Similar presentations
© 2025 SlidePlayer.com. Inc.
All rights reserved.