Reputation: 27
I can't understand why the keyboard interrupt service routine I wrote for my program (should print "hello world" each time I press a key) only occurs once when I execute the .exe on dosbox. Here is the code:
NAME keyb
PILE SEGMENT STACK
db 20 dup ('LA PILE ')
PILE ENDS
DONNEE SEGMENT
message db "Hello wolrd !$"
DONNEE ENDS
PROGRAMME SEGMENT
ASSUME CS:PROGRAMME , DS:DONNEE, ES:NOTHING, SS:PILE
debut:
mov ax,DONNEE
mov ds,ax
cli
xor ax,ax
mov es, ax ; load ES with segment address of interrupt pointer table
mov bx, 24h ; load BX with interrupt type
mov word ptr es:[bx], OFFSET SUBR ; isr address
mov word ptr es:[bx]+2, SEG SUBR ; isr segment
mov ax, 01h
sti
BOUCLE: jmp BOUCLE
SUBR PROC NEAR
cli
mov ah,9
mov dx,OFFSET message
int 21h
sti
IRET
SUBR ENDP
PROGRAMME ENDS
END debut
I tried a few things, like push-ing and pop-ing registers, using another interrupt (system clock 08h), but none of them worked. I know that the ISR runs at least once because the message "hello world" appears on screen, but it should print every time I press a key and I have no idea why it doesn't.
How can I resolve this?
Upvotes: 2
Views: 1703
Reputation: 14409
You have to do some work to "free" keyboard and interrupt. Take a look here. The easiest way is to jump to the old IRQ-handler at the end of your own handler:
NAME keyb
PILE SEGMENT STACK
db 20 dup ('LA PILE ')
PILE ENDS
DONNEE SEGMENT
message db "Hello wolrd !$"
oldvec dw 0, 0
DONNEE ENDS
PROGRAMME SEGMENT
ASSUME CS:PROGRAMME , DS:DONNEE, ES:NOTHING, SS:PILE
debut:
mov ax,DONNEE
mov ds,ax
cli
xor ax,ax
mov es, ax ; load ES with segment address of interrupt pointer table
mov bx, 24h ; load BX with interrupt type
; Store the old IRQ vector
mov ax, es:[bx]
mov oldvec, ax
mov ax, es:[bx+2]
mov oldvec + 2, ax
mov word ptr es:[bx], OFFSET SUBR ; isr address
mov word ptr es:[bx]+2, SEG SUBR ; isr segment
mov ax, 01h
sti
BOUCLE: jmp BOUCLE
SUBR PROC NEAR
push ax
push dx
mov ah,9
mov dx,OFFSET message
int 21h
pop dx
pop ax
jmp dword ptr [oldvec]
SUBR ENDP
PROGRAMME ENDS
END debut
Upvotes: 1
Reputation: 13109
Basically put, unless you call the default handler after you've done your work, you'll have to tell the PIC (programmable interrupt controller) that you're done - the default handler will do this for you - sending the EOI (end of interrupt signal) to the PIC(s). The PIC wont fire the interrupt again until you tell it that you've done with the current one.
Code to do this in 32 bit protected mode looks something like this. Do note, that I use a generic handler for all IRQs, just handing off to the appropriately registered user callback function. I've included both.
First, the generic IRQ handler
irq_handler:
push ebp
mov ebp, esp
add ebp, 8
mov eax, [ebp +registers_t.int_no]
cmp eax, IRQ7 ; this just dumps spurious IRQ7 interrupts
je .irqHandlerDone
cmp eax, IRQ8 ; if it's IRQ0 - IRQ7, the first controller fired the int, otherwise the slave controller did
jb .slaveResetDone
.resetSlave:
mov al,20H ; send End-Of-Interrupt signal
out 0xA0,al ; to the 8259 _slave_ Programmable Interrupt Controller
.slaveResetDone:
.resetMaster:
mov al, 0x20 ; send End-Of-Interrupt signal
out 0x20, al ; to the 8259 master Programmable Interrupt Controller
mov eax, [ebp + registers_t.int_no]
shl eax, 2 ; x4
mov esi, interrupt_handlers
add esi, eax ; esi --> interrupt_handlers[int_no]
cmp dword [esi], 0
je .irqHandlerDone
call [esi]
.irqHandlerDone:
pop ebp
ret
Next, the IRQ1 (keyboard) handler. The registration function simply copies the offset of the function into a table (interrupt_handlers
) of 32 bit addresses.
I'm in flat-memory mode (4GB addressable, ES already holds the segment selector for a writeable data segment. 0xB8000 + 79*2 simply points to the character on the top-right of a 80x25 mode3 text screen)
; keyboard IRQ handler
irq1Handler:
push ebp
mov ebp, esp
add ebp, 8+4
in al, 0x60
mov bl, al
mov byte [port60], al
in al, 0x61
mov ah, al
or al, 0x80
out 0x61, al
xchg ah, al
out 0x61, al
and bl, 0x80
jnz .done
pusha
;mov al, [port60]
;call outputChar
mov edi, 0xB8000 + 79*2
mov al, [port60]
mov [es:edi], al
popa
.done:
pop ebp
ret
port60 db 0
The code draws from James M's tutorial, here: http://www.jamesmolloy.co.uk/tutorial_html/5.-IRQs%20and%20the%20PIT.html
There is much that can be read about interfacing to hardware over at OSDev.org - http://wiki.osdev.org/Main_Page
UPDATE: Here's a 16bit ISR that functions in DosBox.
;-----------------------------------------------------
; handles int 0x09
;-----------------------------------------------------
keyhandler:
cli
pusha
in al, 0x60 ; get key data
mov bl, al ; save it
mov byte [port60], al
in al, 0x61 ; keybrd control
mov ah, al
or al, 0x80 ; disable bit 7
out 0x61, al ; send it back
xchg ah, al ; get original
out 0x61, al ; send that back
mov al, 0x20 ; End of Interrupt
out 0x20, al ;
and bl, 0x80 ; key released
jnz done ; don't repeat
mov al, [port60]
;
; do something with the scan-code here
;
done:
popa
iret
port60 db 0 ; where we'll store the scan-code
Upvotes: 1