;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Simulated Linear Frame Buffer
; Christopher Giese <geezer@execpc.com>
; http://www.execpc.com/~geezer/os/
;
; Updated March 6, 2001: can now return to V86 mode when done
;
; Some stuff (actually, pmode VESA BIOS bank switching) fixed by
; Alexei A. Frounze <alexfru@chat.ru>
; http://alexfru.chat.ru
;
; You can do anything with this code but blame us for it.
;
; To run this code, you need a PC with a 32-bit processor (386SX or better)
; and DOS. This code has been tested with the following SVGA video boards:
;
; bank-switch function
; buss chipset used by this code
; ---- ------- --------------------
; 16-bit ISA Cirrus 5422 Cirrus
; 32-bit PCI S3 86c765 (Trio 64V+) S3
; 32-bit PCI (?) STB Nitro (?) S3
;
; To assemble this code, you need NASM (http://www.web-sites.co.uk/nasm/)
; nasm -f bin -o slfb.com slfb.asm
;
; bugs/to do:
; - test with other systems
;
; Here is an interesting link:
; http://marc.theaimsgroup.com/?m=88102879813311&w=2
; Also, look at the "vflat" files in the source code to the
; SciTech MGL graphics library
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
LFB_BASE equ 0C0000000h
%if 1
VESA_MODE equ 101h ; 640x480x256
WIDTH equ 640
HEIGHT equ 480
%else
VESA_MODE equ 103h ; 800x600x256
WIDTH equ 800
HEIGHT equ 600
%endif
SEQN_INDX equ 3C4h
SEQN_DATA equ 3C5h
CRTC_INDX equ 3D4h
CRTC_DATA equ 3D5h
GRFX_INDX equ 3CEh
GRFX_DATA equ 3CFh
BITS 16
ORG 100h
; save CS value for return to real mode
mov [real_mode_cs],cs
; there is no foolproof way to ID an Intel CPU, but we'll try.
; If we are in V86 mode, the VMM may interfere with our attempts
; to change the IOPL and NT bits
pushf
pushf
pop ax
xor ah,07h ; try changing b14 (NT) and b13:b12
push ax ; (IOPL) of FLAGS
popf
pushf
pop bx
popf ; restore old FLAGS value
cmp ah,bh
je is_32bit
mov si,not_32bit_msg
jmp msg_and_exit
not_32bit_msg:
db "Sorry; this code requires a 32-bit CPU (386SX or better)."
db 13, 10, 0
is_32bit:
; check for supported video card. First, look for VESA 2.x BIOS
call probe_vesa
je vid_ok
; no VESA 2.x BIOS; try direct hardware probing for Cirrus 5422
call probe_cirrus
je vid_ok
; not Cirrus; try probing for S3
call probe_s3
je vid_ok
mov si,sorry_msg
jmp msg_and_exit
sorry_msg:
db 13, 10, "Sorry, no supported video card was found.", 13, 10, 0
ok_msg:
db "DETECTED", 13, 10, 0
vid_ok:
mov si,ok_msg
call wrstr
; our pmode code will use the stack DOS gave us, but we have
; to zero the top 16 bits of the stack pointer (ESP)
xor eax,eax
mov ax,sp
mov esp,eax
; get the virtual-to-physical conversion value. Though we turn on paging,
; this code runs in memory that is identity-mapped (i.e. no page-based
; address translation). We do, however, use segment-based address
; translation, to give things the same addresses in both real and pmode.
xor ebx,ebx
mov bx,cs
shl ebx,4
; set base addresses of the 16- and 32-bit code segment descriptors
mov eax,ebx
mov [gdt2 + 2],ax
mov [gdt4 + 2],ax
shr eax,16
mov [gdt2 + 4],al
mov [gdt4 + 4],al
mov [gdt2 + 7],ah
; mov [gdt4 + 7],ah ; no; this one is 16-bit
; now do the same with the data/stack segment descriptors
mov eax,ebx
mov [gdt3 + 2],ax
mov [gdt5 + 2],ax
shr eax,16
mov [gdt3 + 4],al
mov [gdt5 + 4],al
mov [gdt3 + 7],ah
; mov [gdt5 + 7],ah ; no; this one is 16-bit
; point the TSS descriptor to the LINEAR/PHYSICAL address of 'tss'
mov eax,tss
add eax,ebx
mov [gdt6 + 2],ax
shr eax,16
mov [gdt6 + 4],al
mov [gdt6 + 7],ah
; point the interrupt gates in the Interrupt Descriptor Table (IDT)
; to the default interrupt handler 'unhand'
mov di,idt
mov cx,(idt_end - idt) / 8 ; number of IDT entries (gates)
do_idt:
mov eax,unhand ; point all gates to 'unhand'
mov [di],ax ; set bits 15:0 of gate offset
shr eax,16
mov [di+6],ax ; set bits 31:16 of gate offset
add di,8 ; next gate
loop do_idt
mov di,idt_0e ; IDT entry 0Eh -> page fault
mov eax,page_fault
mov [di],ax
shr eax,16
mov [di+16],ax
; point 'gdt_ptr' to LINEAR/PHYSICAL address of the GDT;
; 'idt_ptr' to LINEAR/PHYSICAL adr of the IDT
add [gdt_ptr + 2],ebx
add [idt_ptr + 2],ebx
; DOS doesn't load .COM or .EXE files on a page (4K) boundary, so
; we must now find a page boundary for the page directory/tables.
xor esi,esi
mov si,page_info ; SI = offset of page_info
add esi,ebx ; ESI = LINEAR/PHYSICAL adr of page_info
add esi,4095 ; round to 4K boundary
and esi,0FFFFF000h ; ESI = LINEAR/PHYSICAL adr of page dir
mov edi,esi
sub edi,ebx ; DI = offset of page dir
; save page dir address for later use, by both VCPI and "raw" code
mov [vcpi_cr3],esi
; EAX = LINEAR/PHYSICAL address of "framebuffer" page table
; (4K above the page directory)
mov eax,esi
add eax,4096
; create entry in page dir for "framebuffer" page table
or al,7 ; Ring 3, writable, present
mov [di + (((LFB_BASE >> 22) & 3FFh) << 2)],eax
; EAX = LINEAR/PHYSICAL address of "kernel" page table
; (8K above the page directory)
mov eax,esi
add eax,8192
; create entry in page dir for "kernel" page table
or al,7 ; Ring 3, writable, present
mov [di + 0],eax
; advance DI from page directory to "framebuffer" page table
add edi,4096
; save the LINEAR/PHYSICAL pointer to the "framebuffer" page table
mov eax,edi
add eax,ebx
mov [fb_page_table],eax
; Map the simulated linear framebuffer from virtual address LFB_BASE
; to the actual banked framebuffer at physical address 000A0000,
; repeating every 64K (16 pages)
;
; 1600x1200x256 display needs 1875K simulated framebuffer
; == 30 banks of 64K each == 469 pages of 4K each
;
; If the simulated framebuffer crosses a 4 meg boundary, or is larger
; than 4 meg, you will need more than one "framebuffer" page table.
push di
add di,(((LFB_BASE >> 12) & 3FFh) << 2)
mov dl,30
map_fb1:
mov ecx,16
mov eax,0A0006h ; Ring 3, writable, NOT PRESENT
map_fb2:
stosd
add eax,4096 ; next page
loop map_fb2
dec dl
jne map_fb1
pop di
; advance DI to "kernel" page table
add di,4096
; using the "kernel" page table, identity-map
; (virtual == physical) the bottom 4M of RAM
; With VCPI, we can skip this step, and simply take over EMM386's
; conventional memory page table using INT 67h AX=DE01h
mov cx,1024
mov eax,7 ; Ring 3, writable, present
identity_map:
stosd
add eax,4096 ; next page
loop identity_map
; move DI back to "kernel" page table, in case we want to change it
sub di,4096
; check for virtual 8086 mode (Windows DOS box or EMM386 loaded)
smsw ax
test al,1 ; look at PE bit of MSW (CR0)
je real0
mov si,v86_msg
call wrstr
; if it's EMM386, we can use VCPI to switch to protected mode
; Testing for VCPI like this makes Win95 pop up a dialog suggesting
; "MS-DOS mode". This may or may not be a good thing.
mov ax,0DE00h
push bx ; save EBX; not interested in VCPI version
int 67h
pop bx
cmp ah,0
je vcpi0
; no VCPI? probably Windows DOS box; check for that
mov si,err_msg
mov ax,1600h
int 2Fh
cmp al,0
je msg_and_exit
cmp al,80h
je msg_and_exit
mov si,win_msg ; yup, it's 'Doze
msg_and_exit:
call wrstr
; exit to DOS with errorlevel 1
mov ax,4C01h
int 21h
vcpi0:
; get "kernel" page table and partial GDT from the VCPI server.
; If we set up our own identity-mapped page table, this step is optional.
; (we still need to do INT 67h AX=DE01h to get the pmode entry
; point to the VCPI server -- so we can return to V86 mode later)
mov si,gdt7
mov ax,0DE01h
push ebx
int 67h
mov [vcpi_entry],ebx
pop ebx
mov si,vcpi_err_msg
cmp ah,0
jne msg_and_exit
; set up the VCPI control block for the switch to pmode
add [vcpi_gdtr],ebx
add [vcpi_idtr],ebx
; await keypress; switch to graphics mode; disable interrupts
mov si,vcpi_msg
call setup
; OK, let's do it
mov esi,vcpi_control_block
add esi,ebx
mov ax,0DE0Ch
; if all goes well, the interrupt will return at 'from_vcpi'
int 67h
real0:
; await keypress; switch to graphics mode; disable interrupts
mov si,real_msg
call setup
; set vital registers: page directory base register (PDBR = CR3)
mov eax,[vcpi_cr3]
mov cr3,eax
; ...GDTR and IDTR
lgdt [gdt_ptr]
lidt [idt_ptr]
; ...enable paging and pmode
mov eax,cr0
or eax,80000001h
mov cr0,eax
jmp CODE_SEL:from_real
; we jump here (in 16-bit pmode) when returning to real mode
; Back-to-real-mode sequence from 14.5 of 386INTEL.TXT:
; 3. load segment registers with values "appropriate to real mode"
real2:
mov ax,DATA_SEL16
mov ss,ax
mov ds,ax
mov es,ax
mov fs,ax
mov gs,ax
; 4. disable interrupts
; (already done)
; 5. clear the PE bit
mov eax,cr0
and al,0FEh
mov cr0,eax
; 6. far jump to true real mode
mov word [real_mode_ip],real3
jmp far [real_mode_ip]
; 7. load an IDT that is compatible with real-mode IVT
real3:
lidt [real_idt_ptr]
; 9. restore real-mode segment registers
real4:
mov ax,cs
mov ss,ax
mov ds,ax
mov es,ax
mov fs,ax
mov gs,ax
; 8. enable interrupts
sti
; go back to text mode
mov ax,3
int 10h
mov si,done_msg
jmp msg_and_exit
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; REAL MODE SUBROUTINES
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; name: setup
; action: 1. calls wrstr
; 2. prompts the user to press a key, and waits for it
; 3. sets VESA graphics mode VESA_MODE
; (terminates if mode-set fails)
; 4. zeroes EFLAGS register (disables interrupts,
; sets IOPL=0, and clears NT bit)
; in: 0-terminated string at DS:SI
; out: (nothing)
; modifies: AX
; minimum CPU: 386SX
; notes:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
press_key_msg:
db "Press a key to continue", 13, 10, 0
vesa_err_msg:
db "Error setting VESA graphics mode", 13, 10, 0
setup:
push bx
; display the message pointed to by SI
call wrstr
; prompt user to press a key
mov si,press_key_msg
call wrstr
; wait for the key
mov ah,0
int 16h
; switch to VESA graphics mode
mov ax,4F02h
mov bx,VESA_MODE
int 10h
cmp ah,0
je setup1
cmp al,4Fh
je setup1
mov si,vesa_err_msg
jmp msg_and_exit
setup1:
push dword 0
popfd
pop bx
ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; name: wrstr
; action: writes ASCIZ string to text screen
; in: 0-terminated string at DS:SI
; out: (nothing)
; modifies: (nothing)
; minimum CPU: 8088
; notes:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
wrstr: push ax
push bx
push si
mov ah,0Eh ; INT 10h: teletype output
xor bx,bx ; video page 0
jmp wrstr2
wrstr1: int 10h
wrstr2: lodsb
or al,al
jne wrstr1
pop si
pop bx
pop ax
ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; name: probe_vesa
; action: detects VESA 2.x video BIOS
; in: (nothing)
; out (success): ZF=1, [bank_switch_fn] set
; out (failure): ZF=0
; modifies: EAX, BX, DI, SI
; minimum CPU: 386SX
; notes:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
vesa_msg:
db "Checking for VESA 2.x video BIOS...", 0
probe_vesa:
mov si,vesa_msg
call wrstr
push es
mov ax,4F00h
mov di,vesa_info
int 10h
cmp ah,0
jne no_vesa
; locate the pmode bank-switch function in the video BIOS ROM
mov ax,4F0Ah
mov bx,1
int 10h
cmp ax,004Fh
jne no_vesa
; Alexei fixed this for me:
; convert address of bank-switch function from seg:off to linear
add di, [es:di] ; add function offset to table offset
xor eax,eax
mov ax,es
sub ax,[real_mode_cs] ; our 32-bit code base address isn't 0,
; gotta adjust this!!!
shl eax,4
movzx edi, di ; yep, this is required as well :)
add eax,edi
; ...and store it
mov [bank_switch_fn],eax
; return ZF=1 for success
xor ax,ax
no_vesa:
pop es
ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; name: probe_s3
; action: detects S3 video board
; in: (nothing)
; out (success): ZF=1, [bank_switch_fn] set
; out (failure): ZF=0
; modifies: EAX, SI
; minimum CPU: 386SX
; notes:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
s3_msg:
db 13, 10, "no; checking for S3 video...", 0
probe_s3:
mov si,s3_msg
call wrstr
push dx
; lock the enhanced registers
mov dx,CRTC_INDX
mov al,039h
out dx,al
inc dx
mov al,00h
out dx,al
dec dx
mov al,38h
out dx,al
inc dx
mov al,00h
out dx,al
; see if CRTC reg 35h is read-only
dec dx
mov al,35h
out dx,al
inc dx
in al,dx
mov ah,al
xor al,0Fh
out dx,al
in al,dx
cmp al,ah
jne no_s3
; unlock S3 registers
dec dx
mov al,38h
out dx,al
inc dx
mov al,48h
out dx,al
dec dx
mov al,39h
out dx,al
inc dx
mov al,0A5h
out dx,al
; can CRTC reg 35h be changed now?
dec dx
mov al,35h
out dx,al
inc dx
in al,dx
mov ah,al
xor al,0Fh
out dx,al
in al,dx
cmp al,ah
je no_s3
; found S3; restore old value of reg 35h
mov al,ah
out dx,al
; set [bank_switch_fn] to linear address of set_s3_bank
mov dword [bank_switch_fn],set_s3_bank
; return ZF=1 for success
xor ax,ax
no_s3:
pop dx
ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; name: probe_cirrus
; action: detects video board with Cirrus 5422 chipset
; in: (nothing)
; out (success): ZF=1, [bank_switch_fn] set
; out (failure): ZF=0
; modifies: EAX, SI
; minimum CPU: 386SX
; notes:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
cirrus_msg:
db 13, 10, "no; checking for Cirrus 5422 video...", 0
probe_cirrus:
mov si,cirrus_msg
call wrstr
push dx
; unlock Cirrus registers
mov dx,SEQN_INDX
mov al,06h
out dx,al
inc dx
mov al,12h
out dx,al
; lock reg should now read as 12h
in al,dx
cmp al,12h
jne no_cirrus
; check the chip ID
mov dx,CRTC_INDX
mov al,27h
out dx,al
inc dx
in al,dx
shr al,2
cmp al,22h
jb no_cirrus
cmp al,28h
ja no_cirrus
; found a Cirrus board
; set [bank_switch_fn] to linear address of set_cirrus_bank
mov dword [bank_switch_fn],set_cirrus_bank
; return ZF=1 for success
xor ax,ax
no_cirrus:
pop dx
ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 32-BIT PROTECTED MODE SUBROUTINES
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
BITS 32
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; name: set_cirrus_bank
; action: sets 64K video bank (Cirrus 5422 video only)
; in: bank number in DL
; out: (nothing)
; modifies: (nothing)
; minimum CPU: 386SX
; notes: EDX and EAX are used only to make this code
; smaller. DX and AX could be used instead,
; if you want code to run on 16-bit CPUs.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
set_cirrus_bank:
push edx
push eax
mov eax,edx
and al,0Fh
shl al,4
mov ah,al
mov dx,GRFX_INDX
mov al,09h
out dx,al
inc edx
mov al,ah
out dx,al
pop eax
pop edx
ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; name: set_s3_bank
; action: sets 64K video bank (S3 video only)
; in: bank number in DL
; out: (nothing)
; modifies: (nothing)
; minimum CPU: 386SX
; notes: EDX and EAX are used only to make this code
; smaller. DX and AX could be used instead,
; if you want code to run on 16-bit CPUs.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
set_s3_bank:
push edx
push eax
mov eax,edx
and al,0Fh
mov ah,al
; grrrr...mode-set locked the S3 registers, so unlock them again
; xxx - do this after mode-set
mov dx,CRTC_INDX
mov al,38h
out dx,al
inc edx
mov al,48h
out dx,al
dec edx
mov al,39h
out dx,al
inc edx
mov al,0A5h
out dx,al
; now: do the bank-switch
mov dx,CRTC_INDX
mov al,35h
out dx,al
inc edx
in al,dx
and al,0F0h
or al,ah
out dx,al
pop eax
pop edx
ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; name: unhand
; action: handler for otherwise unhandled exceptions;
; draws short red diagonal line on SVGA screen
; in: (nothing)
; out: (nothing)
; modifies: (nothing)
; minimum CPU: 386SX
; notes: DOES NOT RETURN
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; write to physical address A0000 instead of virtual address
; LFB_BASE to avoid more page faults
unhand:
mov ax,LINEAR_SEL
mov es,ax
mov byte [es:0A0000h + (WIDTH + 1) * 0],4
mov byte [es:0A0000h + (WIDTH + 1) * 1],4
mov byte [es:0A0000h + (WIDTH + 1) * 2],4
mov byte [es:0A0000h + (WIDTH + 1) * 3],4
jmp $
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; name: page_fault
; action: handler for INT 0Eh (page fault); performs
; SVGA bank-switching for faults that come
; from the simulated linear framebuffer
; in: (nothing)
; out: (nothing)
; modifies: (nothing)
; minimum CPU: 386SX
; notes: This is an interrupt handler, so be careful.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
page_fault:
pusha
; did it come from the LFB? jump to 'unhand' if not
mov eax,cr2
sub eax,LFB_BASE
jc unhand
cmp eax, WIDTH*HEIGHT
jnc unhand ; some extra checking :)
; set SVGA bank
mov edx,cr2
shr edx,16
mov bh,0 ; bh=0 - set bank no. (VESA only)
call [bank_switch_fn]
; unmap all pages in the LFB
; xxx - this is slow; need only unmap the pages that are currently mapped
mov ebx,[fb_page_table]
mov ecx,1024
unmap:
and byte [es:ebx],0FEh ; Present=0
add ebx,4
loop unmap
; bits 31:22 of faulting adr are page dir index
; bits 21:12 of faulting adr are page table index
mov ebx,cr2
shr ebx,12
and ebx,3FFh
shl ebx,2
add ebx,[fb_page_table]
; map in 16 4K pages (one 64K bank)
mov ecx,16
map_fb:
or byte [es:ebx],01h ; Present=1
add ebx,4
loop map_fb
; we changed the page tables, so reload CR3 to flush the page
; table cache (the TLB). 486+ could use INVLPG instruction instead
mov eax,cr3
mov cr3,eax
popa
; drop error code
add esp,4
iret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; name: draw_lines
; action: draw green and blue 'X' on the screen,
; return after a delay
; in: (nothing)
; out: (nothing)
; modifies: ECX, EBX
; minimum CPU: 386SX
; notes:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Note the absence of bank-switching code here. Simple, eh?
; xxx - this code for 256-color screen only
draw_lines:
mov ecx,HEIGHT
mov ebx,LFB_BASE + (WIDTH - HEIGHT) / 2
ul_lr:
mov byte [es:ebx],1 ; blue
add ebx,WIDTH + 1
loop ul_lr
mov ecx,HEIGHT
mov ebx,LFB_BASE + (WIDTH - HEIGHT) / 2 + HEIGHT
ur_ll:
mov byte [es:ebx],2 ; green
add ebx,WIDTH - 1
loop ur_ll
; xxx - await keypress here -- somehow
mov ecx,03FFFFFFh
spin:
loop spin
ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; MAIN 32-BIT PMODE CODE, here from VCPI
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
from_vcpi:
mov ax,DATA_SEL
mov ss,ax
mov ds,ax
mov ax,LINEAR_SEL
mov es,ax
mov fs,ax
mov gs,ax
call draw_lines
; return to V86 mode
mov eax,0000DE0Ch
; from E-67DE0C of Ralf Brown's list:
; DS = segment selector mapping entire linear
; address space obtained via AX=DE01h
; I'm not sure what that means, but I peeked inside EMM386.EXE,
; and it sets DS for me, so I don't have to.
movzx ebx,word [real_mode_cs]
mov ebp,esp
push ebx ; GS
push ebx ; FS
push ebx ; DS
push ebx ; ES
push ebx ; SS
push ebp ; ESP
push dword 00023020h ; EFLAGS
push ebx ; CS
push dword real4 ; EIP
call far [vcpi_entry]
; should not reach this
jmp short $
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; MAIN 32-BIT PMODE CODE, here from "raw" mode (real mode)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
from_real:
mov ax,DATA_SEL
mov ss,ax
mov ds,ax
mov ax,LINEAR_SEL
mov es,ax
mov fs,ax
mov gs,ax
call draw_lines
; back-to-real-mode sequence from 14.5 of 386INTEL.TXT
; 1. TURN OFF PAGING
; 1a. jump to code memory which is identity-mapped
; (already done)
; 1b. clear the PG bit
mov eax,cr0
and eax,7FFFFFFFh
mov cr0,eax
; 1c. "Move zeros to CR3 to clear out the paging cache."
xor eax,eax
mov cr3,eax
; 2. jump to 16-bit code segment (real2 is above, in the BITS 16 section)
jmp CODE_SEL16:real2
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; DATA
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
v86_msg:
db "CPU is in virtual 8086 mode...", 13, 10, 0
err_msg:
db "...and I don't know why (no EMM386, no Windows).", 13, 10
db "Sorry, can't switch to protected mode.", 13, 10, 0
win_msg:
db "Sorry, this code won't run in a Windows DOS box.", 13, 10
db "Re-start your computer in 'MS-DOS mode'.", 13, 10, 0
vcpi_msg:
db "VCPI server detected (probably EMM386).", 13, 10
db "I will try to use VCPI to switch to protected mode.", 13, 10, 0
vcpi_err_msg:
db "Error getting VCPI server entry point.", 13, 10, 0
real_msg:
db "CPU is in real mode.", 13, 10, 0
done_msg:
db "Back to DOS, wheee!", 13, 10, 0
; Even if you don't use TSS-based task-switching, you need one
; TSS to hold the user stack pointer.
tss:
dw 0, 0 ; back link
dd 0 ; ESP0
dw DATA_SEL, 0 ; SS0, reserved
dd 0 ; ESP1
dw 0, 0 ; SS1, reserved
dd 0 ; ESP2
dw 0, 0 ; SS2, reserved
dd 0 ; CR3
dd 0, 0 ; EIP, EFLAGS
dd 0, 0, 0, 0 ; EAX, ECX, EDX, EBX
dd 0, 0, 0, 0 ; ESP, EBP, ESI, EDI
dw 0, 0 ; ES, reserved
dw 0, 0 ; CS, reserved
dw 0, 0 ; SS, reserved
dw 0, 0 ; DS, reserved
dw 0, 0 ; FS, reserved
dw 0, 0 ; GS, reserved
dw 0, 0 ; LDT, reserved
dw 0, 0 ; debug, IO perm. bitmap
; null descriptor
gdt:
dw 0 ; limit 15:0
dw 0 ; base 15:0
db 0 ; base 23:16
db 0 ; type
db 0 ; limit 19:16, flags
db 0 ; base 31:24
LINEAR_SEL equ $-gdt
dw 0FFFFh
dw 0
db 0
db 92h ; present, ring 0, data, expand-up, writable
db 0CFh ; page-granular, 32-bit
db 0
CODE_SEL equ $-gdt
gdt2:
dw 0FFFFh
dw 0
db 0
db 9Ah ; present, ring 0, code, non-conforming, readable
db 0CFh ; page-granular, 32-bit
db 0
DATA_SEL equ $-gdt
gdt3:
dw 0FFFFh
dw 0
db 0
db 92h ; present, ring 0, data, expand-up, writable
db 0CFh ; page-granular, 32-bit
db 0
CODE_SEL16 equ $-gdt
gdt4:
dw 0FFFFh
dw 0
db 0
db 9Ah ; present, ring 0, code, non-conforming, readable
db 0 ; byte-granular, 16-bit
db 0
DATA_SEL16 equ $-gdt
gdt5:
dw 0FFFFh
dw 0
db 0
db 92h ; present, ring 0, data, expand-up, writable
db 0 ; byte-granular, 16-bit
db 0
TSS_SEL equ $-gdt
gdt6:
dw 103
dw 0
db 0
db 089h ; Ring 0 available 32-bit TSS
db 0
db 0
VCPI_SEL equ $-gdt
gdt7:
dd 0, 0 ; dummy descriptors used by VCPI
dd 0, 0
dd 0, 0
gdt_end:
idt:
%rep 14
dw 0 ; low 16 bits of ISR address
dw CODE_SEL ; selector
db 0 ; word count
db 8Eh ; access byte: Present, Ring 0, '386 intr gate
dw 0 ; high 16 bits of ISR
%endrep
idt_0e:
%rep 18
dw 0 ; low 16 bits of ISR address
dw CODE_SEL ; selector
db 0 ; word count
db 8Eh ; access byte: Present, Ring 0, '386 intr gate
dw 0 ; high 16 bits of ISR
%endrep
idt_end:
gdt_ptr:
dw gdt_end - gdt - 1 ; GDT limit
dd gdt ; linear, physical address of GDT
idt_ptr:
dw idt_end - idt - 1 ; IDT limit
dd idt ; linear, physical address of IDT
real_idt_ptr:
dw 3FFh ; limit 1023
dd 0 ; IDT (IVT, actually) at address 0
bank_switch_fn:
dd 0
krnl_page_table:
dw 0
fb_page_table:
dd 0
real_mode_ip:
dw 0
real_mode_cs:
dw 0
page_info:
times 4096 db 0 ; padding to 4K boundary
times 4096 db 0 ; page dir somewhere in here
times 4096 db 0 ; "framebuffer" page table for SLFB
times 4096 db 0 ; "kernel" page table for bottom 4 meg
vcpi_entry:
dd 0
dw VCPI_SEL
vcpi_control_block:
vcpi_cr3:
dd 0
vcpi_gdtr:
dd gdt_ptr
vcpi_idtr:
dd idt_ptr
vcpi_ldtr:
dw 0
vcpi_tr:
dw TSS_SEL
vcpi_eip:
dd from_vcpi
vcpi_cs:
dw CODE_SEL
vesa_info:
db "VBE2"
vesa_minor:
db 0
vesa_major:
db 0
times 506 db 0