When you write directly to the screen you bypass the BIOS TTY print routines. By writing starting at 0xb8000 you are always writing to the upper left hand corner of the screen. The BIOS does maintain current row and column state in the BIOS Data Area (BDA) after each update. If you want to continue where the BIOS left off you'd have to load the current row and colum stored in the BDA. You can find the BDA described here:
http://stanislavs.org/helppc/bios_data_area.htmlIn particular the current row and column of page 0 are stored in the 16-bit word at 0x0040:0x0050 (physical address 0x0450). The byte at 0x450 is the column, the byte at 0x451 is the current row. You can also retrieve the number of columns for the last(current) video mode the BIOS set from the word at 0x0040:0x004a (physical address 0x044a).
Using this data you can compute the offset of the BIOS cursor in video memory by ((cur_row * num_columns) + cur_column)*2. The multiply by two is because each cell in text video memory is 2 bytes (character and attribute). You can then add that value to 0xb8000 to get the actual video memory address to write to.
Some basic code that does rudimentary TTY output to page 0 that supports down scrolling, backspace, Carriage return. line feed is below. It ignore TAB characters:The code was the second stage of a bootloader I wrote to help someone else.You can ignore the code related to the GDT/A20 line etc
Code:
org 0x7e00
VIDEO_TEXT_ADDR EQU 0xb8000 ; Hard code beginning of text video memory
ATTR_WHITE_ON_BLACK EQU 0x07 ; White on black attribute
CR EQU 0x0d ; Carriage return
LF EQU 0x0a ; Line feed
BS EQU 0x08 ; Back space
TAB EQU 0x09 ; Tab
PM_MODE_STACK EQU 0x9c000 ; Realmode stack below EBDA
BITS 16
start:
mov si, boot_init_msg ; Print boot initialization message
call print_string_rm
; Fast method of enabling A20 may not work on all x86 BIOSes
; It is good enough for emulators and most modern BIOSes
; See: https://wiki.osdev.org/A20_Line
cli ; Disable interrupts
in al, 0x92
or al, 2
out 0x92, al ; Enable A20 using Fast Method
mov si, load_gdt_msg ; Print loading GDT message
call print_string_rm
lgdt [gdtr] ; Load our GDT
mov si, enter_pm_msg ; Print protected mode message
call print_string_rm
mov eax, cr0
or eax, 1
mov cr0, eax ; Set protected mode flag
jmp CODE32_SEL:start32 ; FAR JMP to set CS
bits 32
start32:
mov ax, DATA32_SEL ; Setup the segment registers with data selector
mov ds, ax
mov es, ax
mov ss, ax
mov esp, 0x9c000 ; Set the stack pointer
mov fs, ax ; Not currently using FS and GS
mov gs, ax
; Initialize our TTY functions with screen height and width, and
; current cursor location from the BIOS
call update_screen_state_from_bios
mov ah, ATTR_WHITE_ON_BLACK ; Attribute to print with
mov al, ah ; Attribute to clear last line when scrolling
mov esi, in_pm_msg ; Print message that we are in protected mode
call print_string_pm
end_loop:
hlt
jmp end_loop
; Function: update_screen_info_from_bios
; set the hardware cursor position based on the
; current column (cur_col) and current row (cur_row) coordinates
;
; Inputs: None
; Clobbers: EAX
; Returns: None
update_screen_state_from_bios:
xor eax, eax ; Clear EAX for the instructions below
mov al, [0x450] ; Byte at address 0x450 = last BIOS column position
mov [cur_col], eax ; Copy to current column
mov al, [0x451] ; Byte at address 0x451 = last BIOS row position
mov [cur_row], eax ; Copy to current row
mov al, [0x484] ; Word at address 0x484 = # of rows-1 (screen height)
mov [screen_height],eax ; Copy to screen height
mov ax, [0x44a] ; Word at address 0x44a = # of columns (screen width)
mov [screen_width], eax ; Copy to screen width
ret
; Function: set_cursor
; set the hardware cursor position based on the
; current column (cur_col) and current row (cur_row) coordinates
; See: https://wiki.osdev.org/Text_Mode_Cursor#Moving_the_Cursor_2
;
; Inputs: None
; Clobbers: EAX, ECX, EDX
; Returns: None
set_cursor:
mov ecx, [cur_row] ; EAX = cur_row
imul ecx, [screen_width] ; ECX = cur_row * screen_width
add ecx, [cur_col] ; ECX = cur_row * screen_width + cur_col
; Send low byte of cursor position to video card
mov edx, 0x3d4
mov al, 0x0f
out dx, al ; Output 0x0f to 0x3d4
inc edx
mov al, cl
out dx, al ; Output lower byte of cursor pos to 0x3d5
; Send high byte of cursor position to video card
dec edx
mov al, 0x0e
out dx, al ; Output 0x0e to 0x3d4
inc edx
mov al, ch
out dx, al ; Output higher byte of cursor pos to 0x3d5
ret
; Function: print_string_pm
; Display a string to the console on display page 0 in protected mode.
; Handles carriage return, line feed, and backspace. Tab characters
; are not processed. Scrolling and wrapping are supported.
; Backspacing beyond the first line does nothing.
;
; Inputs: ESI = Offset of address to print
; AH = Attribute of string to print
; AL = Attribute to use when filling bottom line during down scrolling
; Clobbers: ECX, EDX
; Returns: None
print_string_pm:
push edi
push esi
push eax
push ebx
push ebp
; Assume base of text video memory is ALWAYS 0xb8000
mov ebx, VIDEO_TEXT_ADDR ; EBX = beginning of video memory
mov cl, al ; CL = attribute to use for clearing while scrolling
call .init ; Initialize register state for use while printing
jmp .getch
.repeat:
cmp al, CR ; Is the character a carriage return?
jne .chk_lf ; If not skip and check for line feed
lea edi, [ebx + edx * 2] ; Set current video memory pointer to beginning of line
mov dword [cur_col], 0 ; Set current column to 0
xor al, al ; AL = 0 = Don't print character
jmp .chk_bounds ; Check screen bounds
.chk_lf:
; Process line feed
cmp al, LF ; Is the character a line feed?
jne .chk_bs ; If not check for backspace
mov ebp, [screen_width]
lea edi, [edi + ebp * 2] ; Set current video memory ptr to same pos on next line
inc dword [cur_row] ; Set current row to next line
xor al, al ; AL = 0 = Don't print character
jmp .chk_bounds ; Check screen bounds
.chk_bs:
; Process back space
cmp al, BS ; Is the character a Back space?
jne .chk_tab ; If not check for tab
cmp edi, ebx
je .getch ; If at beginning of display, ignore and get next char
dec dword [cur_col] ; Set current column to previous column
jmp .chk_bounds ; Check screen bounds
; Process tab - ignore character
.chk_tab:
cmp al, TAB ; Is the character a Tab?
je .getch ; If it is, skip and get next character
; Check row and column boundaries and clip them if necessary
; If we exceed the number of rows on display,scroll down by a line
.chk_bounds:
mov ebp, [screen_width] ; EAX=screen width
cmp [cur_col], ebp ; Have we reached edge of display?
jl .chk_col_start ; If not - continue by checking for beginning of line
mov dword [cur_col], 0 ; Reset current column to beginning of line
inc dword [cur_row] ; Advance to the next row
jmp .chk_rows ; Check number of rows in bounds
.chk_col_start:
cmp dword [cur_col], 0 ; Check if beginning of line
jge .chk_rows ; If not negative (beginning of line) check row bounds
mov dword [cur_col], 0 ; Set column to 0
.chk_rows:
mov ebp, [screen_height] ; EAX=screen width
cmp [cur_row], ebp ; Have we reached edge of display?
jle .test_char ; If not then continue by updating display
dec dword [cur_row] ; Back one row since we will be scrolling down a line
call .scroll_down_one_line ; Scroll display down by a line
call .init ; Reinitialize register state after scroll
; Display character to video memory at current location if not a NUL character
.test_char:
test al, al ; Is the character 0?
jz .getch ; If it is we are finished, get next character
cmp al, BS ; Is the character a Back space?
jne .not_bs ; If not back space print char and advance cursor
mov al, ' '
sub edi, 2 ; Go back one cell in video memory
mov [es:edi], al ; Print a space to clear previous character
jmp .getch ; Don't advance cursor and get next character
.not_bs:
stosw ; Update current character at current location
inc dword [cur_col] ; Advance the current column by 1 position
; Get next character from string parameter
.getch:
lodsb ; Get character from string
test al, al ; Have we reached end of string?
jnz .repeat ; if not process next character
.end:
call set_cursor ; Update hardware cursor position
pop ebp
pop ebx
pop eax
pop esi
pop edi
ret
; Function: print_string_pm.scroll_down_one_line
; Internal function of print_string_pm to scroll the display down
; by a single line. The top line is lost and the bottom line is
; filled with spaces.
;
; Inputs: EBX = Base address of video page
; AH = Attribute to use when clearing last line
; Clobbers: None
; Returns: None, display updated
.scroll_down_one_line:
pusha
mov ebp, [screen_height] ; EBP = (num_rows-1)
mov eax, [screen_width] ; EAX = screen_width
lea esi, [ebx + eax * 2] ; ESI = pointer to second line on screen
mov edi, ebx ; EDI = pointer to first line on screen
mul ebp ; EAX = screen_width * (num_rows-1)
mov ecx, eax ; ECX = number of screen cells to copy
rep movsw
lea edi, [ebx + eax * 2] ; Destination offset =
; last row = screen_width * (num_rows-1)
mov ecx, [screen_width] ; Update a rows worth of word cells
mov ah, cl
mov al, ' ' ; Use a space character with current background attribute
rep stosw ; to clear the last line.
popa
ret
; Function: print_string_pm.init
; Internal function of print_string_pm to compute the video memory
; address of the current cursor location and the address to the
; beginning of the current line
;
; Inputs: EBX = Base address of video page
; Returns: EDI = Current video memory offset of cursor
; EDX = Video memory offset to beginning of line
; Clobbers: None
.init:
push eax
mov eax, [cur_row] ; EAX = cur_row
mul dword [screen_width] ; EAX = cur_row * screen_width
mov edx, eax ; EDX = copy of offset to beginning of line
add eax, [cur_col] ; EAX = cur_row * screen_width + cur_col
lea edi, [ebx + eax * 2] ; EDI = memory location of current screen cell
pop eax
ret
bits 16
; Function: print_string_rm
; Display a string to the console on display page 0 in real mode
;
; Inputs: SI = Offset of address to print
; Clobbers: AX, BX, SI
; Returns: None
print_string_rm:
mov ah, 0x0e ; BIOS tty Print
xor bx, bx ; Set display page to 0 (BL)
jmp .getch
.repeat:
int 0x10 ; print character
.getch:
lodsb ; Get character from string
test al,al ; Have we reached end of string?
jnz .repeat ; if not process next character
.end:
ret
align 4
cur_row: dd 0x00
cur_col: dd 0x00
screen_width: dd 0x00
screen_height:dd 0x00
boot_init_msg:
db "Booting sequence initialized...", CR, LF, 0
load_gdt_msg:
db "Loading GDT...", CR, LF, 0
enter_pm_msg:
db "Entering 32-bit Protected Mode...", CR, LF, 0
in_pm_msg:
db "Executing code in protected mode!", CR, LF, 0
align 4
gdt_start:
dd 0 ; null descriptor
dd 0
gdt32_code:
dw 0FFFFh ; limit low
dw 0 ; base low
db 0 ; base middle
db 10011010b ; access
db 11001111b ; 32-bit, 4kb granularity, limit 0xffffffff bytes
db 0 ; base high
gdt32_data:
dw 0FFFFh ; limit low (Same as code)
dw 0 ; base low
db 0 ; base middle
db 10010010b ; access
db 11001111b ; 32-bit, 4kb granularity, limit 0xffffffff bytes
db 0 ; base high
end_of_gdt:
gdtr:
dw end_of_gdt - gdt_start - 1
; limit (Size of GDT - 1)
dd gdt_start ; base of GDT
CODE32_SEL equ gdt32_code - gdt_start
DATA32_SEL equ gdt32_data - gdt_start