Search code examples
nasmintelx86-16real-mode

Binding PIT, PIC and IVT together to get a sleep() function


I have decided to write a small program to check, if I am able to implement a sleep function in a real mode app. Thats, what I did:

  1. Added an IRQ_0 handler function to the address at 0x0000:0x80, corresponding to the 0x20 interrupt in the IVT
  2. Set up two PICs, telling the primary one in the ICW2 to bind its IRQ0 to the 0x20 interrupt in the IVT
  3. Set up PIT wit the divisor value 11931 in order to get approximately 100hz freq
  4. Set up the stack at 0xffff, since it is a small program, written just to test the sleep function, so I doubt it will need more than 1kib of memory.
  5. Enabled the VGA graphics mode with a 320x200 screen and 256 colors via the 0x10 BIOS interrupt
  6. Put a pink pixel somewhere in the middle of the screen
  7. sleep for some short amount of time
  8. Put a differently colored pixel right next to it.

And my program never gets to printing the second pixel. Though, I am sure that interrupt 0x20 is my IRQ0 handler, since i called it via int 0x20 for testing.

[BITS 16]
[ORG 0x7C00]

%macro sleep 1              ; one argument, word sized, time to sleep
    mov word [countdown], %1; store the arg in the countdown
    push ax
    %%sleeploop:    
        cli                 ; disable interrupts
        mov ax, word [countdown]
        sti
        test ax, ax
        jz %%endsleep
        nop
        nop
        nop
        nop
        nop
        nop
        jmp %%sleeploop
    %%endsleep:
    sti
    pop ax
%endmacro



;add an IRQ_0 handler at IVT 0x20 entry:
xor ax, ax
mov ds, ax
cli                         ; disable interrupts while modifying
mov word [ds:0x80], IRQ_0   ; IVT entry at 0000:0x80 = 128 / 4 = interrupt number 32, left open fo user to initialize
mov word [ds:0x82], ax      ; our interrupts segment is also 0000, since were touching just the first kib of RAM
sti                         ; enable interrupts, since were finished with modifying IVT

; set up the PIC:
;ICW1:
mov al, 0x11                ; load the ICW1 into al
out 0x20, al                ; send it to the port 0x20(master PIC)
out 0xa0, al                ; send it to the port 0xa0(slave PIC)
;ICW2:
mov al, 0x20                ; the number of the 0 interrupt of the master PIC
out 0x20, al                ; send it to the master PIC. Now IRQ0 is interrupt 0x20 in the IVT table
mov al, 0x28                ; the number of the first interrupt of the slave PIC
out 0xa0, al                ; send it to the slave PIC. Now IRQ9 is interrupt 0x28 in the IVT table
;ICW3:
mov al, 0x4                 ; 0x4 - IRQ2, used to call the slave PIC
out 0x21, al                ; send it to the master PIC
mov al, 0x2                 ; IRQ2 for the slacve PIC
out 0xa1, al                ; send it to the slave PIC
;ICW4:
mov al, 1                   ; only the bit 0 is set. 
out 0x21, al                ; by sending it, tell the master PIC, that we are in a 8086 PC
out 0xa1, al                ; same, for the slave PIC
;Null out the PIC data registers
xor al, al
out 0x21, al
out 0xa1, al


; set up the PIT:
mov al, 0x36                ; a control word fot PIT, tellig that next values will be in binary, not BCD
out 0x43, al                ; send it to PIT
mov ax, 11931               ; our divisor for frequency in order to get 100hz
out 0x40, al                ;}
mov al, ah                  ;}=> send the divisor to PIT
out 0x40, al                ;}

; set up the stack:
mov sp, 0xffff 

cld 
xor ah, ah
mov al, 0x13
int 0x10
mov ax, 0xa000
mov es, ax

mov al, 60
mov byte [es:0x7d96], al
sleep 1
mov al, 50
mov byte [es:0x7d97], al


hlt

IRQ_0:                      ; a function, bound to the IRQ_0 interrupt
    push ax
    mov ax, word [countdown]
    test ax, ax
    jz .IRQ0_end
    dec ax
    mov word [countdown], ax
    .IRQ0_end:
    pop ax
iret

countdown dw 0

times 510 - ($-$$) db 0
dw 0xAA55

I guess, I do something wrong with the PIC or PIT initialization.


Solution

  • I guess, I figured out, what i was doing wrong. First of all, PIC is already initialized by BIOS, defaulting IRQ0 to interrupt 0x08. Also we need to send the EOI to the PIC in the end of out custom interrupt. Now it works! Comments in the code below are probably irrelevant and make no sense anymore.

    [BITS 16]
    [ORG 0x7C00]
    
    %macro sleep 1              ; one argument, word sized, time to sleep
        mov word [countdown], %1; store the arg in the countdown
        push ax
        %%sleeploop:    
            cli                 ; disable interrupts
            mov ax, word [countdown]
            sti
            test ax, ax
            jz %%endsleep
            nop
            nop
            nop
            nop
            nop
            nop
            jmp %%sleeploop
        %%endsleep:
        sti
        pop ax
    %endmacro
    
    xor bp, bp
    
    ;add an IRQ_0 handler at IVT 0x08 entry:
    xor ax, ax
    mov ds, ax
    cli                         ; disable interrupts while modifying
    mov word [ds:0x20], IRQ_0   ; IVT entry at 0000:0x08 
    mov word [ds:0x22], ax      ; our interrupts segment is also 0000, since were touching just the first kib of RAM
    sti                         ; enable interrupts, since were finished with modifying IVT
    
    
    ; set up the PIT:
    mov al, 0x34                ; a control word fot PIT, tellig that next values will be in binary, not BCD
    out 0x43, al                ; send it to PIT
    mov ax, 11931               ; our divisor for frequency in order to get 100hz
    out 0x40, al                ;}
    mov al, ah                  ;}=> send the divisor to PIT
    out 0x40, al                ;}
    
    ; set up the stack:
    mov sp, 0xffff 
    
    cld 
    xor ah, ah
    mov al, 0x13
    int 0x10
    mov ax, 0xa000
    mov es, ax
    
    mov al, 60
    mov byte [es:0x7d96], al
    sleep 100
    mov al, 50
    mov byte [es:0x7d97], al
    
    
    hlt
    
    IRQ_0:                      ; a function, bound to the IRQ_0 interrupt
        push ax
        mov ax, word [countdown]
        test ax, ax
        jz .IRQ0_end
        dec ax
        mov word [countdown], ax
        .IRQ0_end:
        mov al, 0x20
        out 0x20, al
        pop ax
    iret
    
    countdown dw 0
    
    times 510 - ($-$$) db 0
    dw 0xAA55