; 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 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 WHITE_ON_BLACK: equ 0x0f vga_print: ; Prints a null-terminated string located ; on the esi register pusha mov edx, VMEM_START vga_print_loop: mov al, [esi] mov ah, WHITE_ON_BLACK 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 add ebx, 2 ; Go to the next character in video memory 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 mov esi, NEWLINE call vga_print 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