I managed to load a small kernel into memory via a bootloader that performs a far jump to 0x0090:0x0000
. The kernel is loaded successfully as I print a character from there to test it and it works properly.
I wanted to remap interrupts 0x08->0x0F
and 0x70->0x77
to interrupts 0x20->0x2F
, so the exception/reserved interrupts are not overlapped. So far, I am only handling a keyboard press and attempting to print it to the screen.
I went over it a bunch of times and for some reason, I just don't know why but nothing happens when I press a key.
The keymap is just an array of the scancodes to their respected ASCII value.
If this is of any help: I tested running a loop and printing a character then HLT
ing, and once 2 characters were printed, it hanged. I made sure to enable interrupts.
(I'm loading 4 sectors (from sector 2 to sector 5) for the bootloader which is why I'm padding this to make it 2048 bytes in size).
By the way, I don't have to CLI
in my interrupt procedures since it's done for me, right? I remember reading this but I am not too sure.
BITS 16
ORG 0x0000
; Setup Segments ;
cli
cld
mov ax, cs
mov ds, ax ; this program was far-jumped to (0x0090:0x0000) so ds = cs = 0x0090
mov ax, VIDEO_ORIGIN
mov es, ax
; Remap PIC Interrupt Vector Offsets to 0x20 -> 0x35 ;
remapInterrupts:
; Send Initialization Command (expecting ICW4)
mov al, 0x11
out 0x20, al
out 0xA0, al
; Remap Vector Offsets (ICW2)
mov al, 0x20 ; Master IRQ lines mapped to 0x20 -> 0x27
out 0x21, al
mov al, 0x28 ; Slave IRQ lines mapped to 0x28 -> 0x2F
out 0xA1, al
; Set Cascade Lines between Master and Slave PICs (ICW3)
mov al, 0x04 ; 00000100 (line 2)
out 0x21, al
mov al, 0x02 ; 00000010 (line 2 in binary, cascade identity)
out 0xA1, al
; Set 80x86 Mode (ICW4)
mov al, 0x01
out 0x21, al
out 0xA1, al
; Set Masks
mov al, 0xFD ; 11111101 (keyboard)
out 0x21, al
mov al, 0xFF ; 11111111
out 0xA1, al
setInterrupts:
push ds
mov ax, 0x0000
mov ds, ax
mov [ds:0x84], word interrupt21 ; 0x84 = 0x21 * 4
mov [ds:0x86], cs
pop ds
jmp start
interrupt20: ; Programmable Interval Timer
; NOT SUPPORTED, place holder
push ax
mov al, 0x20
out 0x20, al
pop ax
iret
interrupt21: ; Keyboard
push ax
push bx
in al, 0x60
test al, 0x80 ; high-bit set = keyup = don't print
jnz .finish
movzx bx, al
mov al, [keymap + bx]
mov ah, 0x07
stosw
.finish:
mov al, 0x20
out 0x20, al
pop bx
pop ax
iret
interrupt22: ; Slave Cascade
interrupt23: ; COM2 / COM4
interrupt24: ; COM1 / COM3
interrupt25: ; LPT2
interrupt26: ; Floppy controller
interrupt27: ; LPT1
; NOT SUPPORTED, place holder
push ax
mov al, 0x20
out 0x20, al
pop ax
iret
interrupt28: ; RTC
interrupt29: ; Unassigned
interrupt2A: ; Unassigned
interrupt2B: ; Unassigned
interrupt2C: ; Mouse Controller
interrupt2D: ; Math Coprocessor
interrupt2E: ; Hard Disk Controller 1
interrupt2F: ; Hard Disk Controller 2
; NOT SUPPORTED, place holder
push ax
mov al, 0x20
out 0xA0, al
out 0x20, al
pop ax
iret
start:
sti
xor di, di
jmp $
; --- CONSTANTS --- ;
VIDEO_ORIGIN EQU 0xB800
; --- DATA --- ;
drive db 0
keymap:
db 00h, 1Bh, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', 08h, 09h
db 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '[', ']', 00h, 00h
db 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ';', "'", '`', 00h, '\'
db 'Z', 'X', 'C', 'V', 'B', 'N', 'M', ',', '.', '/', 00h, 00h, 00h, ' ', 00h,
db 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h,
db '-', 00h, 00h, 00h, '+', 00h, 00h, 00h, 00h, 00h
times 2048 - ($ - $$) db 0
Real mode interrupt routines have to be developed as if nothing but the CS register is known (and the interrupt flag is cleared). CS:IP is set via the interrupt vector when we get a hardware interrupt. CS will be the segment we wrote to the interrupt vector table. In your case it was 0x0090 since you did this (with DS=0x0000) to update the interrupt vector table:
Since we can't rely on DS being what we want when our interrupt handler is called we can either push DS onto the stack, copy CS to DS and access our memory variables via DS, and then restore DS. Alternatively we can modify our memory operands so they explicitly use the CS register. We could modify the interrupt handler to look like this:
I've documented the lines I added. But important things are:
movzx
we can simply clear the upper part of the BX register to zero.movzx
is only available on 386 processors. You can keep that instruction if you are on 386+, but if you intend to target 8086/8088/80188/80286 then you can't use it.Modify your start routine to be:
Some emulators don't always do screen refreshes if you do a tight loop with
jmp $
. A better way to do it is with the HLT instruction. When interrupts are on the CPU will halt the processor until the next interrupt occurs. When one does occur it will be serviced by the interrupt handlers and eventually will fall to the next instruction. In this case we jump back and do the HLT again waiting for the next interrupt.Since we added a new variable to keep track of the screen cell we are writing to, we will need to add
videopos
to you.data
segment:These changes do seem to work on Bochs, QEMU, and VirtualBox. If this doesn't work for you then possibly you aren't loading the second stage (4 sectors worth) properly into 0x0090:0x0000? Since I can't see your first stage code I can't really say for certain.