Page 1 of 1

x86 QEMU BIOS COM Interrupt supported?

Posted: Tue Apr 02, 2024 5:28 pm
by jemo07
Hi all, I hopping someone can help me out. I have created what I thought would be a trivial test for a fun bare metal implementation, but I seem to have hit a limit wall, which I can't seem to figure out if it's a qemu limitation or my own :D :D :D

Inspiration came from https://pages.cs.wisc.edu/~bolo/shipyard/3ins4th.html and the goal was to use this to created some hand crafted machine code and execute in bare metal for low level benchmarking. Things like latency for different calls, register access times, memory pipeline performance ect...

Ok, I have this simple code, it's just a bios that does the following, initiates UART on com1, it listens on com1, and do some storing and fetching of bytes. I have been looking at the code for some time now, and the send of the data through qemu seems to fail. I don't have HW to test this on to connect a real serial on it, here is my original code in FASAM. The other two files, are iteration of me testing different areas of the code to see if this was an issue with my com1 setup.

when I was trying to setup the code to simply revert back what I was sending, I could not get it to properly print on the VGA screen what was been sent via the serial console, I have run the code with QEMU -serial stdio and without, but it seems that either way, I can type a character, hit return, CR will trigger, but the value is not correctly printed or submitted.

Original_3word.asm
FASM code
(2.8 KiB) Downloaded 30 times

Code: Select all

org 0x7c00                  ; BIOS load address

start:
    ; Initialize the processor (not explicitly done in the code, usually BIOS job)
    ; Initialize the serial port
    mov ah, 0x00            ; Set COM port initialization function
    mov al, 0x03            ; Baud rate divisor for 9600 baud
    mov dx, 0               ; COM1 port number (0 for COM1)
    int 0x14                ; Call BIOS COM interrupt

    ; Clear the screen
    mov ax, 0x0003          ; Set video mode to 80x25 text mode
    int 0x10

main_loop:
    call read_byte          ; Read a byte from the serial port
    cmp al, 0x01            ; Check if byte = 01 [fetch]
    je fetch_byte
    cmp al, 0x02            ; Check if byte = 02 [store]
    je store_byte
    cmp al, 0x03            ; Check if byte = 03 [call]
    je call_subroutine
    jmp main_loop           ; Loop back if none of the above

fetch_byte:
    call read_address       ; Get address from the serial port
    mov al, [bx]            ; Fetch the byte from that address
    call send_byte          ; Send the byte to the serial port
    jmp main_loop

store_byte:
    call read_address       ; Get address from the serial port
    call read_byte          ; Get a byte from the serial port
    mov [bx], al            ; Store the byte at that address
    jmp main_loop

call_subroutine:
    call read_address       ; Get address from the serial port
    call far [bx]           ; Call the subroutine at that address
    jmp main_loop           ; Return to the main loop after subroutine

read_address:               ; Function to read an address from the serial port
    call read_byte          ; Read low byte
    mov bl, al
    call read_byte          ; Read high byte
    mov bh, al
    ret

read_byte:                  ; Function to read a byte from the serial port
    mov dx, 0x3f8           ; COM1 base address
wait_for_data:
    in al, dx               ; Read line status register
    test al, 1              ; Test if data available (bit 0)
    jz wait_for_data        ; If not, loop until data is available
    mov dx, 0x3f8           ; Reset dx to point to the data register
    in al, dx               ; Read received byte from data register
    ret

send_byte:                  ; Function to send a byte to the serial port
    push ax                 ; Save AX register
    mov ah, al              ; Move byte to AH for sending
    mov dx, 0               ; COM1 port number (0 for COM1)
    int 0x14                ; Call BIOS COM interrupt
    pop ax                  ; Restore AX register
    ret

; The print_byte and nibble_to_ascii functions are not necessary for the goals
; and have been removed to simplify the code.

times 510-($-$$) db 0       ; Pad remaining bytes with zeros
dw 0xaa55                   ; Boot signature 
Start here, this was the original code I started with, the two others it's just me testing things see if I would find something broken.

Thank you all,

Re: x86 QEMU BIOS COM Interrupt supported?

Posted: Tue Apr 02, 2024 6:41 pm
by Octocontrabass
jemo07 wrote:

Code: Select all

    mov al, 0x03            ; Baud rate divisor for 9600 baud
No, that's 110 baud. If you want 9600 baud, set AL to 0xE3.
jemo07 wrote:

Code: Select all

    mov al, [bx]            ; Fetch the byte from that address
    mov [bx], al            ; Store the byte at that address
    call far [bx]           ; Call the subroutine at that address
You never set DS, so you have no idea what addresses these instructions are actually using.
jemo07 wrote:

Code: Select all

    mov dx, 0x3f8           ; COM1 base address
    in al, dx               ; Read line status register
That's not the line status register, that's the data register.
jemo07 wrote:

Code: Select all

    mov ah, al              ; Move byte to AH for sending
That's not what you need to put in AH before calling INT 0x14. What are you using as your reference for BIOS calls?

Re: x86 QEMU BIOS COM Interrupt supported?

Posted: Wed Apr 03, 2024 6:04 am
by jemo07
Thank you that was most helpful, I had missed some of the suggestions you made... however, I'm thinking that before I get to the fetch/store/jump functions, I need to ensure that my serial communications is correct. For this, I have overly simplified this to see if I can catch some of my errors.

Currently, when I assemble the code, I get a "00" for every entry when running qemu with -serial: stdio or with -serial tcp::9600,server,nowait connected through tera term, telnet. Any key sent now produces a "30" hex or "0" in ascii. #-o #-o #-o #-o

I have created two files:

boot.fasm:

Code: Select all

; boot.fasm

use16
org 0x7C00

start:
    cli             ; Disable interrupts
    mov ax, 0x07C0  ; Set up the stack
    mov ss, ax
    mov sp, 0x1000
    sti             ; Enable interrupts

    call init_uart  ; Initialize UART for serial communication

    ; Set video mode to 80x25 text mode
    mov ah, 0x00
    mov al, 0x03
    int 0x10

    ; Main loop to echo characters
main_loop:
    call read_byte_from_com1  ; Read a byte from COM1
    call print_char           ; Print the received character
    jmp main_loop             ; Repeat indefinitely

; Include the serial communication routines
include 'com.fasm'

; Print a character to the screen
print_char:
    mov ah, 0x0E   ; BIOS teletype output
    mov bh, 0x00   ; Page number
    mov bl, 0x07   ; Attribute (light grey on black)
    int 0x10       ; Call BIOS
    ret

times 510-($-$$) db 0  ; Pad the bootloader to 510 bytes
dw 0xAA55              ; Boot signature
And com.fasm:

Code: Select all

; com.fasm 
use16

PORT equ 0x3F8  ; COM1 Base Port

; Initialize UART for serial communication
init_uart:
    pusha                      ; Save all general-purpose registers

    ; Disable all UART interrupts
    mov dx, PORT + 1           ; Interrupt Enable Register
    mov al, 0x00               ; Disable all interrupts
    out dx, al

    ; Set baud rate to 9600
    mov dx, PORT + 3           ; Line Control Register
    mov al, 0x80               ; Enable DLAB (Divisor Latch Access Bit)
    out dx, al
    mov ax, 0x000C             ; Divisor for 9600 baud rate (12)
    mov dx, PORT               ; Divisor latch low byte
    out dx, al
    mov dx, PORT + 1           ; Divisor latch high byte
    mov al, ah
    out dx, al

    ; Set line control register: 8 bits, no parity, 1 stop bit
    mov dx, PORT + 3
    mov al, 0x03               ; 8 bits, no parity, one stop bit
    out dx, al

    ; Enable FIFO, clear them, with 1-byte threshold (optional, for simplicity)
    ;mov dx, PORT + 2
    ;mov al, 0x01               ; Enable FIFO & set 1-byte threshold
    ;out dx, al

    popa                       ; Restore all general-purpose registers
    ret

; Send a byte to COM1
send_byte_to_com1:
    push ax
    push dx

    mov dx, PORT + 5           ; Line Status Register
wait_for_transmit_empty:
    in al, dx
    test al, 0x20              ; Wait for the transmitter to be empty
    jz wait_for_transmit_empty

    pop dx
    mov dx, PORT
    pop ax
    out dx, al
    ret

; Read a byte from COM1
read_byte_from_com1:
    push ax
    push dx

    mov dx, PORT + 5           ; Line Status Register
wait_for_data_ready:
    in al, dx
    test al, 0x01              ; Check if data is available
    jz wait_for_data_ready

    mov dx, PORT
    in al, dx
    pop dx
    pop ax
    ret
 

Re: x86 QEMU BIOS COM Interrupt supported?

Posted: Wed Apr 03, 2024 11:31 am
by jemo07
here is the problem, kept saving AX on the read_byte_from_com1... that left AL untouched :oops: #-o

Was pointed out on the FASM forum... :D !
Here is the corrected code:

Code: Select all

; Read a byte from COM1
read_byte_from_com1:
    ; push ax
    push dx

    mov dx, PORT + 5           ; Line Status Register
wait_for_data_ready:
    in al, dx
    test al, 0x01              ; Check if data is available
    jz wait_for_data_ready

    mov dx, PORT
    in al, dx                  ; Read the byte
    ; pop ax
    pop dx
    ret