INT 13, 2 hanging on x86 real mode when trying to read sectors from floppy drive
Asked Answered
D

1

4

I'm writing a DOS clone for a school project and I'm trying to read some sectors from a floppy drive (mainly the root directory of a FAT12 file system, sector 19) using BIOS INT 13, 2. I set the parameters right - or at least I think I do so - and then call INT 0x13 with AH = 2. Then, though, the system hangs. I can't figure why.

I'm using the following code to call the interrupt:

mov ah, 0x2   ;Read sectors function
mov al, 1     ;I want to read one sector
mov ch, 0     ;From track 0
mov cl, 2     ;Sector 2
mov dh, 1     ;Head 1
mov dl, [device_number] ;Obtained from BIOS during boot
mov bx, 0x7C0 ;Buffer located at 0x7C00:0x0000*
mov es, bx
mov bx, 0
stc           ;Set carry for older BIOSes that just unset it.
int 0x13      ;Call BIOS INT 0x13
jc .error     ;If there's an error, jump to the .error subroutine.

The disk read code above is run inside a keyboard interrupt handler that processes keystrokes. When the interrupt handler finds a command it recognizes (ie DIR) it runs the routine that calls the disk reading code. My keyboard handler looks like:

; Keyboard ISR
; Installed to Real mode IVT @ 0x0000:0x0024 (IRQ1) replacing
;     original BIOS interrupt vector

isr_teclado:
    pusha
    in al, 0x60                             ; Get keystroke
    call check_pressed_key                  ; Process Character
    call fin_int_pic1                       ; Send EOI to Master PIC
    popa
    iret

; Routine to send EOI to master PIC.
fin_int_pic1:
    push ax
    mov al, 0x20                        ;PIC EOI signal
    out 0x20, al                        ;Send signal to PIC 1
    pop ax
    ret

I've been testing with QEMU and with Virtual Box. They don't jump to the .error subroutine nor continue execution after the interrupt has been called. I've also tested with Bochs, but Bochs DOES lift the carry flag and jump to .error. Don't really know why.

It's important to note that I'm not writing a bootloader. My system has already been loaded into memory using a similar procedure that actually works (not with hardcoded values, these are just for testing, but using other values obtained from other BIOS functions doesn't seem to work either). That same procedure doesn't work here neither. Also, my system is loaded at 0x0500:0x0000 and the stack segment is set to 0x0780:0x0000, with the stack base and the stack pointer both starting at 0x0780:0x0400 (1KiB).

Am I doing anything wrong? Are my parameters incorrect, am I missing something? Any information that would be useful that I've not posted here?

Dunghill answered 27/7, 2018 at 11:56 Comment(10)
One more thing: if I call INT 0x13 AH=0 (Reset Disk System) with DL = [device_number], that seems to work perfectly. INT 0x13 AH=2, on the other hand, though, does not.Dunghill
My bootloader works fine, it's the kernel that's malfunctioning. You are right about the buffer location. Also, I seem to be trying to read sector 2, when I actually was trying to read sector 19. I'll fix that. Nevertheless, it still hangs after calling INT 13, 2.Dunghill
I'm trying to load sector 19. I may have gotten all my numbers wrong and maybe that's why it isn't working?Dunghill
On bochs it just throws an error (sets the carry flag), so I can't really use bochs to debug what's happening here.Dunghill
Logical Block Address (LBA) 19 is the 20th logical sector on the disk (LBA is 0 based) and yes that would be CHS=(0,1,2). If Bochs is giving an error that means one of the parameters is wrong. Since you don't show all your code I don't even know if this line is correct mov dl, [device_number] . I'd verify in BOCHs just prior to int 0x13 that DL has the correct value (should be zero if it is floppy disk A). If you saved [device_number] in memory with a differnt DS segment than the DS segment you ar eusing ith this code then the read will be from the wrong memory address.Extremely
My disk is a 1.44 floppy disk image, and I'm just trying to read sector 19 so it doesn't seem to be falling off the edge. I've double checked the numbers I've used and they seem to be right (sector / head / track). I also would love to share my code, but it's a scruffy awful lot of files not commented in English, so that may be a problem.Dunghill
Well then, my bootloader is here and the code mentioned in the question above is here on line 472. I find it very odd that the bootloader works and loads the kernel fine, though.Dunghill
Looking over the code it appears your keyboard interrupt in fact calls the routine that does the disk read. That means you are doing int 0x13 from within an interrupt handler. Since you haven't sent an EOI you will not receive any lower priority interrupts (or future keyboard interrupts) until that EOI is sent. That means disk interrupts won't arrive which probably causes the problem.Probably a very bad idea for your keyboard interrupt to being disk reads.Extremely
It's a very bad idea to do anything time consuming in the interrupt handler itself... Interrupt should do just the minimal possible amount of code to handle the signal and set up probably some buffer/flags for the remaining part of processing to happen in the main kernel thread, not in handler itself.Kaolinite
"Looking over the code it appears your keyboard interrupt in fact calls the routine that does the disk read." you are so right!! This fixed everything, thank you!! If you post that comment as an answer I will accept it. Thank you very very much again!Dunghill
E
8

Your code isn't working because the int 0x13 BIOS call is returning a 0x80 status code (timeout). This is because you are doing the disk read BIOS call inside an interrupt handler (ISR).

When the CPU transfers control to your ISR in real mode it clears the IF flag. This causes the CPU to ignore maskable external interrupts. Even if you were to enable interrupts with STI you won't get any more interrupts sent from the PICs that are of lower or equal priority to the current interrupt. In essence IRQ0 (higher priority interrupt than IRQ1) is the only interrupt you could get until you send an EOI. You won't get the floppy disk controller interrupts that the BIOS call needs to properly complete a request. This is likely the cause of the timeout.

The best idea for doing ISRs is to limit them to doing the bare minimum and do it in the least amount of time possible. You should avoid making other BIOS calls from your ISR unless you know what you are doing.

In a keyboard ISR you can read keystrokes into a buffer and defer processing them until later. A ring buffer is often used by kernels for handling keyboard data. Once the character is read into the buffer you can send the EOI and exit your ISR. Replace the JMP $ that is your kernel's main loop with a loop that processes the keys stored by the keyboard ISR. You can then take whatever actions are appropriate. You could replace your JMP $ with something like:

main_loop:
    hlt                 ; Halt processor until next interrupt occurs

    [check for characters in the keyboard buffer and process them as needed]
    ...
    jmp main_loop

Since this is done outside an ISR you are not constrained by the issues you had running inside an ISR.


An example implementation of an interrupt safe lockless ring buffer that can work with one consumer and producer is shown below. The example has a keyboard ISR that takes each scancode and places it into a buffer if the buffer isn't full. The main loop checks each iteration if there is a scancode available (buffer isn't empty). If one is available it is translated to ASCII and printed to the console.

KBD_BUFSIZE equ 32                 ; Keyboard Buffer length. **Must** be a power of 2
                                   ;     Maximum buffer size is 2^15 (32768)
KBD_IVT_OFFSET equ 0x0024          ; Base address of keyboard interrupt (IRQ) in IVT

bits 16
org 0x7c00

start:
    xor ax, ax
    mov ds, ax                     ; DS=0 since we use an ORG of 0x7c00.
                                   ;     0x0000<<4+0x7C00=0x07C00
    mov ss, ax
    mov sp, 0x7c00                 ; SS:SP stack pointer set below bootloader

    cli                            ; Don't want to be interrupted when updating IVT
    mov word [KBD_IVT_OFFSET], kbd_isr
                                   ; 0x0000:0x0024 = IRQ1 offset in IVT
    mov [KBD_IVT_OFFSET+2], ax     ; 0x0000:0x0026 = IRQ1 segment in IVT
    sti                            ; Enable interrupts

    mov ax, 0xb800
    mov es, ax                     ; Set ES to text mode segment (page 0)
    xor di, di                     ; DI screen offset = 0 (upper left)
    mov ah, 0x1F                   ; AH = White on Blue screen attribute
    mov bx, keyboard_map           ; BX = address of translate table used by XLAT
    cld                            ; String instructions set to forward direction

.main_loop:
    hlt                            ; Halt processor until next interrupt
    mov si, [kbd_read_pos]
    cmp si, [kbd_write_pos]
    je .main_loop                  ; If (read_pos == write_pos) then buffer empty and
                                   ;     we're finished

    lea cx, [si+1]                 ; Index of next read (tmp = read_pos + 1)
    and si, KBD_BUFSIZE-1          ; Normalize read_pos to be within 0 to KBD_BUFSIZE
    mov al, [kbd_buffer+si]        ; Get next scancode
    mov [kbd_read_pos], cx         ; read_pos++ (read_pos = tmp)
    test al, 0x80                  ; Is scancode a key up event?
    jne .main_loop                 ;     If so we are finished

    xlat                           ; Translate scancode to ASCII character
    test al, al
    je .main_loop                  ; If character to print is NUL we are finished
    stosw                          ; Display character on console in white on blue

    jmp .main_loop

; Keyboard ISR (IRQ1)
kbd_isr:
    push ax                        ; Save all registers we modify
    push si
    push cx

    in al, 0x60                    ; Get keystroke

    mov cx, [cs:kbd_write_pos]
    mov si, cx
    sub cx, [cs:kbd_read_pos]
    cmp cx, KBD_BUFSIZE            ; If (write_pos-read_pos)==KBD_BUFSIZE then buffer full
    je .end                        ;    If buffer full throw char away, we're finished

    lea cx, [si+1]                 ; Index of next write (tmp = write_pos + 1)
    and si, KBD_BUFSIZE-1          ; Normalize write_pos to be within 0 to KBD_BUFSIZE
    mov [cs:kbd_buffer+si], al     ; Save character to buffer
    mov [cs:kbd_write_pos], cx     ; write_pos++ (write_pos = tmp)

.end:
    mov al, 0x20
    out 0x20, al                   ; Send EOI to Master PIC

    pop cx                         ; Restore all registers modified
    pop si
    pop ax
    iret

align 2
kbd_read_pos:  dw 0
kbd_write_pos: dw 0
kbd_buffer:    times KBD_BUFSIZE db 0

; Scancode to ASCII character translation table
keyboard_map:
    db  0,  27, '1', '2', '3', '4', '5', '6', '7', '8'    ; 9
    db '9', '0', '-', '=', 0x08                           ; Backspace
    db 0x09                                               ; Tab
    db 'q', 'w', 'e', 'r'                                 ; 19
    db 't', 'y', 'u', 'i', 'o', 'p', '[', ']', 0x0a       ; Enter key
    db 0                                                  ; 29   - Control
    db 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';'   ; 39
    db "'", '`', 0                                        ; Left shift
    db "\", 'z', 'x', 'c', 'v', 'b', 'n'                  ; 49
    db 'm', ',', '.', '/', 0                              ; Right shift
    db '*'
    db 0                                                  ; Alt
    db ' '                                                ; Space bar
    db 0                                                  ; Caps lock
    db 0                                                  ; 59 - F1 key ... >
    db 0,   0,   0,   0,   0,   0,   0,   0
    db 0                                                  ; < ... F10
    db 0                                                  ; 69 - Num lock
    db 0                                                  ; Scroll Lock
    db 0                                                  ; Home key
    db 0                                                  ; Up Arrow
    db 0                                                  ; Page Up
    db '-'
    db 0                                                  ; Left Arrow
    db 0
    db 0                                                  ; Right Arrow
    db '+'
    db 0                                                  ; 79 - End key
    db 0                                                  ; Down Arrow
    db 0                                                  ; Page Down
    db 0                                                  ; Insert Key
    db 0                                                  ; Delete Key
    db 0,   0,   0
    db 0                                                  ; F11 Key
    db 0                                                  ; F12 Key
    times 128 - ($-keyboard_map) db 0                     ; All other keys are undefined

times 510 - ($-$$) db 0                                   ; Boot signature
dw 0xAA55

Note: This implementation is a demonstration. A real OS would likely have a queue of events that the main loop would check for. The ISRs would push an event into a queue, and the main loop would pop each off and process them. The demonstration is inefficient since it is always checking for scancodes in the buffer whether a keyboard event occurred or not.

enter image description here

The code is based on a ring buffer implementation that would look like this in pseudo-code:

buffer[BUFSIZE]; /* BUFSIZE has to be power of 2 and be <= 32768 */
uint16_t read_pos = 0;
uint16_t write_pos = 0;

normalize(val)   { return val & (BUFSIZE - 1); }
saveelement(val) { buffer[normalize(write_pos++)] = val; }
getelement()     { return buffer[normalize(read_pos++)]; }
numelements()    { return write_pos - read_pos; }
isfull()         { return numelements() == BUFSIZE; }
isempty()        { return write_pos == read_pos; }

Before using saveelement you must call isfullto make sure the buffer isn't full. Before using getelement you must call isempty to make sure there is a value to read.

Extremely answered 27/7, 2018 at 21:21 Comment(2)
Slightly confusing to read this when you use the word "interrupt" for int 0x13 BIOS calls, and for async external interrupts. IMO it's not necessary to call them interrupts just because we invoke them with the int instruction, especially not when talking about hardware interrupts.Lilialiliaceous
@PeterCordes they’re called interrupts in just about every x86 assembly documentation, and “soft interrupts” (as opposed to “hard interrupts” (and probably NMI, but academics don’t care about those)) in CS theory. So, while not technically necessary, this is common jargon.Burson

© 2022 - 2024 — McMap. All rights reserved.