2

There are many questions here on SO related to overriding the default int 9 ISR, and I have read through them carefully. My issue appears to be unique. There are many good examples of writing keyboard interrupt handlers, but they involve chaining on to the original ISR.

I would like to totally replace the original int 9 ISR. I don't use the int 16h BIOS keyboard services and I don't mind if the keyboard data area isn't managed. In fact, replacing the ISR would be beneficial to me in two ways.

  1. My own ISR would probably be faster. I could quickly react to the incoming make/break code, decide what should be done in a way that is specifc to my needs, then immediately resume my program.

  2. I would no longer need to include code in my main loop to clear the type-ahead buffer.

Here's a program I wrote which installs keyboard (int 9) and timer (int 1ch) ISRs. The program responds to keystrokes by writing pixels to the screen. In this example the keyboard ISR is chained on to the original int 9 ISR and it works perfectly fine in both DOSBox, and hardware (Pentium laptop running DOS). The timer ISR justs updates pixel 0 in the buffer every 55 milliseconds. It's used as an indicator of the current state of the program (crashed, or not). Since i'm messing around with the keyboard ISR I can't always tell if the machine has locked up by, for instance, pressing caps lock and seeing if the LED indicator lights up.

;----------------------------------------------------------------------------|                                                                     
 theStack SEGMENT STACK                                                     ;|
;____________________________________________________________________________|
 db 64 dup ('THESTACK')   
;----------------------------------------------------------------------------|
 theStack ENDS                                                              ;|
;____________________________________________________________________________|








;----------------------------------------------------------------------------|   
 varData SEGMENT                                                            ;|                            
;____________________________________________________________________________|


 pixCol db ?  ;current color index
 pixPos dw ?  ;current position in VGA pixel buffer

;----------------------------------------------------------------------------|
 varData ENDS                                                               ;|
;____________________________________________________________________________|







;----------------------------------------------------------------------------|
 code SEGMENT       

 assume cs:code,ds:varData

 oldInt9  dd 0                   ;original int 9 target
 oldInt1c dd 0                   ;original int 1c target                            

 main PROC                                                                  

 start:                                                                      

    mov ax, varData                            
    mov ds, ax                   ;Load the variable segment into ds           
;____________________________________________________________________________|






    call SET_VGA_256             ;set video mode 13h
    call INSTALL_ISR             ;install the ISRs

    mov ax, 40h
    mov es, ax                   ;access keyboard data area via segment 40h

loop0:
                                 ;clr type-ahead buff, prevents PC spkr beep

                                 ;(has to occur at some point if you don't 
                                 ;use int 16h BIOS keyboard services, and
                                 ;the original int 9 ISR is still handling
                                 ;keystrokes)

    mov WORD PTR es:[1ah], 1eh   ;set the kbd buff head to start of buff
    mov WORD PTR es:[1ch], 1eh   ;set the kbd buff tail to same as buff head


    inc pixCol                   ;increment the current pixel color
    inc pixPos                   ;increment the current pixel position
    cmp pixPos, 63999d           ;Is pixPos < pixel area
    jb loop0                     ;If so, loop back
    mov pixPos, 0                ;If not, reinit pixPos

    in al, 60h                   ;al <- last key press
    cmp al, 1                    ;Was the key Esc
    jne loop0                    ;If not, loop back

    call EXIT2DOS                ;Uninstall ISRs, restore text mode, exit.                 






SET_VGA_256 PROC

;SETS VIDEO MODE TO 13H (VGA 320x200 256 COLORS)

    push ax
    mov ax, 13h   ;320x200 256 colors
    int 10h       ;video mode set 
    pop ax 
    ret

SET_VGA_256 ENDP




INSTALL_ISR PROC

;PATCHES NEW VECTOR ENTRIES INTO THE INTERRUPT VECTOR TABLE.
;THE DEFAULT INPUT (INT 9H) ISR IS REPLACED WITH MY OWN INPUT HANDLING ISR.
;Int 1C TIMER ISR IS CHAINED FOR DEBUGGING PURPOSES.
;THE OLD VECTOR ENTRIES ARE BACKED UP. THEY WILL BE RESTORED BEFORE PROGRAM
;TERMINATION.


    push es


    ;// BACKUP THE OLD IVT ENTRIES //

    cli                                      ;disable hardware interrupts
    xor ax, ax
    mov es, ax                               ;es <- 0
    mov ax, WORD PTR es:[9*4]                ;ax <- offset part of IVT entry 
    mov WORD PTR cs:oldInt9, ax              ;store it in MSW of oldInt9
    mov ax, WORD PTR es:[9*4+2]              ;si <- segment part of IVT entry 
    mov WORD PTR cs:oldInt9+2, ax            ;store it in LSW of oldInt9

    mov ax, WORD PTR es:[1ch*4]     
    mov WORD PTR cs:oldInt1c, ax   
    mov ax, WORD PTR es:[1ch*4+2]  
    mov WORD PTR cs:oldInt1c+2, ax           ;store INT 1C IVT entry



    ;// INSTALL THE NEW INTERRUPT HANDLERS //

    mov WORD PTR es:[9*4], OFFSET INPUT_ISR   ;copy my new ISR offset to IVT   
    mov WORD PTR es:[9*4+2], cs               ;copy my code segment to IVT

    mov WORD PTR es:[1ch*4], OFFSET TIMER_ISR  
    mov WORD PTR es:[1ch*4+2], cs             ;do the same for int 1c entry    
    sti                                       ;enable hardware interrupts


    pop es
    ret

INSTALL_ISR ENDP




INPUT_ISR PROC

;PRINTS A PIXEL OF SOME COLOR INDEX TO SOME LOCATION IN THE PIXEL BUFFER
;WHEN A KEYSTROKE OCCURS. ONE KEYSTROKE GENERATES TWO INTERRUPTS, ONE FOR 
;KEY-DOWN, AND ONE FOR KEY-UP. 


    pushf                       
    cli                            ;disable hardware interrupts
                                   ;(disabled anyway upon entry)
    push ds
    push es
    push bx
    push ax

    mov bx, varData              
    mov ds, bx                     ;ds <- data segment
    mov bx, 0a000h
    mov es, bx                     ;es <- VGA pixel buffer
    mov bx, pixPos                 ;bx <- current pixel position
    mov ah, pixCol                 ;ah <- current pixel color 
    mov BYTE PTR es:[bx], ah       ;write the pixel to the buffer

    pop ax
    pop bx
    pop es
    pop ds
    popf

    jmp cs:oldInt9                 ;now execute original ISR


INPUT_ISR ENDP             




TIMER_ISR PROC

;USED FOR DEBUGGING. IF THE COLOR OF PIXEL POSITION 0 STOPS UPDATING, THINGS
;HAVE GONE VERY WRONG. 



    pushf                       
    cli                            ;disable hardware interrupts
                                   ;(disabled anyway upon entry)
    push ds
    push es
    push bx
    push ax


    mov bx, varData              
    mov ds, bx                     ;ds <- data segment
    mov bx, 0a000h
    mov es, bx                     ;es <- VGA pixel buffer
    mov ah, pixCol                 ;ah <- current pixel color
    xor bx, bx                     ;bx <- pixel position 0 
    mov BYTE PTR es:[bx], ah       ;write the pixel to the buffer


    pop ax
    pop bx
    pop es
    pop ds
    popf

    jmp cs:oldInt1c                ;now execute original ISR


TIMER_ISR ENDP






EXIT2DOS PROC

;UNINSTALL ISR, CLEAR THE TYPE-AHEAD BUFFER, RESTORE TEXT MODE, AND EXIT.

    cli                             ;disable hardware interrupts
    xor ax, ax
    mov es, ax

    mov ax, WORD PTR cs:oldInt9
    mov WORD PTR es:[9*4], ax 
    mov ax, WORD PTR cs:oldInt9+2
    mov WORD PTR es:[9*4+2], ax     ;Original int 9 ISR restored to IVT

    mov ax, WORD PTR cs:oldInt1c
    mov WORD PTR es:[1ch*4], ax 
    mov ax, WORD PTR cs:oldInt1c+2
    mov WORD PTR es:[1ch*4+2], ax   ;Original int 1c ISR restored to IVT
    sti                             ;enable hardware interrupts


                                    ;clear type-ahead buffer just before exit 
                                    ;to prevent dumping garbage characters
                                    ;to DOS prompt.
    mov ax, 40h
    mov es, ax                      ;access kbd data area via segment 40h
    mov WORD PTR es:[1ah], 1eh      ;set the kbd buff head to start of buff
    mov WORD PTR es:[1ch], 1eh      ;set kbd buff tail to same as buff head
                                    ;now the keyboard buffer is cleared.

    xor ah, ah                      ;select video mode function
    mov al, 3                       ;select 80x25 16 colors
    int 10h                         ;restore VIDEO back to text mode

    mov ax, 4c00h                   ;Terminate process DOS service
    int 21h                         ;Control returns to DOS

EXIT2DOS ENDP








;----------------------------------------------------------------------------|
 main ENDP                                                                  ;|
                                                                            ;|
 code ENDS                                                                  ;|
                                                                            ;|
 END start                                                                  ;| 
;____________________________________________________________________________|   

If I change the INPUT_ISR procedure slightly, so that it sends an EOI to the PIC and executes an IRET the original int 9 ISR will never execute.

INPUT_ISR PROC

    pushf                       
    cli                            ;disable hardware interrupts
                                   ;(disabled anyway upon entry)
    push ds
    push es
    push bx
    push ax

    mov bx, varData              
    mov ds, bx                     ;ds <- data segment
    mov bx, 0a000h
    mov es, bx                     ;es <- VGA pixel buffer
    mov bx, pixPos                 ;bx <- current pixel position
    mov ah, pixCol                 ;ah <- current pixel color 
    mov BYTE PTR es:[bx], ah       ;write the pixel to the buffer

    mov al, 20h
    out 20h, al                    ;EOI 

    pop ax
    pop bx
    pop es
    pop ds
    popf

    iret

INPUT_ISR ENDP 

However, this causes issues in both DOSBox and on hardware. In DOSBox it doesn't crash, but erratic behavior occurs. Pixels will often bunch up on top of each other in the upper right hand corner of the window, and the pixels are sluggish to appear on the screen. The timer ISR still executes and the program can be terminated normally. In hardware an instant crash occurs. The timer ISR stops updating it's pixel, and the machine has to be rebooted.

Is it even safe to replace the int 9 ISR? I know it's specific from machine to machine as to what it does behind the scenes, but does it often do things that are system critical? As far as I know it just manages the keyboard data area, right?

bad
  • 939
  • 6
  • 18
  • Okay, so now looking at the code I should make you aware of something. If you **replace** the Int 9h handler with your own, the BIOS Data area (BDA) will not contain keyboard related data (like the look ahead buffer).The BIOS installs an interurpt handler that updates the BDA as each character is pressed. – Michael Petch Mar 28 '18 at 02:41
  • Yeah, I know.. But when I chain to the int 9 ISR, the BDA will be managed. That's why the type-ahead buffer is cleared in the main loop. If I could replace the int 9 ISR, I wouldn't need to clear the buffer, and no annoying PC speaker beep will occur. – bad Mar 28 '18 at 02:44
  • If you are chaining then you can't send the EOI because the original handler will do the EOI – Michael Petch Mar 28 '18 at 02:45
  • Yes, I know. The second code example is the version of my ISR that doesn't chain. – bad Mar 28 '18 at 02:46
  • If you are chaining I don't see where in your code you are calling the original BIOS interrupt? – Michael Petch Mar 28 '18 at 02:46
  • In the first code example, scroll down to `INPUT_ISR`, I execute a `jmp cs:oldInt9` – bad Mar 28 '18 at 02:47
  • Your second code won't work because it doesn't chain to the original so the loop in the main program will never see keyboard input because the BDA is no longer being updated as keys are pressed – Michael Petch Mar 28 '18 at 02:47
  • But I'm looking at your EOI version and there is no chaining so I don't know how you expected that to work. – Michael Petch Mar 28 '18 at 02:47
  • Thats the unchained version. That is the version of the procedure that causes the issues. When it replaces the version in the first code example, that's when I experience the issue. – bad Mar 28 '18 at 02:50
  • And the EOI version sending EOI wouldn't be enough anyway. You have to at least read a character from the keyboard at each interrupt or you won't get further interrupts from the keyboard. Possibly that is your issue. You need to read a character from the keyboard even if you throw it away for test purposes. – Michael Petch Mar 28 '18 at 02:50
  • Well that's probably the issue. So should I just read port 60h before ending the interrupt? – bad Mar 28 '18 at 02:51
  • Yes, read port 60h even if you just throw it away. Since it is in the interrupt you don't even have to check the bit to determine if the keyboard controller is ready to have a character read from it (something you need to do if you use keyboard polling) – Michael Petch Mar 28 '18 at 02:52
  • Solved. Thanks. Where Is that documented? In all the countless web pages I have read on writing keyboard ISRs, I have never heard of needing to read port 60h before sending the EOI. It's just never mentioned. Let me try this on actual hardware and get back to you. – bad Mar 28 '18 at 02:55
  • Yep, works on hardware too. What's the actual issue at the hardware level when you don't read port 60h before sending EOI? Why does it halt the machine? Anyway, if you provide that as an actual answer i'll award you the solution. – bad Mar 28 '18 at 03:02
  • The machine isn't halting. When you get an interrupt if you don't read the byte currently available no further interrupts will occur. That probably makes your program look like nothing is happening. No interrupts, nothing to process. I will have to look up in the documentation about having to read a character to ensure future key presses generate an interrupt. But having done keyboard handler in the past, it is just something I know from experience if you don't chain to the BIOS key handler. The BIOS version of the interrupt reads a char from port 60h which is why your original version works. – Michael Petch Mar 28 '18 at 03:06
  • Of course the BIOS keyboard handler also does an EOI already which is why you don't send one if you chain. In fact sending an extra EOI can actually cause problems. – Michael Petch Mar 28 '18 at 03:10
  • I know this comment chain is getting kind of long, but does it disable all interrupts. The timer ISR stops working, as well. You would think that it would just mask out IRQ 1. – bad Mar 28 '18 at 03:11
  • No, if you don't read a character from the keyboard, only future keyboard interrupts will be affected. All other interrupts should work as expected. The keyboard controller seeing there is an unread character in the buffer will just refuse to send another interrupt until the current character is read. – Michael Petch Mar 28 '18 at 03:11
  • It doesn't though, that's the weird thing. Timer ISR stops updating it's pixel. Tried it on three different machines now. That's why i'm interested in what actually happens if you fail to read port 60h. – bad Mar 28 '18 at 03:13
  • You must have another issue with your timer or interrupts then. i can tell you that not reading from the keyboard when a keyboard interrupt occurs only affects future keyboard interrupts, not all interrupts. – Michael Petch Mar 28 '18 at 03:16
  • 1
    Although it shouldn't have an effect on the timer (at least I don't see why) you should not be doing this in the main loop `in al, 60h ;al <- last key press cmp al, 1 ;Was the key Esc` . You should be setting some global variable and have your interrupt handler set a flag saying escape has been pressed. There is actually no guarantee that if you read a character from the keyboard handler that it will be the same value if you read it a second time (one in the interrupt handler and another potentially in the loop) – Michael Petch Mar 28 '18 at 03:23
  • If you are chaining you should look at the keyboard buffer in the BIOS Data area for the next character. When not chaining you'll have to have something that stores the last character read in the interrupt handler. – Michael Petch Mar 28 '18 at 03:24
  • Are you saying that the act of reading port 60h can alter it's value? Otherwise, the point is that when Esc key is pressed, the value 1 will temporarily appear at port 60h. When the value 1 appears, the program will terminate. Yes it may be replaced with some other scancode at any random point in time, but worst case scenario is that you have to press Esc a few times to terminate, or hold it down for a short time. – bad Mar 28 '18 at 03:30
  • On real hardware especially (emulators are more lenient) if your interrupt handler reads a character from port 60h and then your main loop then does a read on port 60h keypresses can appear to go missing because it was already read once by the interrupt handler and reading it again could yield a different character. – Michael Petch Mar 28 '18 at 03:33
  • The basic rule of thumb is this. If you read characters from outside an interrupt handler you should not be using interrupts (the two collide and can cause issues). If you are polling (without keyboard interrupts) from the main loop you also need to wait until the keyboard controller is ready to have another keyboard press read. (You do that through the status register). – Michael Petch Mar 28 '18 at 03:35
  • That's definitely good to know, I should only need to read it once when I write my actual ISR. – bad Mar 28 '18 at 03:36
  • Yes read it once in your ISR if you are going to use keyboard interrupts (same goes for the mouse) – Michael Petch Mar 28 '18 at 03:36
  • This answer has (reported to be working) example of keyboard interrupt: https://stackoverflow.com/q/47115120/4271923 – Ped7g Mar 28 '18 at 09:52
  • Yes, but I wouldn't have been able to surmise the real reason that my ISR wasn't working just from reading someone else's code. Unless the code specifically mentions that port 60h must be read. That's something that most int 9 interrupt handlers do anyway, so it's kind of hard to determine that it's absolutely necessary to do so just from looking at an example. – bad Mar 28 '18 at 09:59
  • @Mylifeisabug. yeah, I agree. It's sort of "obvious", because the IRQ (Interrupt ReQuest) itself is *"there is key press data waiting on 60h port"*, so it's kind of weird to not read it in the ISR, but I can't recall this stated literally anywhere, so I can understand your struggle and the way you are asking. Still there's even more to the correct keyboard interrupt handler, I think I did put some comment into that code, which part is doing what, but certainly it's not as wholesome as studying actual documentation and all the details). – Ped7g Mar 28 '18 at 10:20

1 Answers1

2

As Michael Petch mentions in the above comment chain, port 60h must be read before ending the ISR, or else no further keyboard interrupts will occur.

Corrected INPUT_ISR procedure:

INPUT_ISR PROC

;PRINTS A PIXEL OF SOME COLOR INDEX TO SOME LOCATION IN THE PIXEL BUFFER
;WHEN A KEYSTROKE OCCURS. ONE KEYSTROKE GENERATES TWO INTERRUPTS, ONE FOR 
;KEY-DOWN, AND ONE FOR KEY-UP. 


    pushf                       
    cli                            ;disable hardware interrupts
                                   ;(disabled anyway upon entry)
    push ds
    push es
    push bx
    push ax



    mov bx, varData              
    mov ds, bx                     ;ds <- data segment
    mov bx, 0a000h
    mov es, bx                     ;es <- VGA pixel buffer
    mov bx, pixPos                 ;bx <- current pixel position
    mov ah, pixCol                 ;ah <- current pixel color 
    mov BYTE PTR es:[bx], ah       ;write the pixel to the buffer


    in al, 60h                     ;read port 60h, must always be done before
                                   ;terminating an int 9 ISR, or else no 
                                   ;keyboard interrupts will occur

    mov al, 20h
    out 20h, al                    ;EOI 

    pop ax
    pop bx
    pop es
    pop ds
    popf

    iret



INPUT_ISR ENDP      
bad
  • 939
  • 6
  • 18