6. HAL and IDT ENGI 3655 Lab Sessions. Richard Khoury2 Textbook Readings Interrupts ◦ Section...
-
Upload
lynn-lucas -
Category
Documents
-
view
216 -
download
0
description
Transcript of 6. HAL and IDT ENGI 3655 Lab Sessions. Richard Khoury2 Textbook Readings Interrupts ◦ Section...
6. HAL and IDTENGI 3655 Lab Sessions
Richard Khoury 2
Textbook Readings Interrupts
◦ Section 13.2.2 Hardware Abstraction Layer
◦ Section 22.3.1
Richard Khoury 3
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
Richard Khoury 4
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
Richard Khoury 5
Hardware Abstraction Layer Our initial HAL will have few parts HAL
◦ CPU Registers GDT IDT
◦ Basic memory-handling functions
Richard Khoury 6
Hardware Abstraction Layer HAL.h
◦ Included in our main file◦ Defines the HAL initialization and shutdown
functionsextern uint32_t hal_initialize();extern uint32_t hal_shutdown();◦ The initialize function is the only one the kernel
calls; it will initialize everything else
Richard Khoury 7
HAL CPU The initialize and shutdown functions activate and
deactivate the hardware◦ For now, our only hardware is the CPU
CPU.hextern uint32_t cpu_initialize ();extern void cpu_shutdown (); Registers.hstruct _R16BIT { uint16_t ax, bx, cx, dx, si, di, bp, sp,
es, cs, ss, ds, flags; uint8_t cflag;};
Richard Khoury 8
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
Richard Khoury 9
HAL GDT We’ll create a GDT structure to store the informationstruct gdt_descriptor {uint16_t limit;uint16_t baseLo;uint8_t baseMid;uint8_t access;uint8_t gran;uint8_t baseHi;
} __attribute__((packed)); We can create each of our five descriptors with these But then we need a place to store themstatic struct gdt_descriptor _gdt [MAX_DESCRIPTORS];
Richard Khoury 10
HAL GDT Next we can define the
descriptors We can even define
constants to make the codemore readable
/*Bits 5-6: Privilege*/#define I86_GDT_DESC_RING0 0x0000 //_00_____#define I86_GDT_DESC_RING1 0x0020 //_01_____#define I86_GDT_DESC_RING2 0x0040 //_10_____#define I86_GDT_DESC_RING3 0x0060 //_11_____ And we create each descriptorgdt_set_descriptor(uint32_t i, uint64_t base, uint64_t limit, uint8_t access, uint8_t gran)
; code descriptordw 0FFFFh ; limit lowdw 0 ; base lowdb 0 ; base middledb 10011010b ; accessdb 11001111b ; granularitydb 0 ; base high
Richard Khoury 11
HAL GDT Recall that loading the GDT into the CPU
requires two things A special structure representing the start
and size of the GDTtoc:
dw end_of_gdt - gdt_data - 1dd gdt_data
A special Assembly instructionlgdt [toc]
We’ll need to do that from C
Richard Khoury 12
HAL GDT We’ll start by defining the GDT structure
struct gdtr {uint16_t m_limit;uint32_t m_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 ();
Richard Khoury 13
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
Richard Khoury 14
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:endgdtendgdt: ret
Richard Khoury 15
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/cIDT.h/c Mode7.c
Mode7.h
Data types(stdint.h)
Registers.h
Richard Khoury 16
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
Richard Khoury 17
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)
Richard Khoury 18
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
Richard Khoury 19
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
Richard Khoury 20
Handling Interrupts Our OS doesn’t handle interrupts yet Try it: add an interrupt in the C kernel using
inline Assembly codeasm volatile ("int $0x3");
What happens?
Richard Khoury 21
For This Lab... We will handle interrupts
◦ Build an interrupt descriptor table (IDT)◦ Write interrupt handlers
Richard Khoury 22
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
Richard Khoury 23
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
Richard Khoury 24
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
Richard Khoury 25
Descriptor StructureBits Interrupt
GateTrap Gate Task Gate
0-15 Interrupt Handler Routine address (0-15)
Not used
16-31 Code Segment Selector Task State Segment (TSS) Selector
32-35 Not Used36-39 Reserved (set to zero) Not used
Richard Khoury 26
Descriptor StructureBits Interrupt
GateTrap Gate Task Gate
40-44Type
01110: 32 bits00110: 16 bits
01111: 32 bits00111: 16 bits
00101
45-46 Privilege Ring Level (00 highest – 11 lowest)47 0/1 = Segment is absent/present48-63 Interrupt Handler Routine
address (16-31)Not used
Richard Khoury 27
Descriptor Structure Easy to represent as a C structure
◦ Remember our C data type interface from last week?
struct idt_descriptor {uint16_t baseLo;uint16_t sel;uint8_t reserved;uint8_t flags;uint16_t baseHi;
};
Richard Khoury 28
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
Richard Khoury 29
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
Richard Khoury 30
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()
Richard Khoury 31
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);}
Richard Khoury 32
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_RING0 0x00 //_00_____#define I86_IDT_DESC_RING3 0x60 //_11_____
Richard Khoury 33
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_t limit;uint32_t base;
} __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();
Richard Khoury 34
Loading the IDT The Assembly function is even simpler than
the one to load the GDTglobal _idt_installextern __idtr
_idt_install: lidt [__idtr] ret
Richard Khoury 35
Loading the IDT Once it’s done, test it We already added an interrupt in the C
kernelasm volatile ("int $0x3");
What happens now?
Richard Khoury 36
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
Richard Khoury 37
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
Six interrupts automatically push an error code before the Low Part
Interrupt Handler Routine
Richard Khoury 38
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
Richard Khoury 39
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
Richard Khoury 40
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
Richard Khoury 41
Interrupt Handler Routine (top part) Top Part is a C functionvoid 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?
Richard Khoury 42
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
Richard Khoury 43
Interrupt Handler Routine (top part) Now we can pass it to our Top Part and use itvoid 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
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 arratvoid 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 0void i86_uninstall_handler(uint32_t idt){idt_routines[idt] = 0;}
Interrupt Handler Routine (plug in)
Richard Khoury 44
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 Khoury 45
Richard Khoury 46
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