; BIOS-based Loader for the 32-bit MOOSE API.
; Copyright (c) François-René "Faré" Bân Rideau Dang-Vu 1994

; Please Read the info/BIOS-API.nfo file

;%OUT Assembling the LOADER ; Yep

Ideal	; I can't bear Microsoftish things.
%NOCTLS
%NOLIST
%NOINCL
%MACS	; to see errors inside macros ; NOMACS is better when no errors :-|

; The LOADER will Load the API and jump to it.

; * First test the CPU Type. Do not load if not a 386 or better.
; * The LOADER detects if the API is in a DOS system file (à la MS-DOS), in
;  reserved sectors (the simplest trick), in extra sectors (as an invisible
;  Virus OS 8), or with its sectors mapped (à la LILO).
; * In case of failure, return with an error message and a "hit any key"
; * Get extended memory size from The BIOS.



; * Test if using a Linux Loading scheme by testing the entry point to be
;  (9020:0) instead of xxxx:100h.
; * Load the API Header in main memory.
; * Verify that there's enough main memory for it.
; * Load the API body in main memory.
; * Load the Kernel Header
; * Load the Kernel into extended memory (320 KB extended memory required)
; * Initialize the API fields; jump into the API.

; To Do:
; * Beware the current 28 KB limit for the API
; * Add the DOS loader ?
; * Have one different loader for each mode (H,C,D,M)
; * Add compression/decompression of the API & Kernel by the loader.
;  LHA has its self-decompressor fit in 1.5 KB, so we can do it too.

P386

__NO_USE_INT_13H__=0

Include "../../macro.inc"
Include "../../mem386.inc"
Include "../../../M_Magic.h"	; magic numbers
Include "../API.inc"		; API header structure

DEBUG=1		; debug messages
__DEBUG__=1	; heavy debug
CLEARSCREEN=1	; clear it before loading
MESSAGES=1	; Do print messages

; This really could have been written in C, if C could have produced small
; code that would have used only the BIOS.
%LIST

;;; Defines that may change.

; We mustn't really use these. The Boot Record need it, though.
LOADER_SEG_SIZE	 =  0F00h	;(60KB) ; size in para for the loader segment.
LOADER_STACK_TOP =  LOADER_SEG_SIZE*16	; SP in the same segment
STACKSIZE	 =  8192		; stack size when using LILO
LOADER_SECTS	 =  3			; size of the loader in sectors.
LOADER_OFFSET	 =  100h		; load the loader as a .COM file.
LOADER_UDATA	 =  0			; no udata area, as 80h-100h is used.
KERNEL_SECTS	 =  512			; Size of Kernel+API to load.
MIN_KERNEL_SPACE =  300			; KB needed by the kernel
MAX_KERNEL_SPACE =  16000		; KB asked by the kernel
MAX_TRY		 =  10		        ; number of retries before aborting I/O

;;; Defines that won't change
LOG_SECT_SIZE	=	9		; Size of sectors (low-level standard)
SECT_SIZE	=   1 shl LOG_SECT_SIZE	; 512
MAP_FILE	 =  LOADER_OFFSET+SECT_SIZE*LOADER_SECTS+LOADER_UDATA
			; just after the loader

; LOAD MODEs:
; * 'D' for loading a DOS file as the API+Kernel. LoadArg points to the
;  name (11 bytes) of the system file containing the API and the Kernel
; * 'H' for using an overformatted floppy's hidden sectors as the API+Kernel.
;  LoadArg contains real max tracks (hi) and real max sects/tracks (lo).
; * 'C' for using contiguous directories after the current one. LoadArg is
;  The number of the first sector (relative to hidden_sectors).
; * 'M' for LILO-like mapping. The map file is already loaded at address
; * 'L' for just moving a LILO (or LOADLIN) loaded kernel
;  MAP_FILE

model tiny
;; BUG in TASM 2.51: UDATA doesn't work :-(
;; masm;.data?;ideal doesn't work either: puts any data with a 32 bit address.
;; AAARRRGGGHHH
;; I'll use dataseg
Macro udata
  udataseg
  ;; If I could safely hook dataseg or other data segment declarations,
  ;; I'd save the old pointer here, and restore the udata pointer.
EndM udata

;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Code first ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
codeseg
		org	LOADER_OFFSET	; .COM file format
Entry_Point:
		;jmp	near Init	; Init just follows.

;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Loader DATA ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
dataseg

;udata	;; not only udata doesn't work, but I allocate data myself in the
	;;command line, thus reducing the disk size & memory size
ConvMem		equ	dword 80h	; Total Conventional memory in bytes
AvConvMem	equ	dword 84h	; Available Conv. mem in bytes
XtdMem		equ	dword 88h	; Total Extended memory in bytes
AvXtdMem	equ	dword 8Ch	; Available Extended mem in bytes

BaseConvMem	dd	1000h  ;4096	; Page aligned, reserve 2K for BIOS.
BaseXtdMem	dd	110000h	;1MB+64KB	; Page aligned, reserve low
					; memory & HMA for dos emulator.

;;; Inits data for Next_cluster
Init_Sector_Read  equ	word 90h

;;; Returns the next block of sector to read in int 13h -able registers.
;;; al=0 if no more clusters.
Next_cluster	equ	word 92h


;;; we assume we could fill the user-defined buffer
;;; if not, then bufferize those routines with an overfillable
;;; buffer (overfilling: up to the maximal value given by Next_cluster).
over_fill	equ	dword 94h
over_fill_ofs	equ	 word 94h
over_fill_seg	equ	 word 96h

over_fill_len	dw	0

;;; We also assume no more than 64 KB overfilled buffer, no 64 KB boundary
;;; crossed, no pointer wraps, new value of buffer not interfering with old
;;; overfull buffer. (that's all :)

;;; Load Mode 'M'
pCluster	equ	word 98h

;;; Load Mode 'H' ; for fdformatted floppies only
;;; (can anyone achieve over-formatted hd's ?)
CurrentTrack	equ	word 9Ah	; hi: track, lo: head
Max_Tracks_on	equ	byte 9Ch	; max. number of "official" tracks

;;; Load Mode 'C'
CurrentSector	equ	dword 0A0h
MaxSector	equ	dword 0A4h

;;; Debugging LOADER
Int13Handler	equ	dword 0ACh

;;; Load Mode 'D'
DataArea	equ	word 0B0h
CurrentCluster	equ	word 0B2h
SectorInCluster	equ	word 0B4h
FAT_Reader	equ	word 0B6h	; routine to read 12 or 16 bit FAT
FAT_Mapped_Beg	equ	word 0B8h	; for 16 bit FATs, which part is mapped
FAT_Mapped_Len	equ	word 0BAh

;;; Load Mode 'L'
; Nothing more is needed !

CPUType 	equ	word 0C0h	; AH:CPU, AL:NPU (see mem386.inc)
ErrorCode	equ	byte 0C1h	; must be all on the same disk.



;;; API Header information	; see APIHeader.asm
API_Signature	equ	(Signature_Struc  es:1000h)
API_Header	equ	(API_Header_Struc es:1020h)
API_Magic	equ	(dword API_Signature.Magic_Number)
API_Length	equ	(dword API_Header.TotalLength)
API_EntryPoint	equ	(dword API_Header.EntryPoint)

;;; LILO style loading
INITSEG		=	9000h
SYSSEG		=	1000h
SETUPSEG	=	9020h
SYSSIZE		=	7F00h
Lxsig_1		=	0AA55h
Lxsig_2		=	05A5Ah

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ARGUMENTS ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; see bootrec.asm for full details...

;;; DOS info table

bytes_per_sect	equ	word 0Bh
sectors_per_clu	equ	byte 0Dh
reserved_sect	equ	word 0Eh
num_FATs	equ	byte 10h
entries_rootdir	equ	word 11h
total_num_sect	equ	word 13h
media_descr	equ	byte 15h
sect_per_FAT	equ	word 16h
sect_per_track	equ	word 18h
num_heads	equ	word 1Ah
hidden_sect	equ	dword 1Ch
d_total_n_sect	equ	dword 20h

;;; MOOSE Loading Parameters

LoadMode	equ	byte 24h
LoadArg		equ	word 25h
BIOS_DL		equ	byte 27h
Clusters	equ	dword 28h

;;; Kernel Header

Kernel_Magic	equ	dword es:0h
Kernel_Length	equ	dword es:20h


;;;;;;;;;;;;;;;;;;;;;;;;; Initialization Routines ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
codeseg
;;;; These procedures are executed only once, so I macro'ed them.

Macro	InitConfused
	STOP fs:,38,14,'C',0Ah
	call I_Confd
   I_Confd:
	STOP fs:,39,14,'o',0Ah
	mov bx,cs
	pop dx	; return address
	ldea cx,I_Confd
	sub cx,dx
	sar cx,4
	sub bx,cx
	mov ds,bx
	@_@
	STOP fs:,40,14,'n',0Ah
	lsa si,<"I dunno where I am !",CRLF>
	STOP fs:,41,14,'f',0Ah
	@_@
Exit2:
	jmp Exit
EndM	InitConfused

Macro LILO_test
	STOP fs:,39,13,'O',0Fh
	@IF nz
	  call L_t
	 AfterJump:
         Init_Confused:
	   InitConfused
         L_t:
	  pop ax			; Get Return Address
	  cmp ax,ofs AfterJump
	@THEN
	  mov bx,cs
	  cmp bx,SETUPSEG
	  jnz Init_Confused
	  cmp ax,ofs AfterJump-100h
	  jnz Init_Confused
	  db 0eah	; far jump
	  dw ofs LILOused,SETUPSEG-10h
        LILOused:
	;;; Init registers for LILO
 	  STOP fs:,37,13,'L',09h
	  mov ax,cs
	  mov ds,ax
	  mov es,ax
	  mov ss,ax
	  ldea sp,BufferBeg+STACKSIZE
	  @_@					; strings not loaded yet.
 	  STOP fs:,37,14,'I',09h
	;;; Load what remains of the loader (as under linux)
	  End_LILOs_work
	  @_@ <"Readjusted from LILO:",CRLF>,SST ; strings not loaded yet.
	;;; Init parameters for LILO
  	  STOP fs:,37,15,'L',09h
	  mov [over_fill_seg],bx
	  mov [LoadMode],'L'
	  mov [byte 80h],99h
	  ;; one day, add support for Linuxish command-line arguments...
 	  STOP fs:,37,16,'O',09h
	@ENDIF
EndM LILO_test

Macro	End_LILOs_work		; stolen from linux/boot/setup.S
; POSTcondition:
; * the Loader is fully placed in memory
; * bx contains the beginning of the system loaded by LILO
; Check signature at end of setup
	P186
	STOP fs:,1,5,'S',09h
	push	ds
	mov	bx,SYSSEG
	cmp	[setup_sig1],Lxsig_1
	jne	bad_sig
	cmp	[setup_sig2],Lxsig_2
	jne	bad_sig
	jmps	good_sig

; We now have to find the rest of the setup code/data
bad_sig:
	STOP fs:,2,5,'B',09h
	mov	ax,INITSEG
	mov	es,ax
	xor	dh,dh
	mov	dl,[es:497]	; get setup sects from boot sector
	sub	dx,4		; LILO loads 4 sectors of setup
	shl	dx,8		; convert to words
	mov	cx,dx
	shr	dx,3		; convert to segment
	add	bx,dx

; Move rest of setup code/data to here
	STOP fs:,3,5,'M',09h
	mov	di,2048		; four sectors loaded by LILO
	clr	si
	mov	ax,SETUPSEG
	mov	es,ax
	mov	ax,SYSSEG
	mov	ds,ax
	;@_@
	rep	movsw

; restore ds
	STOP fs:,4,5,'R',09h

; that's all, folks
good_sig:
	STOP fs:,5,5,'G',09h
	pop	ds
	STOP fs:,6,5,'D',09h

EndM	End_LILOs_work

Macro	Init_LOADER
;;; Test CPU & NPU type
  def_proc_TestCPU inline
  mov [CPUType],ax
  @IF b
    cmp ah,3
  @THEN
    lsa si,<'MOOSE currently needs a 32 bit (386+) processor'>
    jmp near Exit
  @ENDIF
 IF __NO_USE_INT_13H__
    push es
    push 0		; Argl ! How to tell to use long or word ???
    pop es
    mov eax,[es:13h*4]
    mov [Int13Handler],eax
    pop es
  ENDIF
EndM	Init_LOADER

Macro	Parse_Options
;;; Verify that DOS aint loaded
   STOP fs:,40,13,'O',0Fh
   @IF nz
	cmp [byte 80h],99h	; The bootrec ought to have put this value
				; 99h >=80h, thus COMMAND.COM won't put it.
   @THEN
      lsa si,<"From DOS, use LOADLIN !",CRLF>
      jmp Puts
   @ENDIF
   mov al,[LoadMode]
   @_@
   ldea bx,D_Init_disk
   ldea dx,D_Next_cluster
   cmp al,'D'
   jz Parsed
   ldea bx,M_Init_disk
   ldea dx,M_Next_cluster
   cmp al,'M'
   jz Parsed
   ldea bx,C_Init_disk
   ldea dx,C_Next_cluster
   cmp al,'C'
   jz Parsed
   ldea bx,H_Init_disk
   ldea dx,H_Next_cluster
   cmp al,'H'
   jz Parsed
   ldea bx,L_Init_disk
   ldea dx,L_Next_cluster
   cmp al,'L'
   jz Parsed
 Load_Mode_Unsupported:
   dataseg
     Mode_not_supported	db "Load Mode "
     Unsupported_Mode	db 'X'," not supported."
     db_zero		db 0
   codeseg
   mov [Unsupported_Mode],al
   ldea si,Mode_not_supported
   jmp near Exit
 Parsed:
   mov [Init_Sector_Read],bx
   mov [Next_cluster],dx
EndM	Parse_Options

Macro	Init_Message	near
	dataseg
	  sLoading	db "Loading using Mode "
	  Loading_Mode	db 'X',"..",0
	codeseg
	mov al,[LoadMode]
	mov [Loading_Mode],al
	ldea si,sLoading
	PUTS
EndM	Init_Message

Macro	Get_Memory
   STOP fs:,41,13,'S',0Fh
  ;;; Get Total extended memory
	mov ah,88h
	int 15h		; result in KB
	movzx eax,ax
	shl eax,10
	mov [XtdMem],eax; result in bytes
  ;;; Compute available x. mem.
	sub eax,65536	; Don't put anything in the HMA zone
	mov [AvXtdMem],eax
  ;;; Get Total conventional memory
	int 12h		; result in KB
	movzx eax,ax
	shl eax,10
	mov [ConvMem],eax
IF 0
  ;;; Compute Available conventional memory
    ;- assuming the boot record loaded us as high as possible) is *not*
    ; possible, since LILO could have done otherwise
	shr eax,4
	sub ax,1000h	; 64KB
	push ds
	mov ds,ax
	mov ebx,0FFFCh
	cmp [dword bx],MAGIC_MARKER
	jnz AvConvMemFound
	dec bx
	dec bx
     Run_Res_ConvMem:
	mov cx,[bx]
	jcxz AvConvMemFound
	sub bx,cx
	jmp Run_Res_ConvMem
     AvConvMemFound:
	shl eax,4
	add eax,ebx
	pop ds
	mov [AvConvMem],eax
ENDIF
EndM	Get_Memory

Macro	Init_Disk_Read
   ;;; Init sector count
	call [Init_Sector_Read]
EndM	Init_Disk_Read

Macro	LOAD_API
   ; Note: for the moment, the API should not be larger than 60 KB,
   ;  and even 28 KB to avoid a 64KB DMA limit with sector prefetching !
   ;  Else a BIOS wrapper will have to buffer sector reading somewhere
   ;  (the 2KB between 00800h and 01000h ?) when some wrapping is detected.

   STOP fs:,42,13,'E',0Fh
   ;;; Init The buffer
	clr ax
	mov es,ax	; 4KB
	mov bx,1000h
   ;;; Load the Header
	mov al,64	; Header length ((!) ah=0 already)
	call DoReadOnce
   ;;; Get the length from the header
	@IF nz
	  cmp [dword es:bx],MAGIC_PC_API ; API_Magic
	@THEN
	  @dwo@ <"Magic number ">,<[dword es:bx]>,<CRLF>
	  @_@
	  lsa si,<"API file fails magic number test.",CRLF>
	  jmps Exit3
	@ENDIF
	mov eax,[API_Length]
	@IF a
	  cmp eax,0F000h ; we cannot it if bigger than 60K.
	@THEN
	  lsa si,<"API Too big",CRLF>
	Exit3:
	 IF DEBUG
	  jmp near Exit
	 ELSE
	  jmp short Exit
	 ENDIF
	@ENDIF

   ;;; Load the entire API.
	add bx,64
	sub ax,64
	call DoReadOnce
   ;;; ... ToDo:
   ;;; - add relocation (?)/ header parsing
   ;;; - add decompression (?)

EndM	LOAD_API

Macro	LUKI_GO_API
   ;;; Jump into the API.
	PUTS <".",CRLF>
	clr bx
	push bx
	mov es,bx
	mov ds,bx
	push [word API_EntryPoint]
	; (!) we should test that it is inferior to the API size !
	IF DEBUG
	  @_@
	  push ds
	  pusha
	  push cs
	  pop ds
	  @Zone@ <"API Entry:">,es,<[word API_EntryPoint]>,16,<>
	  GETC <CRLF,"Hit any key to go MOOSE !",CRLF>
	  popa
	  pop ds
	ENDIF
	retf
EndM	LUKI_GO_API

Macro	Do_Exit
	call PrCRLF
	PUTS
	PUTS	<CRLF,"Hit any key to reboot">
	GETC
EndM	Do_Exit

Proc	Init	near
P8086

IF __DEBUG__
	P386
	push 0B800h
	pop fs
	STOP fs:,38,13,'M',0Fh
ENDIF

IF CLEARSCREEN
	mov	ax,0003h	; 80x25, 16 colors
	int	10h
ENDIF

 ;__ <"Testing if using Linux-ish loading",CRLF> ; Won't work if using LILO !
	LILO_test

IF DEBUG
  P186	; for __ 's pusha/popa
ENDIF
 __ <"Testing @_@",CRLF>
	@_@

 __ <"Initing self",CRLF>
	Init_LOADER
	@wo@ <"CPU:">,[CPUType],<CRLF>

P386

IF 0
 __ <"Testing Int 13h",CRLF>
	push 0
	pop es
	@addr@ <" at ">,<[word es:13h*4+2]>,<[word es:13h*4]>,<CRLF>
	mov ah,0
	mov dl,0
	Int13h
	@_@
ENDIF

 __ <"Looking at options",CRLF>
	Parse_Options

 __ <"Hello, World !",CRLF>
	Init_Message

 __ <"Seeing how much memory is left",CRLF>
	Get_Memory
        @dwo@ <"Found 0x">,[ConvMem],<" bytes of conventional memory,",CRLF>
        @dwo@ <"and 0x">,[XtdMem],<" bytes of extended memory",CRLF>

 __ <"Initialize disk reading",CRLF>
	Init_Disk_Read

 __ <"Load the API at 4KB",CRLF>
	LOAD_API
	@dwo@ <"Done loading a 0x">,[API_Length],<" long API",CRLF>

 __ <"Jumping into the API",CRLF>
	LUKI_GO_API

Exit:
 __ <"Aborting...",CRLF>
	Do_Exit			; On error, exit
	ret
EndP	Init

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Console I/O ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
codeseg

IFE MESSAGES

Putc	EQU	Return
Puts	EQU	Return
PrCRLF	EQU	Return
cGetc	EQU	Return
Return:	ret

ELSE

Putc	EQU	cPutc
Getc	EQU	cGetc

; Adapted from MicroSoft's own boot sector
; DS:SI=ASCIIZ string to print
; AX spoilt, SI points just after the string-ending zero, 7 put into BX.
Proc	Puts	near
	;mov	al,[cs:si]
	;inc	si
	cld
	lodsb
	or	al,al
	jz	Return
	PUTC
	jmps	Puts
EndP	Puts

IF 0
Proc	Beep
; Echo a BEEP to the the Console
	mov	al,7
;	jmps	cPutc
EndP	Beep
ENDIF

; Outputs a character to console
; spoils AX and DI, keeps si
Proc	cPutc
	Pushm	bx,si,di,es	; no pusha: may be used on 8088 !!!
	mov	ah,0Eh	; function Teletype
	mov	bx,7	; attribute
	int	10h	; Video BIOS call
	Popm	bx,si,di,es
Return:
	retn
EndP	cPutc

IF DEBUG
;%%OUT Watch out: @__
;@__

dataseg
  RegsMsg	db "DI   SI   BP   SP   "
		db "BX   DX   CX   AX   "
		db "FLGS CS   DS   ES   "
		db "SS   IP   STK1 STK2"
		db CRLF,0
  ERegsMsg1	db "EDI      ESI      EBP      ESP      "
		db "EBX      EDX      ECX      EAX      EFLAGS",CRLF,0
  ERegsMsg2	db "CS   DS   ES   SS   IP   STK1 STK2 STK3 "
		db "STK4 STK5 STK6 STK7 STK8 STK9 STKA STKB"
		db CRLF,0
codeseg

Proc	DumpERegs
	push ss
	push es
	push ds
	push cs
	pushfd
	pushad
	push ss
	pop es
	push cs
	pop ds
	ldea si,ERegsMsg1
	PUTS
	mov di,sp
	mov cx,9
	call PutLZone
	ldea si,ERegsMsg2
	PUTS
	mov cx,16
	call PutWZone
	popad
	popfd
	pop es	;no pop cs above 8086
	pop ds
	pop es
	pop ss
 Return2:
	ret
EndP	DumpERegs

Proc	DumpRegs
	push ss
	push es
	push ds
	push cs
	pushf
	pusha
	push ss
	pop es
	push cs
	pop ds
	ldea si,RegsMsg
	PUTS
	mov di,sp
	mov cx,16
	call PutWZone
	call PrCRLF
	popa
	popf
	pop es	;no pop cs above 8086
	pop ds
	pop es
	pop ss
	ret
EndP	DumpRegs

Proc	PutLZone_
	call	Space
PutLZone:
	mov eax,[es:di]
	add di,4
	call PutHexDword
	loop PutLZone_
	ret
EndP	PutLZone_

Proc	PutWZone_
	call	Space
PutWZone:
	mov ax,[es:di]
	inc di
	inc di
	call	PutHexWord
	loop PutWZone_
	ret
EndP	PutWZone_
Proc	PutZone_
	call	Space
PutZone:
	mov al,[es:di]
	inc di
	call	PutHexByte
	jcxz	short Return ;2
	call	Space
	loop PutZone_
	ret
EndP	PutZone_

Proc	PutAddr
;; Prints the contents of es:di
	push	di
	mov	ax,es
	call	PutHexWord
	PUTC	':'
	pop	ax
	jmps	PutHexWord
EndP	PutAddr

Proc	PutHexDword
	rol	eax,16
	call	PutHexWord
	rol	eax,16
;	jmps	PutHexWord
EndP	PutHexDword

PutHexWord:
; Prints the contents of AX in hexa.
; everything but F saved
		xchg	al,ah
		call	PutHexByte
		xchg	al,ah
;		jmp	PutHexByte

ENDIF
;__@
;%%OUT __@ reached

; Prints the contents of AL in hexa.
; saves AX.
Proc	PutHexByte
	push	cx
	mov	cl,4
	rol	al,cl
	call	PutHexNybble
	rol	al,cl
	pop	cx
;	jmp	PutHexNybble
EndP	PutHexByte

; Prints to BIOS console the hexa nybble in low AL
; saves AX, puts 7 into BX
Proc	PutHexNybble
	push	ax
	and	al,0Fh
	add	al,'0'
	cmp	al,0Ah+'0'
	jb	do_putnyb
	add	al,'A'-'0'-0Ah
 do_putnyb:	
	PUTC
	pop	ax
	retn
EndP	PutHexNybble

Proc	cGetc
	clr	ah
	int	16h
	retn
EndP	cGetc

IF 0
Proc	sPutc
	stosb
	retn
EndP	sPutc
Proc	sGetc
	lodsb
	retn
EnP	sGetc
ENDIF

Proc	PrCRLF	near
	PUTC	13
	mov	al,10
	jmp	Putc
EndP	PrCRLF

IF DEBUG ;@__
Proc	Space
	mov al,' '
	jmp Putc
EndP	Space
ENDIF;__@

ENDIF	; MESSAGES

;;;;;;;;;;;;;;;;;;;;;;;;;; Misc. Subroutines ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

codeseg

Macro	_@_ m,s
   IF DEBUG
	push ds
	push ss
	pop ds
	pusha
	PUTS <m>
	popa
	pop ds
	@_@ <>,s
   ENDIF
EndM	_@_

Proc	safe_movsb
	jcxz	Return_0
safe_movsb_:
; compare source & destination
	_@_ <"safe_movsb:",CRLF>
	push esi
	push edi
	push eax
	push ebx
	clr eax
	clr ebx
	mov ax,ds
	mov bx,es
	shl eax,4
	shl ebx,4
	movzx esi,si
	movzx edi,di
	add esi,eax
	add edi,ebx
	cmp edi,esi
	pop ebx
	pop eax
	pop edi
	pop esi
	ja sm_dest_gt_source
;sm_source_gt_dest:
	_@_ <"source gt dest:",CRLF>		; I couldn't write "s>d" ):
	cld
	push	cx
	@IF ncxz
	  shr	cx,2
	@THEN
	  rep	movsd
	@ENDIF
	pop	cx
	@IF ncxz
	and	cx,3
	@THEN
	  rep	movsb
	@ENDIF
Return_0:
	ret
; assuming dest > source
 sm_dest_gt_source:
	_@_ <"source < dest:",CRLF>
	add	si,cx
	add	di,cx
	sub	si,4
	sub	di,4
	std
	push	cx
	@IF ncxz
	  shr	cx,2
	@THEN
	  rep	movsd
	@ENDIF
	pop	cx
	add	si,3
	add	di,3
	@IF ncxz
	  and	cx,3
	@THEN
	  rep	movsb
	@ENDIF
	ret
EndP	safe_movsb

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; DISK PROCESSING ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
IF __NO_USE_INT_13H__
  Macro Int13h
    __ <"Int 13h called:",CRLF>
    @_@
    pushf
    push cs
    call Do_Int13h
    __ <"Int 13h returned:",CRLF>
    @_@
  EndM
  Proc Do_Int13h
    cli
    jmp [Int13Handler]
  EndP
ELSE
  Macro Int13h
    __ <"Int 13h called:",CRLF>
    @_@
    Int 13h
    __ <"Int 13h returned:",CRLF>
    @_@
  EndM
ENDIF


;;;;;;;;;;;;;;;;;;;;;;;;;;;; General Subroutines ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

Proc	SectorNumToBIOS_Args
; In: eax (0 for first sector)
; Out: BIOS sector identifier (cl,dh,ch)

	;__ <"SectorNumToBIOS_Args Input:",CRLF>
	;@__@

	clr edx
	movzx ebx,[byte sect_per_track]
	div ebx
	mov cl,dl	; remaining: sector
	inc cl		; they are from one on
	movzx ebx,[byte num_heads]
	clr edx
	div ebx
	mov dh,dl	; remaining: head
	mov ch,al
	shl ah,6
	or cl,ah

	;__ <"SectorNumToBIOS_Args Output:",CRLF>
	;@__@
	ret
EndP	SectorNumToBIOS_Args

;Proc	BIOSToSectorNum
; In: BIOS sector identifier
; Out: eax
;	dec cl			; sectors begin with 1
;	movzx ebx,cl		; ebx := sector number
;	and bl,3Fh
;	shr cl,6		; ax := cylinder number
;	mov al,ch
;	mov ah,cl
;	movzx ecx,dh		; ecx := head number
;	mul [byte num_heads]	; dx,ax := cylinder number * heads
;	push dx			; eax := dx,ax
;	push ax
;	pop eax
;	add eax,ecx
;	movzx edx,[byte sect_per_track]
;	mul edx
;	add eax,ebx
;	ret
;EndP	BIOSToSectorNum

Proc	Read_Sectors_With_Retries
;; ax,bx,cd,dx registers unchanged.
;; si (?), di spoilt
;; CF set if error (then see [ErrorCode])
 ;__ <"Reading Sectors",CRLF>
 ;@_@
   mov di,MAX_TRY	; maximum number of retries
   mov ah,02h	; Read
  Read_Try:
   push di
   push ax
   Int13h
   mov [ErrorCode],ah
   pop ax
   jnc Read_ok
   push ax
   clr ah		; reset controller
   Int13h
   pop ax
   pop di
   dec di
   jnz Read_Try
 ;Too_Many_Retries:
   stc
   ;@by@ <"Disk I/O Error 0x">,[ErrorCode],<CRLF>
   ret
 Read_ok:
   pop di
   ret
EndP	Read_Sectors_With_Retries

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Global Routines ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Proc	Read_Once
; Input:
; * (Min) Length to read in ax
; * buffer in es:bx
; Output:
; * buffer (overwritten)
; * if buffer changed place, data moved, assuming no interference
; * ax: preserved
; * es:bx unchanged
; * cx: data actually read (overfull if > ax; end of file if < ax)
; * si,di,dx spoilt ; bp, ds preserved
; *** NOTE: if you use data over bx+ax, you must set [over_fill_len] to
; ***  zero, or set it to another good value, and modify [over_fill_ofs]
; * other registers spoilt

 ;;; That's Structured programming in assembly !
 __ <"Read Once Input: ",CRLF>
 @wo@ <"asked 0x">,ax,<" bytes at ">
 @addr@ <>,es,bx,<CRLF>

 ;;; load old buffer address
   mov si,[over_fill_ofs]
   mov dx,[over_fill_seg]
   mov cx,[over_fill_len]
 @wo@ <"Already were 0x">,cx,<" bytes at ">
 @wo@ <>,dx,<":">
 @wo@ <>,si,<CRLF>

   jcxz RO_Buffer_ok
 ;;; compare to new one
   @IF nz	; If new buffer different
     mov di,es
     cmp di,dx
    ;@OR nz ; not supported yet
     jnz RO_new_buf_dif
     cmp si,bx
   @THEN	; Then move old to new
    RO_new_buf_dif:
     push ds
     push cx
     mov ds,dx
     mov di,bx
     @_@
     call safe_movsb ; es:bx,ax unchanged
     pop cx
     pop ds
   @ENDIF

 RO_Buffer_ok:
 __ <"Buffer now ok",CRLF>
   push bx
   @WHILE b	; While buffer strictly not overfull enough
     cmp cx,ax
   @DO		; Do fill it again
     push ax
     push cx
     push bx		; bx: buffer address
     call [Next_cluster]; al,cx,dx: next cluster to read.
     pop bx
     or al,al		; al: number of sectors to read
     jz End_Of_File
     push ax
     call Read_Sectors_With_Retries
     pop dx		; now, it's dl
     pop cx
     pop ax
     jc Disk_IO_Error
     __ <"Cluster Read",CRLF>
     @_@
     shl dx,LOG_SECT_SIZE ; (!) wrong if LOG_ < 8
     add cx,dx
     add bx,dx
   @WEND
 ;; full enough, then give the caller what he asked
   pop bx ; original buffer
   ;pop ax ; original length asked for
   mov dx,cx ; length actually read.
   sub dx,ax
   mov [over_fill_len],dx
   @wo@ <"Overfill length now 0x">,dx,<>
   mov dx,bx
   add dx,ax
   mov [over_fill_ofs],dx
   mov [over_fill_seg],es
   @addr@ <" at ">,es,dx,<CRLF>
   __ <"Done Reading once",CRLF>
   @_@
   ret

 End_Of_File:
   __ <"End of file reached",CRLF>
   @_@
   pop cx ; total unsufficient length of data read
   pop ax ; length asked
   pop bx ; old buffer value
   mov dx,bx
   add dx,cx
   mov [over_fill_seg],es
   mov [over_fill_ofs],dx
   mov [over_fill_len],0
   ret

;;; On Errors,
 Disk_IO_Error:
   @PrByte <CRLF,"Error 0x">,[ErrorCode],<CRLF>
   ldea si,db_zero
   pop bx ; original bx
   pop ax ; return address to Main
   jmp Exit
EndP	Read_Once

Proc DoReadOnce
; calls Read_Once with abortion in case of end-of-file reached.
; See Read_Once just above for specifications.
   call Read_Once
   cmp cx,ax
   jb Premature_end_of_file
   ret
 Premature_end_of_file:
   lsa si,<"Unexpected end of file",CRLF>
   pop ax; return address
   jmp near Exit
EndP DoReadOnce

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Load Mode 'M' ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

Proc M_Init_disk near
    mov [pCluster],MAP_FILE
    ret
EndP M_Init_disk

Proc M_Next_cluster near
	push si
	mov si,[pCluster]
	lodsw
	mov cx,ax
	lodsw
	mov dh,ah
	@IF nz
	  or al,al
	@THEN
	  mov [pCluster],si
	@ENDIF
	pop si
	mov dl,[BIOS_DL]
	ret
;Entry format:
;BIOS_CX	dw	0002h		; CH:cyl&0xFF, CL:sect+((cyl>>8)<<6)
;BIOS_AL	db	02h		; Int 13h parameters to load the LOADER
;BIOS_DH	db	00h		; head
EndP M_Next_cluster

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Load Mode 'D' ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

Proc D_Init_disk near
; Unsupported yet
	mov al,'D'
	jmp Load_Mode_Unsupported
; Look in the root directory, search for the system file, see length and
; cluster.

; ...

EndP D_Init_disk

Proc D_Next_cluster near

; memorized part of cluster number is next sectors to load.
; while we have a contiguous (without even track limit) zone of sectors on
; disk, look in the FAT for next cluster
; When we reach a limit (or when finished), load the contiguous part.
; memorize next part of cluster to load

; Look in the FAT for the cluster number
; if 12 bit FAT, all the FAT fits in buffer
; if 16 bit FAT, have bits of it; happily a single entry won't be split
; on two sectors.

; ...
EndP D_Next_cluster

;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Load Mode 'H' ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;; The LoadParam is used as sect/trk (low) & #cyl (hi).

Proc H_Init_disk near
; Leave track 0 head 0 for the boot sector + fake boot sector + loader
; floppies have less than 255 tracks and 2 heads, (thus) less than 65535 sects.
   mov [word CurrentTrack],0100h	; already read Cyl 0 (lo), Hd 1 (hi)
   mov ax,[total_num_sect]
   shr ax,1 ; number of heads=1 ; cf should be 0
   div [byte sect_per_track]
   ; we should have ah=0 (all sectors used in declared surfaces&cylinders)
   mov [Max_Tracks_on],al
   ret
EndP H_Init_disk

Proc H_Next_cluster near
   mov ax,[LoadArg]	; real max tracks & max sects/tracks
   mov dx,[CurrentTrack]
   @IF z
     xor dh,1	; change head
   @THEN
     inc dl	; change track
   @ENDIF
   cmp dl,[Max_Tracks_on]
   ja  extra_track
   mov cl,[byte sect_per_track]
   sub al,cl
   inc cl
 H_done:
   mov [CurrentTrack],dx
   mov ch,dl
   mov dl,[BIOS_DL]
   ret
 extra_track:
   mov cl,1
   cmp dl,ah	; only ah real tracks.
   jbe H_done
   clr al
   ret
EndP H_Next_cluster


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Load Mode 'C' ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Proc C_Init_disk near
	__ <"'C' Init disk",CRLF>
	mov ebx,[hidden_sect]
  ; we store the next sector to read in CurrentSector
	movzx eax,[LoadArg]
	add eax,ebx
	mov [CurrentSector],eax
  ; we store the maximal value to read in MaxSector
	movzx eax,[reserved_sect]
	add eax,ebx
	mov [MaxSector],eax
	;@__@
	ret
EndP C_Init_disk

Proc C_Next_cluster near
	clr al
	mov esi,[CurrentSector]
	mov edi,[MaxSector]
	sub edi,esi
	jb CNcret
	push esi
	mov eax,esi
	call SectorNumToBIOS_Args
	mov dl,cl ; sector
	and dl,3Fh
	mov al,[byte sect_per_track]
	sub al,dl
	inc al
	movzx eax,al
	@IF a
	  cmp eax,edi
	@THEN
	  mov eax,edi
	@ENDIF
	pop esi
	add esi,eax
	mov [CurrentSector],eax
	mov dl,[BIOS_DL]

;__ <"'C' Next Cluster Output:",CRLF>
	;@__@

  CNcret:
	ret
EndP C_Next_cluster

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Load Mode 'L' ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Proc L_Init_disk near
	__ <"'L' Init disk",CRLF>
	;mov [over_fill_seg],1000h	; (!) set in End_LILOs_work
	mov [over_fill_ofs],0
	mov [over_fill_len],8000h	; (let's say) 32K max API+kernel length
					; so we're safe
	ret
EndP L_Init_disk

Proc L_Next_cluster near
	__ <"'L' Next cluster",CRLF>
	clr al	; end_of_file
	ret
EndP L_Next_cluster


;;;;;;;;;;;;;;;;;;;;;;;;;; Low-Level Disk Functions ;;;;;;;;;;;;;;;;;;;;;;;;;;;

; From Linux:
; * This procedure turns off the floppy drive motor, so
; * that we enter the kernel in a known state, and
; * don't have to worry about it later.
Proc kill_motor near
	push dx
	mov dx,03f2h
	xor al,al
	out dx,al
	pop dx
	retn
EndP kill_motor


;;;;;;;;;;;;;;;;;;;;;;;; Signature for Linuxish loading ;;;;;;;;;;;;;;;;;;;;;;
dataseg
  align 4

setup_sig1	dw	Lxsig_1
setup_sig2	dw	Lxsig_2

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; End of LOADER.com ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
BufferBeg:

End Entry_Point
