I define these things statically. Uses less memory, less cpu, less lines of code and just as easy to expand on.
I would have to calculate off hand where I expect the ISR's to start, where the IDT will be, how big everything will be......
No, why would do such a thing? We have symbolic assemblers for a reason.
Generate the ISR's with macros. You can add any number of them without recalculating anything:
Code: Select all
; ------------------------------------------
; Exception/Interrupt Service Routine Stubs
; ------------------------------------------
section '.code'
macro ISR_ZEROERR [NUM] {
isr#NUM:
cli
push 0
push NUM
jmp isr_common_stub
}
macro ISR_CPUERR [NUM] {
isr#NUM:
cli
push NUM
jmp isr_common_stub
}
; For the exceptions:
ISR_ZEROERR 0, 1, 2, 3, 4, 5, 6, 7, 9, 15, 16, 17, 18
ISR_CPUERR 8, 10, 11, 12, 13, 14
ISR_ZEROERR 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31
; For the interrupts:
ISR_ZEROERR 32, 33, 34, 35, 36, 37, 38, 39, 40
ISR_ZEROERR 41, 42, 43, 44, 45, 46, 47
; For the system call interface:
ISR_ZEROERR 48, 49, 50
Then you make your IDT, make sure to repeat the correct number of times to cover all the ISRs. Notice the symbol KERNEL_CODE_SEL, it will be computed later, you don't have to hand compute it and fill in the number here.
Code: Select all
; ------------------------------------------
; Interrupt Descriptor Table
; ------------------------------------------
; idt_entry is
; 0 base_low.w
; 2 code_segment_selector.w
; 4 always0.b
; 5 flags.b
; 6 base_high.w
; end idt_entry = 8 bytes
section '.data' align 8
idtr: dw idt_end - idt - 1 ; IDT limit
dd idt ; address of IDT
; ------------------------------------------
macro make_idt_entry {
dw 0 ; isr0 and 0xFFFF
dw KERNEL_CODE_SEL
db 0
db 0x8E ; Access flags: Running in kernel mode (| 0x60 for usermode)
dw 0 ; (isr#num shr 16) and 0xFFFF
}
align 8
idt: repeat 32+16+3 ; exceptions+interrupts+syscall
make_idt_entry
end repeat
idt_end:
Because the elf object format doesn't support shifted symbols (in expressions) the entries has to be filled in at run-time (if you want to assemble to an object file and link it with other object files, if you assemble directly to final executable you can probably fill them in statically). Still, setting the entries is almost automatic with the macro:
Code: Select all
; IDT -> ISR installation
macro set_idt_entry [num] {
; base_lo = isr0 and 0xFFFF
mov eax, isr#num
mov [idt+num*8], ax
; base_hi = (isr0 >> 16) and 0xFFFF
shr eax, 16
mov [idt+num*8+6], ax
}
section '.code'
idt_install:
public idt_install
; remap irq here
; connect idt entries to isrs
set_idt_entry 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
set_idt_entry 11, 12, 13, 14, 15, 16, 17, 18, 19
set_idt_entry 20, 21, 22, 23, 24, 25, 26, 27, 28, 29
set_idt_entry 30, 31, 32, 33, 34, 35, 36, 37, 38, 39
set_idt_entry 40, 41, 42, 43, 44, 45, 46, 47, 48, 49
set_idt_entry 50
; Change access rights on int 50 to ring 3 so we can communicate
; with the kernel through this interrupt
or byte [idt+50*8+5], $60
lidt [idtr]
ret
The GDT is simple. If things are moved around, no problem, the selector constants are updated automatically. The +3 means user-mode.
Code: Select all
; ------------------------------------------
; Global Descriptor Table
; ------------------------------------------
section '.code'
public gdt_install
gdt_install:
lgdt [gdtr]
mov ax, KERNEL_DATA_SEL
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
jmp KERNEL_CODE_SEL:@f
@@:
call tss_install
ret
section '.data' align 8
; gdt_entry is
; limit_low.w
; base_low.w
; base_middle.b
; access.b
; granularity.b
; base_high.b
; end gdt_entry = 8 bytes
gdtr: dw gdt_end - gdt - 1 ; GDT limit
dd gdt ; address of GDT
; ------------------------------------------
align 8
; null descriptor
gdt: dw 0
dw 0
db 0
db 0
db 0
db 0
; kernel code
KERNEL_CODE_SEL = $-gdt ; Segment selector
gdt2: dw 0xFFFF ; limit 0xFFFFF
dw 0 ;
db 0 ;
db 0x9A ; present, ring 0, code, readable
db 0xCF ; page-granular, 32-bit
db 0 ;
; kernel data
KERNEL_DATA_SEL = $-gdt ; Segment selector
gdt3: dw 0xFFFF ; limit 0xFFFFF
dw 0 ; base 0
db 0
db 0x92 ; present, ring 0, data, writable
db 0xCF ; page-granular, 32-bit
db 0
; user code
USER_CODE_SEL = $-gdt+3 ; Segment selector
gdt4: dw 0xFFFF
dw 0
db 0
db 0xFA ; present, ring 3, code, readable
db 0xCF
db 0
; user data
USER_DATA_SEL = $-gdt+3
gdt5: dw 0xFFFF
dw 0
db 0
db 0xF2 ; present, ring 3, data, writable
db 0xCF
db 0
; user TSS
USER_TSS_SEL = $-gdt
gdt6: dw 103
dw 0 ; set to tss
db 0
db 0xE9 ; present, ring 3, 32-bit available TSS
db 0
db 0
gdt_end: