;****************************************************************************
;*
;*                  SciTech OS Portability Manager Library
;*
;*  ========================================================================
;*
;*    The contents of this file are subject to the SciTech MGL Public
;*    License Version 1.0 (the "License"); you may not use this file
;*    except in compliance with the License. You may obtain a copy of
;*    the License at http://www.scitechsoft.com/mgl-license.txt
;*
;*    Software distributed under the License is distributed on an
;*    "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
;*    implied. See the License for the specific language governing
;*    rights and limitations under the License.
;*
;*    The Original Code is Copyright (C) 1991-1998 SciTech Software, Inc.
;*
;*    The Initial Developer of the Original Code is SciTech Software, Inc.
;*    All Rights Reserved.
;*
;*  ========================================================================
;*
;*            Based on original code Copyright 1994 Otto Chrons
;*
;* Language:    NASM
;* Environment: IBM PC 32 bit protected mode
;*
;* Description: Low level page fault handler for virtual linear framebuffers.
;*
;****************************************************************************

include "scitech.mac"           ; Memory model macros

header      _vflat              ; Set up memory model

VFLAT_START     EQU 0F0000000h
VFLAT_END       EQU 0F03FFFFFh
PAGE_PRESENT    EQU 1
PAGE_NOTPRESENT EQU 0
PAGE_READ       EQU 0
PAGE_WRITE      EQU 2

ifdef   DOS4GW

;----------------------------------------------------------------------------
; DOS4G/W flat linear framebuffer emulation.
;----------------------------------------------------------------------------

begdataseg  _vflat

; Near pointers to the page directory base and our page tables. All of
; this memory is always located in the first Mb of DOS memory.

PDBR            dd  0               ; Page directory base register (CR3)
accessPageAddr  dd  0
accessPageTable dd  0

; CauseWay page directory & 1st page table linear addresses.

CauseWayDIRLinear dd 0
CauseWay1stLinear dd 0

; Place to store a copy of the original Page Table Directory before we
; intialised our virtual buffer code.

pageDirectory:  resd 1024           ; Saved page table directory

ValidCS         dw  0               ; Valid CS for page faults
Ring0CS         dw  0               ; Our ring 0 code selector
LastPage        dd  0               ; Last page we mapped in
BankFuncBuf:    resb 101            ; Place to store bank switch code
BankFuncPtr     dd  offset BankFuncBuf

INT14Gate:
INT14Offset     dd      0           ; eip of original vector
INT14Selector   dw      0           ; cs of original vector

        cextern _PM_savedDS,USHORT
        cextern VF_haveCauseWay,BOOL

enddataseg  _vflat

begcodeseg  _vflat              ; Start of code segment

        cextern VF_malloc,FPTR

;----------------------------------------------------------------------------
; PF_handler64k - Page fault handler for 64k banks
;----------------------------------------------------------------------------
; The handler below is a 32 bit ring 0 page fault handler.  It receives
; control immediately after any page fault or after an IRQ6 (hardware
; interrupt). This provides the fastest possible handling of page faults
; since it jump directly here.  If this is a page fault, the number
; immediately on the stack will be an error code, at offset 4 will be
; the eip of the faulting instruction, at offset 8 will be the cs of the
; faulting instruction.  If it is a hardware interrupt, it will not have
; the error code and the eflags will be at offset 8.
;----------------------------------------------------------------------------
cprocfar    PF_handler64k

; Check if this is a processor exeception or a page fault

        push    eax
        mov     ax,[cs:ValidCS]     ; Use CS override to access data
        cmp     [ss:esp+12],ax      ; Is this a page fault?
        jne     @@ToOldHandler      ; Nope, jump to the previous handler

; Get address of page fault and check if within our handlers range

        mov     eax,cr2             ; EBX has page fault linear address
        cmp     eax,VFLAT_START     ; Is the fault less than ours?
        jb      @@ToOldHandler      ; Yep, go to previous handler
        cmp     eax,VFLAT_END       ; Is the fault more than ours?
        jae     @@ToOldHandler      ; Yep, go to previous handler

; This is our page fault, so we need to handle it

        pushad
        push    ds
        push    es
        mov     ebx,eax             ; EBX := page fault address
        and     ebx,invert 0FFFFh   ; Mask to 64k bank boundary
        mov     ds,[cs:_PM_savedDS]; Load segment registers
        mov     es,[cs:_PM_savedDS]

; Map in the page table for our virtual framebuffer area for modification

        mov     edi,[PDBR]          ; EDI points to page directory
        mov     edx,ebx             ; EDX = linear address
        shr     edx,22              ; EDX = offset to page directory
        mov     edx,[edx*4+edi]     ; EDX = physical page table address
        mov     eax,edx
        mov     edx,[accessPageTable]
        or      eax,7
        mov     [edx],eax
        mov     eax,cr3
        mov     cr3,eax             ; Update page table cache

; Mark all pages valid for the new page fault area

        mov     esi,ebx             ; ESI := linear address for page
        shr     esi,10
        and     esi,0FFFh           ; Offset into page table
        add     esi,[accessPageAddr]
%assign off 0
%rep 16
        or      [DWORD esi+off],0000000001h ; Enable pages
%assign off off+4
%endrep

; Mark all pages invalid for the previously mapped area

        xchg    esi,[LastPage]      ; Save last page for next page fault
        test    esi,esi
        jz      @@DoneMapping       ; Dont update if first time round
%assign off 0
%rep 16
        or      [DWORD esi+off],0FFFFFFFEh  ; Disable pages
%assign off off+4
%endrep

@@DoneMapping:
        mov     eax,cr3
        mov     cr3,eax             ; Flush the TLB

; Now program the new SuperVGA starting bank address

        mov     eax,ebx             ; EAX := page fault address
        shr     eax,16
        and     eax,0FFh            ; Mask to 0-255
        call    [BankFuncPtr]       ; Call the bank switch function

        pop     es
        pop     ds
        popad
        pop     eax
        add     esp,4               ; Pop the error code from stack
        iretd                       ; Return to faulting instruction

@@ToOldHandler:
        pop     eax
        jmp     far dword [cs:INT14Gate]; Chain to previous handler

cprocend

;----------------------------------------------------------------------------
; PF_handler4k  - Page fault handler for 4k banks
;----------------------------------------------------------------------------
; The handler below is a 32 bit ring 0 page fault handler.  It receives
; control immediately after any page fault or after an IRQ6 (hardware
; interrupt). This provides the fastest possible handling of page faults
; since it jump directly here.  If this is a page fault, the number
; immediately on the stack will be an error code, at offset 4 will be
; the eip of the faulting instruction, at offset 8 will be the cs of the
; faulting instruction.  If it is a hardware interrupt, it will not have
; the error code and the eflags will be at offset 8.
;----------------------------------------------------------------------------
cprocfar    PF_handler4k

; Fill in when we have tested all the 64Kb code

        jmp     far dword [cs:INT14Gate]; Chain to previous handler

cprocend

;----------------------------------------------------------------------------
; void InstallFaultHandler(void *baseAddr,int bankSize)
;----------------------------------------------------------------------------
; Installes the page fault handler directly int the interrupt descriptor
; table for maximum performance. This of course requires ring 0 access,
; but none of this stuff will run without ring 0!
;----------------------------------------------------------------------------
cprocstart  InstallFaultHandler

        ARG     baseAddr:ULONG, bankSize:UINT

        enter_c

        mov     [DWORD LastPage],0  ; No pages have been mapped
        mov     ax,cs
        mov     [ValidCS],ax        ; Save CS value for page faults

; Put address of our page fault handler into the IDT directly

        sub     esp,6               ; Allocate space on stack
        sidt    [ss:esp]            ; Store pointer to IDT
        pop     ax                  ; add esp,2
        pop     eax                 ; Absolute address of IDT
        add     eax,14*8            ; Point to Int #14

; Note that Interrupt gates do not have the high and low word of the
; offset in adjacent words in memory, there are 4 bytes separating them.

        mov     ecx,[eax]           ; Get cs and low 16 bits of offset
        mov     edx,[eax+6]         ; Get high 16 bits of offset in dx
        shl     edx,16
        mov     dx,cx               ; edx has offset
        mov     [INT14Offset],edx   ; Save offset
        shr     ecx,16
        mov     [INT14Selector],cx  ; Save original cs
        mov     [eax+2],cs          ; Install new cs
        mov     edx,offset PF_handler64k
        cmp     [UINT bankSize],4
        jne     @@1
        mov     edx,offset PF_handler4k
@@1:    mov     [eax],dx            ; Install low word of offset
        shr     edx,16
        mov     [eax+6],dx          ; Install high word of offset

        leave_c
        ret

cprocend

;----------------------------------------------------------------------------
; void RemoveFaultHandler(void)
;----------------------------------------------------------------------------
; Closes down the virtual framebuffer services and restores the previous
; page fault handler.
;----------------------------------------------------------------------------
cprocstart  RemoveFaultHandler

        enter_c

; Remove page fault handler from IDT

        sub     esp,6               ; Allocate space on stack
        sidt    [ss:esp]            ; Store pointer to IDT
        pop     ax                  ; add esp,2
        pop     eax                 ; Absolute address of IDT
        add     eax,14*8            ; Point to Int #14
        mov     cx,[INT14Selector]
        mov     [eax+2],cx          ; Restore original CS
        mov     edx,[INT14Offset]
        mov     [eax],dx            ; Install low word of offset
        shr     edx,16
        mov     [eax+6],dx          ; Install high word of offset

        leave_c
        ret

cprocend

;----------------------------------------------------------------------------
; void InstallBankFunc(int codeLen,void *bankFunc)
;----------------------------------------------------------------------------
; Installs the bank switch function by relocating it into our data segment
; and making it into a callable function. We do it this way to make the
; code identical to the way that the VflatD devices work under Windows.
;----------------------------------------------------------------------------
cprocstart  InstallBankFunc

        ARG     codeLen:UINT, bankFunc:DPTR

        enter_c

        mov     esi,[bankFunc]      ; Copy the code into buffer
        mov     edi,offset BankFuncBuf
        mov     ecx,[codeLen]
    rep movsb
        mov     [BYTE edi],0C3h     ; Terminate the function with a near ret

        leave_c
        ret

cprocend

;----------------------------------------------------------------------------
; int InitPaging(void)
;----------------------------------------------------------------------------
; Initializes paging system. If paging is not enabled, builds a page table
; directory and page tables for physical memory
;
;   Exit:       0   - Successful
;               -1  - Couldn't initialize paging mechanism
;----------------------------------------------------------------------------
cprocstart  InitPaging

        push    ebx
        push    ecx
        push    edx
        push    esi
        push    edi

; Are we running under CauseWay?

        mov     ax,0FFF9h
        int     31h
        jc      @@NotCauseway
        cmp     ecx,"CAUS"
        jnz     @@NotCauseway
        cmp     edx,"EWAY"
        jnz     @@NotCauseway

        mov     [BOOL VF_haveCauseWay],1
        mov     [CauseWayDIRLinear],esi
        mov     [CauseWay1stLinear],edi

; Check for DPMI

        mov     ax,0ff00h
        push    es
        int     31h
        pop     es
        shr     edi,2
        and     edi,3
        cmp     edi,2
        jz      @@ErrExit               ; Not supported under DPMI

        mov     eax,[CauseWayDIRLinear]
        jmp     @@CopyCR3

@@NotCauseway:
        mov     ax,cs
        test    ax,3                    ; Which ring are we running
        jnz     @@ErrExit               ; Needs zero ring to access
                                        ; page tables (CR3)
        mov     eax,cr0                 ; Load CR0
        test    eax,80000000h           ; Is paging enabled?
        jz      @@ErrExit               ; No, we must have paging!

        mov     eax,cr3                 ; Load directory address
        and     eax,0FFFFF000h

@@CopyCR3:
        mov     [PDBR],eax              ; Save it
        mov     esi,eax
        mov     edi,offset pageDirectory
        mov     ecx,1024
        cld
        rep     movsd                   ; Copy the original page table directory
        cmp     [DWORD accessPageAddr],0; Check if we have allocated page
        jne     @@HaveRealMem           ; table already (we cant free it)

        mov     eax,0100h               ; DPMI DOS allocate
        mov     ebx,8192/16
        int     31h                     ; Allocate 8192 bytes
        and     eax,0FFFFh
        shl     eax,4                   ; EAX points to newly allocated memory
        add     eax,4095
        and     eax,0FFFFF000h          ; Page align
        mov     [accessPageAddr],eax

@@HaveRealMem:
        mov     eax,[accessPageAddr]    ; EAX -> page table in 1st Mb
        shr     eax,12
        and     eax,3FFh                ; Page table offset
        shl     eax,2
        cmp     [BOOL VF_haveCauseWay],0
        jz      @@NotCW0
        mov     ebx,[CauseWay1stLinear]
        jmp     @@Put1st

@@NotCW0:
        mov     ebx,[PDBR]
        mov     ebx,[ebx]
        and     ebx,0FFFFF000h          ; Page table for 1st megabyte

@@Put1st:
        add     eax,ebx
        mov     [accessPageTable],eax
        sub     eax,eax                 ; No error
        jmp     @@Exit

@@ErrExit:
        mov     eax,-1

@@Exit: pop     edi
        pop     esi
        pop     edx
        pop     ecx
        pop     ebx
        ret

cprocend

;----------------------------------------------------------------------------
; void ClosePaging(void)
;----------------------------------------------------------------------------
; Closes the paging system
;----------------------------------------------------------------------------
cprocstart  ClosePaging

        push    eax
        push    ecx
        push    edx
        push    esi
        push    edi

        mov     eax,[accessPageAddr]
        call    AccessPage              ; Restore AccessPage mapping
        mov     edi,[PDBR]
        mov     esi,offset pageDirectory
        mov     ecx,1024
        cld
        rep     movsd                   ; Restore the original page table directory

@@Exit: pop     edi
        pop     esi
        pop     edx
        pop     ecx
        pop     eax
        ret

cprocend

;----------------------------------------------------------------------------
; long AccessPage(long phys)
;----------------------------------------------------------------------------
; Maps a known page to given physical memory
;   Entry:      EAX - Physical memory
;   Exit:       EAX - Linear memory address of mapped phys mem
;----------------------------------------------------------------------------
cprocstatic     AccessPage

        push    edx
        mov     edx,[accessPageTable]
        or      eax,7
        mov     [edx],eax
        mov     eax,cr3
        mov     cr3,eax                 ; Update page table cache
        mov     eax,[accessPageAddr]
        pop     edx
        ret

cprocend

;----------------------------------------------------------------------------
; long GetPhysicalAddress(long linear)
;----------------------------------------------------------------------------
; Returns the physical address of linear address
;   Entry:      EAX - Linear address to convert
;   Exit:       EAX - Physical address
;----------------------------------------------------------------------------
cprocstatic     GetPhysicalAddress

        push    ebx
        push    edx
        mov     edx,eax
        shr     edx,22                  ; EDX is the directory offset
        mov     ebx,[PDBR]
        mov     edx,[edx*4+ebx]         ; Load page table address
        push    eax
        mov     eax,edx
        call    AccessPage              ; Access the page table
        mov     edx,eax
        pop     eax
        shr     eax,12
        and     eax,03FFh               ; EAX offset into page table
        mov     eax,[edx+eax*4]         ; Load physical address
        and     eax,0FFFFF000h
        pop     edx
        pop     ebx
        ret

cprocend

;----------------------------------------------------------------------------
; void CreatePageTable(long pageDEntry)
;----------------------------------------------------------------------------
; Creates a page table for specific address (4MB)
;       Entry:  EAX - Page directory entry (top 10-bits of address)
;----------------------------------------------------------------------------
cprocstatic     CreatePageTable

        push    ebx
        push    ecx
        push    edx
        push    edi
        mov     ebx,eax                 ; Save address
        mov     eax,8192
        push    eax
        call    VF_malloc              ; Allocate page table directory
        add     esp,4
        add     eax,0FFFh
        and     eax,0FFFFF000h          ; Page align (4KB)
        mov     edi,eax                 ; Save page table linear address
        sub     eax,eax                 ; Fill with zero
        mov     ecx,1024
        cld
        rep     stosd                   ; Clear page table
        sub     edi,4096
        mov     eax,edi
        call    GetPhysicalAddress
        mov     edx,[PDBR]
        or      eax,7                   ; Present/write/user bit
        mov     [edx+ebx*4],eax         ; Save physical address into page directory
        mov     eax,cr3
        mov     cr3,eax                 ; Update page table cache
        pop     edi
        pop     edx
        pop     ecx
        pop     ebx
        ret

cprocend

;----------------------------------------------------------------------------
; void MapPhysical2Linear(ulong pAddr, ulong lAddr, int pages, int flags);
;----------------------------------------------------------------------------
; Maps physical memory into linear memory
;   Entry:      pAddr   - Physical address
;               lAddr   - Linear address
;               pages   - Number of 4K pages to map
;               flags   - Page flags
;                           bit 0   =       present
;                           bit 1   =       Read(0)/Write(1)
;----------------------------------------------------------------------------
cprocstart  MapPhysical2Linear

        ARG     pAddr:ULONG, lAddr:ULONG, pages:UINT, pflags:UINT

        enter_c

        and     [ULONG pAddr],0FFFFF000h; Page boundary
        and     [ULONG lAddr],0FFFFF000h; Page boundary
        mov     ecx,[pflags]
        and     ecx,11b                 ; Just two bits
        or      ecx,100b                ; Supervisor bit
        mov     [pflags],ecx

        mov     edx,[lAddr]
        shr     edx,22                  ; EDX = Directory
        mov     esi,[PDBR]
        mov     edi,[pages]             ; EDI page count
        mov     ebx,[lAddr]

@@CreateLoop:
        mov     ecx,[esi+edx*4]         ; Load page table address
        test    ecx,1                   ; Is it present?
        jnz     @@TableOK
        mov     eax,edx
        call    CreatePageTable         ; Create a page table
@@TableOK:
        mov     eax,ebx
        shr     eax,12
        and     eax,3FFh
        sub     eax,1024
        neg     eax                     ; EAX = page count in this table
        inc     edx                     ; Next table
        mov     ebx,0                   ; Next time we'll map 1K pages
        sub     edi,eax                 ; Subtract mapped pages from page count
        jns     @@CreateLoop            ; Create more tables if necessary

        mov     ecx,[pages]             ; ECX = Page count
        mov     esi,[lAddr]
        shr     esi,12                  ; Offset part isn't needed
        mov     edi,[pAddr]
@@MappingLoop:
        mov     eax,esi
        shr     eax,10                  ; EAX = offset to page directory
        mov     ebx,[PDBR]
        mov     eax,[eax*4+ebx]         ; EAX = page table address
        call    AccessPage
        mov     ebx,esi
        and     ebx,3FFh                ; EBX = offset to page table
        mov     edx,edi
        add     edi,4096                ; Next physical address
        inc     esi                     ; Next linear page
        or      edx,[pflags]            ; Update flags...
        mov     [eax+ebx*4],edx         ; Store page table entry
        loop    @@MappingLoop
        mov     eax,cr3
        mov     cr3,eax                 ; Update page table cache

        leave_c
        ret

cprocend

endcodeseg  _vflat

endif

        END                     ; End of module
