;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Simulated Linear Frame Buffer ; Christopher Giese ; http://my.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 ; 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