2022-11-16 11:56:03 +01:00
|
|
|
; Some utilities to deal with the TTY using the BIOS
|
|
|
|
; during real mode and writing directly to VGA memory
|
|
|
|
; once we switch to protected mode
|
|
|
|
[bits 16]
|
|
|
|
|
|
|
|
; I/O routines based on BIOS interrupt 13
|
|
|
|
|
|
|
|
bios_newline:
|
|
|
|
; Points the TTY cursor to the next
|
|
|
|
; line
|
|
|
|
pusha
|
|
|
|
; Source: http://www.techhelpmanual.com/118-int_10h_03h__query_cursor_position_and_size.html
|
|
|
|
mov ah, 0x3 ; Get cursor position
|
|
|
|
mov bh, 0x0 ; Page 0
|
|
|
|
int 0x10
|
|
|
|
; Go to the next row
|
|
|
|
; Source: http://www.techhelpmanual.com/117-int_10h_02h__set_cursor_position.html
|
|
|
|
mov ah, 0x2 ; Set cursor position
|
|
|
|
mov bh, 0x0 ; Page 0
|
|
|
|
xor dl, dl ; Goes to column 0 (i.e. start of the line)
|
|
|
|
inc dh ; Goes to the next row
|
|
|
|
int 0x10
|
|
|
|
popa
|
|
|
|
ret
|
|
|
|
|
|
|
|
|
|
|
|
bios_print:
|
|
|
|
; Prints a null-terminated string whose address
|
|
|
|
; is located in the si register
|
|
|
|
pusha
|
|
|
|
mov ah, 0xe ; Set the screen in TTY mode
|
|
|
|
bios_print_loop:
|
|
|
|
mov al, [si]
|
|
|
|
int 0x10 ; Writes the content of al to the screen
|
|
|
|
inc si
|
|
|
|
cmp byte [si], 0x0 ; If we got to the null byte, we're done
|
|
|
|
jne bios_print_loop ; Otherwise, we run this again
|
|
|
|
popa
|
|
|
|
ret
|
|
|
|
|
|
|
|
|
|
|
|
bios_println:
|
|
|
|
; Prints a null-terminated string whose address
|
|
|
|
; is located in the si register and sets the TTY
|
|
|
|
; cursor to the next line
|
|
|
|
pusha
|
|
|
|
call bios_print
|
|
|
|
call bios_newline
|
|
|
|
popa
|
|
|
|
ret
|
|
|
|
|
|
|
|
|
|
|
|
bios_printh:
|
|
|
|
; Prints the value of dx in hexadecimal format
|
|
|
|
pusha
|
|
|
|
xor cx, cx ; This serves as our index and loop variable
|
|
|
|
bios_printh_loop:
|
|
|
|
cmp cx, 4 ; loop 4 times
|
|
|
|
je bios_printh_end
|
|
|
|
|
|
|
|
; Here we extract the last digit from dx using
|
|
|
|
; a bitmask, with ax as our working register,
|
|
|
|
; and convert it to ASCII by adding 30 to it if
|
|
|
|
; it's less than 9 (meaning it's a digit) or 37
|
|
|
|
; if if it's a letter (that's because letters and
|
|
|
|
; numbers are 7 digits apart in the ASCII table)
|
|
|
|
mov ax, dx
|
|
|
|
and ax, 0xf
|
|
|
|
add al, 0x30
|
|
|
|
cmp al, 0x39
|
|
|
|
jle bios_printh_step2
|
|
|
|
add al, 7
|
|
|
|
|
|
|
|
bios_printh_step2:
|
|
|
|
; Now we start filling our string variable (starting from the
|
|
|
|
; back, since we are extracting digits from the end of the
|
|
|
|
; number) and then rotate the number by 4 bits to access the
|
|
|
|
; next digit. This works and is the same as the more common
|
|
|
|
; modulo division because each hexadecimal digits represents
|
|
|
|
; exactly 4 bits and we can take advantage of the CPU's much
|
|
|
|
; faster bitwise operations rather than burden ourselves with
|
|
|
|
; a costly modulo 10 division (which would take hundreds of
|
|
|
|
; clock cycles, as opposed to it only taking one for a rotate
|
|
|
|
; operation)
|
|
|
|
mov si, HEX_OUT + 5 ; We skip the '0x' part and jump to the last digit
|
|
|
|
sub si, cx ; We subtract si by cx so we land on the right digit
|
|
|
|
mov [si], al ; We copy the character in al to the character pointed to by si
|
|
|
|
ror dx, 4 ; With an example input: 0x1234 -> 0x4123 -> 0x3412 -> 0x2341 -> 0x1234
|
|
|
|
inc cx
|
|
|
|
jmp bios_printh_loop
|
|
|
|
|
|
|
|
bios_printh_end:
|
|
|
|
; Since we have a string now, we just call our
|
|
|
|
; handy routine to print it out. We're done!
|
|
|
|
mov si, HEX_OUT
|
|
|
|
call bios_print
|
|
|
|
popa
|
|
|
|
ret
|
|
|
|
|
2022-11-16 15:25:53 +01:00
|
|
|
bios_cls:
|
|
|
|
pusha
|
|
|
|
mov ah, 0x00
|
|
|
|
mov al, 0x03 ; text mode 80x25 16 colours
|
|
|
|
int 0x10
|
|
|
|
popa
|
|
|
|
ret
|
|
|
|
|
2022-11-16 11:56:03 +01:00
|
|
|
|
|
|
|
HEX_OUT: db '0x0000', 0 ; reserve memory for our new string
|
|
|
|
|
|
|
|
[bits 32]
|
|
|
|
|
|
|
|
; I/O routines that directly manipulate video memory
|
|
|
|
VMEM_START: equ 0xb8000 ; Video memory always starts at this address
|
|
|
|
; A character on the screen in VGA text mode is composed of 2 bytes:
|
|
|
|
; the first byte is the ASCII codepoint to be printed, while the next
|
|
|
|
; octet represents additional formatting information (color, blink,
|
|
|
|
; underline, etc.). More info: https://en.wikipedia.org/wiki/VGA_text_mode
|
2022-11-16 15:25:53 +01:00
|
|
|
TEXT_COLOR: equ 0x07
|
2022-11-16 11:56:03 +01:00
|
|
|
|
|
|
|
|
|
|
|
vga_print:
|
|
|
|
; Prints a null-terminated string located
|
2022-11-16 15:25:53 +01:00
|
|
|
; on the esi register. Offsets the write
|
|
|
|
; by ecx bytes
|
2022-11-16 11:56:03 +01:00
|
|
|
pusha
|
|
|
|
mov edx, VMEM_START
|
2022-11-16 15:25:53 +01:00
|
|
|
add edx, ecx
|
2022-11-16 11:56:03 +01:00
|
|
|
vga_print_loop:
|
|
|
|
mov al, [esi]
|
2022-11-16 15:25:53 +01:00
|
|
|
mov ah, TEXT_COLOR
|
2022-11-16 11:56:03 +01:00
|
|
|
cmp al, 0
|
|
|
|
je vga_print_done ; If we're at the null byte, we exit
|
|
|
|
mov [edx], ax ; Write the 2-byte character to video memory
|
|
|
|
inc esi ; Go to the next character in the string
|
2022-11-16 15:25:53 +01:00
|
|
|
add edx, 2 ; Go to the next character in video memory
|
2022-11-16 11:56:03 +01:00
|
|
|
jmp vga_print_loop
|
|
|
|
|
|
|
|
vga_print_done:
|
|
|
|
popa
|
|
|
|
ret
|
|
|
|
|
|
|
|
|
|
|
|
vga_println:
|
|
|
|
; Prints a null-terminated string located on the
|
|
|
|
; esi register and terminates it with a newline
|
|
|
|
pusha
|
|
|
|
call vga_print
|
2022-11-16 15:25:53 +01:00
|
|
|
;mov esi, NEWLINE
|
|
|
|
;call vga_print
|
2022-11-16 11:56:03 +01:00
|
|
|
popa
|
|
|
|
ret
|
|
|
|
|
|
|
|
|
|
|
|
vga_printh:
|
|
|
|
; Prints the value of edx in hexadecimal format
|
|
|
|
pusha
|
|
|
|
xor ecx, ecx ; This serves as our index and loop counter
|
|
|
|
vga_printh_loop:
|
|
|
|
cmp ecx, 8 ; loop 8 times
|
|
|
|
je vga_printh_end
|
|
|
|
; Here we extract the last digit from edx using
|
|
|
|
; a bitmask, with eax as our working register,
|
|
|
|
; and convert it to ASCII by adding 30 to it if
|
|
|
|
; it's less than 9 (meaning it's a digit) or 37
|
|
|
|
; if if it's a letter (that's because letters and
|
|
|
|
; numbers are 7 digits apart in the ASCII table)
|
|
|
|
mov eax, edx
|
|
|
|
and eax, 0xf
|
|
|
|
add ax, 0x30
|
|
|
|
cmp ax, 0x39
|
|
|
|
jle vga_printh_step2
|
|
|
|
add ax, 7
|
|
|
|
|
|
|
|
vga_printh_step2:
|
|
|
|
; Now we start filling our string variable (starting from the
|
|
|
|
; back, since we are extracting digits from the end of the
|
|
|
|
; number) and then rotate the number by 4 bits to access the
|
|
|
|
; next digit. This works and is the same as the more common
|
|
|
|
; modulo division because each hexadecimal digits represents
|
|
|
|
; exactly 4 bits and we can take advantage of the CPU's much
|
|
|
|
; faster bitwise operations rather than burden ourselves with
|
|
|
|
; a costly modulo 10 division (which would take hundreds of
|
|
|
|
; clock cycles, as opposed to it only taking one for a rotate
|
|
|
|
; operation)
|
|
|
|
mov esi, HEX_OUT_LONG + 9 ; We skip the '0x' part and jump to the last digit
|
|
|
|
sub esi, ecx ; We subtract esi by ecx so we land on the right digit
|
|
|
|
mov [esi], ax ; We copy the character in ax to the character pointed to by esi
|
|
|
|
ror edx, 4 ; With an example input: 0x1234 -> 0x4123 -> 0x3412 -> 0x2341 -> 0x1234
|
|
|
|
inc ecx
|
|
|
|
jmp vga_printh_loop
|
|
|
|
|
|
|
|
vga_printh_end:
|
|
|
|
mov esi, HEX_OUT_LONG
|
|
|
|
call vga_print
|
|
|
|
popa
|
|
|
|
ret
|
|
|
|
|
|
|
|
|
|
|
|
NEWLINE: db 0xA, 0xD
|
|
|
|
HEX_OUT_LONG: db '0x00000000', 0
|