;   >>> this is file LOADLINM.ASM
;============================================================================
;   LOADLIN v1.6 (C) 1994..1996 Hans Lermen (lermen@elserv.ffm.fgan.de)
;
;   This program is free software; you can redistribute it and/or modify
;   it under the terms of the GNU General Public License as published by
;   the Free Software Foundation; either version 2 of the License, or
;   (at your option) any later version.
;
;   This program is distributed in the hope that it will be useful,
;   but WITHOUT ANY WARRANTY; without even the implied warranty of
;   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;   GNU General Public License for more details.
;
;   You should have received a copy of the GNU General Public License
;   along with this program; if not, write to the Free Software
;   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
;
;----------------------------------------------------------------------------
;   Comments and bug reports are welcome and may be sent to:
;   E-Mail:    lermen@elserv.ffm.fgan.de
;   SnailMail: Hans Lermen
;              Am Muehlenweg 38
;              D53424 REMAGEN-Unkelbach
;              GERMANY
;
;============================================================================

;---------------------------------------------------------------------------



call_pmode_routine proc near
; NOTE: must have called get_VCPI_interface before this
;       _AND_ must be already moved high, else the pagetable
;       will not be aligned.
; input:
;  AX  = offset of protected mode routine
; The called routine will have a pointer in ebp to a pushad_struct
; of the other input registers, and all segment register point to
; High_seg
;
@@regs struc
  pushAD_struc @@
@@regs ends
          pushf
          CLI
          pushad
          mov    ebp,esp
                 ; ÚÄÄÄÄÄ  IRET stack for return to VM86
          push   0
           push   gs
          push   0
           push   fs
          push   0
           push   ds
          push   0
           push   es
          push   0
           push   ss
          push   ebp
          sub    sp,4           ; space for EFLAGS
          push   0
           push   cs
          push   0
           push   offset @@pmode_return
                 ; ÀÄÄÄÄÄÄ  IRET stack for return to VM86
          mov    cs:pmode_return_esp,esp
          lea    ax,@@pmode_task
          movzx  eax,ax
          mov    dword ptr protected_mode_target,eax
          mov    word ptr protected_mode_target+4,g_code
          movzx   esi,cs:High_Seg
          shl     esi,4
          lea     si,our_CR3[si]
          mov     ax,0DE0Ch
          int     emm_int    ; jumps to protected mode
          ; and comes here in 16-bit protected mode
@@pmode_task:
          mov     ax,g_data
          mov     ss,ax
          mov     ds,ax
          mov     es,ax
          mov     fs,ax
          mov     gs,ax
          mov     esp,cs:pmode_return_esp

                             ; ebp pointing to pushad_struct
          mov     ax, word ptr [bp].@@eax
          call    ax         ; call the routine

          ; now we return to VM86 mode
          cli      ; to be save
          mov     ax,g_core    ; selector for addressing first MByte
          mov     ds,ax
          .386p
          CLTS                 ; clear taskswitch flag
          .386
          mov     eax,0DE0Ch
          call    fword ptr cs:server_vcpi_entry  ; back to VM86

          ; and returns here in VM86 mode
@@pmode_return:
          popad
          popf
          ret
call_pmode_routine endp


IF 0
AUXPAGE       =  (modul_end-code_org0) ; behind our code
ELSE
AUXPAGE       =  0 ; at start of our segment, only used in protected mode
ENDIF
AUXPAGE_size  =  02000h
AUXPAGE_entry =  (AUXPAGE shr (12-2))
AUXPAGE_S     =  AUXPAGE
AUXPAGE_T     =  AUXPAGE+AUXPAGE_size

AUXPAGE_S_entry equ page0+AUXPAGE_entry[bx]
AUXPAGE_T_entry equ page0+(AUXPAGE_entry+(AUXPAGE_size shr (12-2)))[bx]
AUXPAGE_BITS  =  3       ; writeable, present

move_anywhere_vcpi proc near
; input:
;   ESI= linear source address
;   EDI= linear target address
;    CX= size to move, must be <= 4096 !!!
; output:
;    all registers preserved
                ; we are in VM86 and at this point assume to have VCPI
        push    ax
        lea     ax,@@pmove
        call    call_pmode_routine
        pop     ax
        ret
                ; this will be executed in 16-bit protected mode
@@pmove:
@@regs struc
  pushAD_struc @@
@@regs ends
                ; save pagetable-entries
        mov     bx,High_Seg
        shr     bx,(12-4-2)
        push    AUXPAGE_S_entry
        push    AUXPAGE_S_entry+4
        push    AUXPAGE_T_entry
        push    AUXPAGE_T_entry+4

mmm     macro   reg,st
        mov     eax,[bp].@@e&reg
        mov     reg,ax
        and     reg,0fffh
        lea     reg,[reg]+AUXPAGE_&st
        and     ax,0f000h
        or      al,AUXPAGE_BITS
        mov     AUXPAGE_&st&_entry,eax
        add     eax,1000h
        mov     AUXPAGE_&st&_entry+4,eax
        endm

        mmm     si,S        ; map source
        mmm     di,T        ; map target
          .386p
        mov     eax,CR3     ; flush TLB
        mov     CR3,eax
          .386

        cld
        movzx   ecx,cx
        ror     ecx,2
        rep movsd
        rol     ecx,2
        rep movsb
                ; restore pagetable-entries
        pop     AUXPAGE_T_entry+4
        pop     AUXPAGE_T_entry
        pop     AUXPAGE_S_entry+4
        pop     AUXPAGE_S_entry
        ret
move_anywhere_vcpi endp


;---------------------------------------------------------------


move_anywhere_INT15 proc near
; input: ESI = source linear address
;        EDI = destination linear address
;        CX  = count
; returns:
;        all registers preserved
;
@@descript    struc
  @@limit     dw    ?
  @@base0     dw    ?
  @@base16    db    ?
  @@typbyte   db    ?
  @@limit16   db    ?
  @@base24    db    ?
@@descript    ends
        push_   es,eax,esi,edi,ecx
        mov     cs:@@src.@@base0,si
        shld    eax,esi,16
        mov     cs:@@src.@@base16,al
        mov     cs:@@src.@@base24,ah
        mov     cs:@@dest.@@base0,di
        shld    eax,edi,16
        mov     cs:@@dest.@@base16,al
        mov     cs:@@dest.@@base24,ah
        mov     cx,(1000h/2)  ; force to be one page
                              ; NOTE: INT15 moves are in WORDS,
                              ; because we always move 1000h,
                              ; we simple do it also for the last page
        push    cs
        pop     es
        lea     si,@@gdt
        mov     ax,08700h
        int     15h
        pop_    es,eax,esi,edi,ecx
        ret
        align   qword
@@gdt   @@descript <0,0,0,0,0,0>
        @@descript <0,0,0,0,0,0>
@@src   @@descript <0ffffh,0,0,093h,0,0>
@@dest  @@descript <0ffffh,0,0,093h,0,0>
        @@descript <0,0,0,0,0,0>      ; BIOS CS
        @@descript <0,0,0,0,0,0>      ; BIOS SS
move_anywhere_INT15 endp

;============================================================================

DUMMY_XMS_BLOCK_START = 100000h

XMS_GET_VERSION   =  0
XMS_QUERY_FREE    =  8
XMS_ALLOC         =  9
XMS_FREE          =  0ah
XMS_MOVE_BLOCK    =  0bh
XMS_LOCK_BLOCK    =  0ch
XMS_UNLOCK_BLOCK  =  0dh

xmscall   macro   function
          mov     ah,function
          call    cs:xms_entry
          endm

check_for_XMS  proc near
          push_   ds,es,ax,bx

          mov     cs:xms_entry,0
          mov     cs:xms_avail,0
          mov     ax,4300h
          INT     2fh
          cmp     al,80h
          jnz     @@ex
          mov     ax,4310h
          INT     2fh
          mov     word ptr cs:xms_entry,bx
          mov     word ptr cs:xms_entry+2,es
          xmscall XMS_GET_VERSION
          cmp     ax,200h
          jb      @@ex0
          xmscall XMS_QUERY_FREE
          or      ax,ax
          jz      @@ex0
          mov     cs:xms_avail,ax
@@ex:
          pop_    ds,es,ax,bx
          cmp     cs:xms_entry,0
          ret
@@ex0:
          mov     cs:xms_entry,0
          jmp     @@ex
check_for_XMS  endp

allocate_xms_block proc near
          push_   bx,dx
          cmp     cs:xms_entry,0
          je      @@ex0
          mov     dx,cs:xms_avail
          xmscall XMS_ALLOC
          or      ax,ax
          je      @@ex0
          mov     cs:xms_handle,dx
@@ex:
          pop_    bx,dx
          or      eax,eax
          ret
@@ex0:
          xor     eax,eax
          jmp     @@ex
allocate_xms_block endp


lock_xms_block proc near
                                      ; try to lock the XMS block
                                      ; (needed to get the phys.address)
          push_   ax,bx,dx
          mov     dx,cs:xms_handle
          xmscall XMS_LOCK_BLOCK
          or      ax,ax
          je      @@err
          mov     word ptr cs:xms_phys_addr,bx
          mov     word ptr cs:xms_phys_addr+2,dx
          pop_    ax,bx,dx
          ret
@@err:
          push    cs
          pop     ds
          lea     dx,@@tx
          call    print
          jmp     exit_to_dos
@@tx      db      13,10,'Cannot lock XMS memory',13,10,'$'
lock_xms_block endp


free_xms_block proc near
          push_   ax,bx,dx
          mov     dx,cs:xms_handle
          xmscall XMS_UNLOCK_BLOCK
          mov     dx, cs:xms_handle
          xmscall XMS_FREE
          pop_    ax,bx,dx
          ret
free_xms_block endp


move_anywhere_xms proc near
; input:
;   ESI= linear source address   (must be in lowmem )
;   EDI= linear target address
;    CX= size to move, must be <= 4096 !!!  (actually _is_ always 4096)
; output:
;    all registers preserved
          cmp     edi,DUMMY_XMS_BLOCK_START
          jb      move_simple
          pushad
          push    ds
          mov     ecx,1000h   ; force a length of one page to move
                              ; NOTE: XMS moves have to be even,
                              ; because we alway move 1000h,
                              ; we simple do it also for the last page
          mov     @@length,ecx
          sub     edi,DUMMY_XMS_BLOCK_START
          mov     @@doffs,edi
          mov     di,xms_handle
          mov     @@dhandle,di
          ror     esi,4
          mov     word ptr @@soffs+2,si
          xor     si,si
          rol     esi,4
          mov     word ptr @@soffs,si
          lea     si,@@length
          push    cs
          pop     ds
          xmscall XMS_MOVE_BLOCK
          pop     ds
          popad
          ret
@@length     dd      ?
@@shandle    dw      0
@@soffs      dd      ?
@@dhandle    dw      ?
@@doffs      dd      ?
move_anywhere_xms endp


;--------------------------------------------------------------------

move_simple  proc near
; input:
;   ESI= linear source address
;   EDI= linear target address
;    CX= size to move, must be <= 4096 !!!
; output:
;    all registers preserved
        pushad
        push_   ds,es
        movzx   ecx,cx
        cld
        ror     edi,4
        mov     es,di
        xor     di,di
        rol     edi,4
        ror     esi,4
        mov     ds,si
        xor     si,si
        rol     esi,4
        ror     ecx,2
        rep movsd
        rol     ecx,2
        rep movsb
@@ex:
        pop_    ds,es
        popad
        ret
move_simple  endp




build_buffer_heap proc near
        pushad
                ; first we setup the normal low heap
                ; (why not use it)
        movzx    eax,kernel_load_frame
        shl      eax,4
        mov      heap_ptr,eax
        movzx    eax,High_Seg       ; NOTE: this is page aligned !
        shl      eax,4
        mov      heap_end,eax
        sub      eax,heap_ptr
        shr      eax,12
        mov      heap_max_pages,eax

                ; now we try to get some extended memory
        cmp     need_mapped_put_buffer,0
        jz      @@err
        cmp     cpu_type,cpu_386V86
        je      @@vcpi
                ; we check what we can use to access extended memory
        call    check_for_XMS
        jz      @@need_int15
        cmp     cs:xms_avail,128
        jb      @@need_int15
        sub     cs:xms_avail,64        ; leave a 64K minimum for system
        call    allocate_xms_block
        jz      @@need_int15
        movzx   eax,cs:xms_avail
        shr     eax,2        ; from 1KB to 4KB
        add     heap_max_pages,eax
        mov     high_heap_ptr,DUMMY_XMS_BLOCK_START
        mov     high_mem_access,USING_XMS
        mov     move_anywhere, offset move_anywhere_xms
@@ex:
        mov      eax,heap_max_pages
        shl      eax,12
        mov      load_buffer_size,eax
        popad
        ret

@@need_int15:
        mov     ax,08800h
        int     15h
        sub     ax,64+64     ; leave HMA untouched, we need 64Kminimum
                             ; (else we would waste more mem than winning)
        jbe     @@err
        movzx   eax,ax
        shr     eax,2        ; from 1KB to 4KB
        add     heap_max_pages,eax
        mov     high_heap_ptr,0110000h
        mov     high_mem_access,USING_INT15
        mov     move_anywhere, offset move_anywhere_int15
        jmp     @@ex
@@err:
        mov     high_mem_access,0
        mov     move_anywhere, offset move_simple
        jmp     @@ex
@@vcpi:
        cmp     have_VCPI,0
        jz      @@ex
        push    edx
        mov     ax,0DE03h   ; get avail 4K VCPI-page
        int     emm_int
        sub     edx,(64/4)      ; leave a 64K minimum for system
        jb      @@err
        add     heap_max_pages,edx
        mov     high_mem_access,USING_VCPI
        mov     move_anywhere, offset move_anywhere_vcpi
        pop     edx
        jmp     @@ex
build_buffer_heap endp


get_buffer_from_heap proc near
; output: EAX = linear address of 4K buffer, page aligned
;               (if XMS, then the offset within the buffer)
;         CARRY =1, if memory overflow
        cmp     heap_max_pages,0
        jna     @@err
        dec     heap_max_pages
        mov     eax,heap_ptr
        cmp     eax,heap_end
        jnb     @@high
        add     heap_ptr,1000h
@@ex1:
        clc
@@ex:
        ret
@@high:
        cmp     high_mem_access,USING_VCPI   ; what heap are we using
        je      @@vcpi
        mov     eax,high_heap_ptr
        add     high_heap_ptr,1000h
        jmp     @@ex1
@@vcpi:
        push    edx
        mov     ax,0DE04h   ; get 4K VCPI-page
        int     emm_int
        mov     eax,edx
        and     ax,0f000h
        pop     edx
        jmp     @@ex1
@@err:
        xor     eax,eax
        stc
        jmp     @@ex
get_buffer_from_heap endp

free_extended_memory proc near
        cmp      need_mapped_put_buffer,0
        jz       @@ex
        cmp      high_mem_access,USING_XMS
        je       @@xms
        cmp      high_mem_access,USING_VCPI
        jne      @@ex
@@vcpi:
        push_    eax,bx,edx
        mov      bx,word ptr pageadjlist.ncount
        shl      bx,2
@@loop:
        sub      bx,4
        jb       @@ex1
        mov      edx,pageadjlist.sources[bx]
        cmp      edx,0100000h
        jb       @@ex1
        mov      ax,0DE05h   ; free 4K VCPI-page
        int      emm_int
        jmp      @@loop
@@ex1:
        pop_     eax,bx,edx
@@ex:
        ret
@@xms:
        call     free_xms_block
        jmp      @@ex
free_extended_memory endp


final_page_adjust_list_handling proc near
        cmp      need_mapped_put_buffer,0
        jz       @@ex
        cmp      high_mem_access,USING_XMS
        jne      @@ex

        push_    eax,bx
        call     lock_xms_block
        mov      bx,word ptr pageadjlist.ncount
        shl      bx,2
@@loop:
        sub      bx,4
        jb       @@ex1
        mov      eax,pageadjlist.sources[bx]
        sub      eax,DUMMY_XMS_BLOCK_START
        jb       @@ex1
        add      eax,xms_phys_addr
        mov      pageadjlist.sources[bx],eax
        jmp      @@loop
@@ex1:
        pop_     eax,bx
@@ex:
        ret
final_page_adjust_list_handling endp



open_new_mapped_block proc near
; input:
;   EDI = linear address of final target address
        cmp      need_mapped_put_buffer,0
        jz       @@ex
        push_    bx,edi
        mov      bx,word ptr pageadjlist.number_of_blocks
        shl      bx,3    ; * sizeof(pblock)
        and      di,0f000h
        mov      pageadjlist.blocks.taddr[bx],edi
        mov      di,word ptr pageadjlist.ncount
        mov      pageadjlist.blocks.tstart[bx],di
        mov      pageadjlist.blocks.tcount[bx],0
        inc      pageadjlist.number_of_blocks
        mov      do_mapped_put_buffer,1    ; notice 'put_buffer' what to do
        pop_     bx,edi
@@ex:
        ret
open_new_mapped_block endp


map_high_page proc near
; input:
;   EDI = linear address of source address
;   because this is called strict sequentially, the target address
;   is allways given by the next free entry in the pageadjlist
        cmp      need_mapped_put_buffer,0
        jz       @@ex
        push_    bx,edi
        mov      bx,word ptr pageadjlist.ncount
        shl      bx,2
        and      di,0f000h
        mov      pageadjlist.sources[bx],edi
        inc      pageadjlist.ncount
        mov      bx,word ptr pageadjlist.number_of_blocks
        dec      bx
        shl      bx,3    ; * sizeof(pblock)
        inc      pageadjlist.blocks.tcount[bx]
        pop_     bx,edi
@@ex:
        ret
map_high_page endp

put_buffer  proc near
; input:
;     AX=  count
;    EDI=  linear destination address (may be > 0100000h)
;  DS:DX=  source address in lowmem
; output:
;   none, all registers preserved
;   in case of memory overflow it doesn't return (jumps to exit_to_dos)
;
        pushad
        push_   ds,es
        movzx   ecx,ax
        cmp     cs:need_mapped_put_buffer,0
        jnz     @@mapped
                ; we can mov it normally
        cld
        ror     edi,4
        mov     es,di
        xor     di,di
        rol     edi,4
        mov     si,dx
        ror     ecx,2
        rep movsd
        rol     ecx,2
        rep movsb
@@ex:
        pop_    ds,es
        popad
        ret
@@mapped:                  ; do high move
        call    get_buffer_from_heap
        jc      @@err
                           ; we don't need EDI any more, because
                           ; if we come here we are writing
                           ; strict sequentialy and have set the
                           ; starting address of the block
                           ; via open_new_mapped_block
        mov     edi,eax
        mov     si,ds
        movzx   esi,si
        shl     esi,4
        movzx   edx,dx
        add     esi,edx
        call    map_high_page
        call    move_anywhere
        jmp     @@ex
@@err:
        pop_    ds,es
        popad
        DosCall  DOS_CLOSE_FILE
        lea     dx,@@tx
        call    print
        jmp     exit_to_dos
@@tx    db      13,10,'Out of memory (may be low or extended)',13,10,'$'
put_buffer  endp

; ---------------------------------------------------------------
; initrd stuff

MAXPHYSMEM_FOR_INT15   = 1000000h

load_initrd proc near
        cmp      cs:option_initrd,0
        jz       @@ex
        pushad
        cmp      need_mapped_put_buffer,0
        jz       @@err_unable
        call     get_effective_physmem
        mov      end_of_physmem,eax
        cmp      high_mem_access,USING_INT15
        jne      @@1
                 ; INT15 on some BIOSes will not access above 16Mb
        cmp      eax,MAXPHYSMEM_FOR_INT15
        jbe      @@1
        mov      eax,MAXPHYSMEM_FOR_INT15
@@1:
        mov      @@end_of_physmem,eax
        mov      ax,DOS_OPEN_FILE shl 8
        lea      dx,rdimage_name
        DosInt
        jc       @@err_open
        mov      fhandle,ax
        mov      bx,ax
        call     get_filesize
        mov      ramdisk_size,eax
        neg      eax
        add      eax,@@end_of_physmem
        and      ax,0f000h     ; round down to full page boundary
        mov      ramdisk_image,eax
        movzx    eax,kernel_size
        add      ax,0ffh
        mov      al,0          ; round up to page boundary
        shl      eax,4         ; size of compressed kernel
        mov      ecx,eax
        shl      eax,1         ; estimated size of decompressed kernel
                               ; (we assume a decompression rate of 1:2)
        sub      ecx,(90000h - 2000h) ; the low buffer size (for bzimages)
        jb       @@3
        add      eax,ecx       ; add padding
@@3:
        add      eax,(100000h+2000h)   ; (+ gunzip-heap), now we have end of kernel
        cmp      eax,ramdisk_image
        jnb      @@err_mem
                 ; ok we have place
                            ; now loading the kernel
                         ; NOTE: needing EDI for open_new_mapped_block
        mov      edi,ramdisk_image
        call     open_new_mapped_block      ; open the first block
        mov      bx,fhandle
        mov      ecx,ramdisk_size
        mov      print_dots,3
        call     read_handle   ; read the ramdisk
        call     print_crlf
        mov      print_dots,0
        call     print_crlf
        jc       @@err_io
        cmp      eax,ecx
        jnz      @@err_io
                           ; ok, all is read into memory
        DosCall  DOS_CLOSE_FILE
        popad
@@ex:
        ret
@@err:
        popad
        mov     dx,@@errtx_addr
        call    print
        jmp     exit_to_dos
@@err_open:
        mov     @@errtx_addr,offset @@tx
        jmp     @@err
@@err_mem:
        mov     @@errtx_addr,offset @@txmem
        jmp     @@err
@@err_io:
        mov     @@errtx_addr,offset @@txio
        jmp     @@err
@@err_unable:
        mov     @@errtx_addr,offset @@txno
        jmp     @@err
@@end_of_physmem dd  0
@@tx    db      13,10,"can't open image file for initrd",13,10,'$'
@@txmem db      13,10,"no place after kernel for initrd",13,10,'$'
@@txio  db      13,10,"IO-error while reading initrd",13,10,'$'
@@txno  db      13,10,"no support in setup for reading initrd",13,10,'$'
@@errtx_addr dw      0
load_initrd endp

get_filesize proc near
; input:
;   bx = filehandle
; output:
;   eax = filesize, all other registers reserved
        push_   cx,dx,esi
        mov     ax,4201h    ; seek to current position
        xor     cx,cx
        xor     dx,dx
        DosInt              ; ... and return current position in DX:AX
        push_   ax,dx       ; save it
        mov     ax,4202h    ; seek to last position
        xor     cx,cx
        xor     dx,dx
        DosInt              ; ... and return EOF position in DX:AX
        mov     si,dx
        shl     esi,16
        mov     si,ax
        pop_    dx,cx
        mov     ax,4200h    ; seek to saved position
        DosInt
        mov     eax,esi
        pop_    cx,dx,esi
        ret
get_filesize endp

