; BA-RM.asm
; Real-Mode code & data for the MOOSE BIOS-based API
; see ~/info/BIOS-API.nfo

Ideal
Include "../API.ash"
Include "BIOS-API.inc"

DEBUG=1
__DEBUG__=1

;;;;;;;;;;;;;;;;;;;;;;;;;;;; EXTRN statements ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Extrn	Init_GDT : DWORD
Extrn	Init_GDT_Len : ABS
Extrn	Init_PM_Target_seg : ABS
Extrn	Init_PM_Target_ofs : NEAR
Extrn	Init_RM_Target_seg : ABS
Extrn	Init_RM_Target_ofs : NEAR

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Constants ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
__RMPART__	=	__RC__
GoRealSEG	=	RMPARTCS

;;;;;;;;;;;;;;;;;;;;;;;;;;; Global Declarations ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
_Code_is	RM_Code
_Data_is	RM_Data
_UData_is	RM_UData

__DEBUG__=1	; heavy debug

;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Global Variables ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Segment	RM_Data
__RD__:			; (!) must be first thing in RM_Data
label	RM_Target	dword
	RM_Target_ofs	dw	ofs Init_RM_Target_ofs
	RM_Target_seg	dw	Init_RM_Target_seg

label PM_Target	pword
	PM_Target_ofs	dd	ofs Init_PM_Target_ofs
	PM_Target_seg	dw	Init_PM_Target_seg

label PM_GDT_address qword	; Length & Base address of protected-mode GDT
	PM_GDT_lim	dw	Init_GDT_Len-1
	PM_GDT_base	dd	ofs Init_GDT

EndS	RM_Data
;;;;;;;;;;;;;;; Standard objects (their low_level implementation) ;;;;;;;;;;;;
Segment	RM_Data
;;; Stdin as a byte per byte input ;;;
__stdin_param__		dw	?
__stdin_Getc__		dw	cGetc
__stdin_Flush__		dw	False
__stdin_EOF__		dw	False
;;; Stdout as a byte per byte output ;;;
__stdout_param__	dw	?
__stdout_Putc__		dw	cPutc
__stdout_Flush__	dw	False
__stdout_FULL__		dw	False
EndS	RM_Data
;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Uninitialized Data ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Segment	RM_UData
__RU__:			; (!) must be first thing in RM_UData
if1
label rm_ss_sp dword
rm_sp		dw	?
rm_ss		dw	?
RMPartStackBot	dw	256 dup (?)
RMPartStackTop	=	offset $
endif
EndS	RM_UData
;;;;;;;;;;;;;;;;;;;;;;;;;; Standard Routines ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Segment RM_Code
__RC__:			; (!) must be first thing in RM_Code
Proc	Puts	near
; 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.
	cld
	lodsb
	or	al,al
	jz	short Return
	push	offset Puts
;	jmps	Putc			; (!) Putc must follow.
EndP	Puts
Proc	Putc	near
	push	si
	mov	si,[__stdout_param__]
	call	[__stdout_Putc__]
	pop	si
	ret
EndP	Putc
Proc	Getc	near
	push	si
	mov	si,[__stdout_param__]
	call	[__stdout_Putc__]
	pop	si
	ret
EndP	Getc
Proc	cGetc	near
	clr	ah
	int	16h
	retn
EndP	cGetc
Proc	Beep	near
; Echo a BEEP to the the Console
	mov	al,7
	;jmps	cPutc
EndP	Beep
Proc	cPutc	near
; Outputs a character to console
; spoils AX and DI, keeps si
	pusha
	push	es
	mov	ah,0Eh	; function Teletype
	mov	bx,7	; attribute
	int	10h	; Video BIOS call
	pop	es
	popa
Return:	ret
EndP	cPutc
; the true condition is "c"; false is "nc"
Proc	True	near
	stc
	ret
EndP	True
Proc	False	near
	clc
	ret
EndP	False
EndS	RM_Code
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; DEBUGGING AID ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Segment RM_Code
IFDEF DEBUG
;%%OUT Watch out: @__
;@__
Seg_Data			; _Data is not recognized when called !
  RegsMsg	db "DI   SI   BP   SP   "
		db "BX   DX   CX   AX   "
		db "FLGS CS   DS   ES   "
		db "SS   IP   STK1 STK2"
		db 13,10,0
  ERegsMsg1	db "EDI      ESI      EBP      ESP      "
		db "EBX      EDX      ECX      EAX      EFLAGS",13,10,0
  ERegsMsg2	db "CS   DS   ES   SS   IP   STK1 STK2 STK3 "
		db "STK4 STK5 STK6 STK7 STK8 STK9 STKA STKB"
		db 13,10,0
End_Data
Proc	DumpERegs	near
	push ss
	push es
	push ds
	push cs
	pushfd
	pushad
	ldea si,ERegsMsg1
	PUTS
	push ss
	pop es
	push cs
	pop ds
	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	near
	push ss
	push es
	push ds
	push cs
	pushf
	pusha
	ldea si,RegsMsg
	PUTS
	mov di,sp
	push ss
	pop es
	push cs
	pop ds
	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_	near
	call	Space
PutLZone:
	mov eax,[es:di]
	add di,4
	call PutHexDword
	loop PutLZone_
	ret
EndP	PutLZone_
Proc	PutWZone_	near
	call	Space
PutWZone:
	mov ax,[es:di]
	inc di
	inc di
	call	PutHexWord
	loop PutWZone_
	ret
EndP	PutWZone_
Proc	PutZone_	near
	call	Space
PutZone:
	mov al,[es:di]
	inc di
	call	PutHexByte
	jcxz	short Return2
	call	Space
	loop PutZone_
	ret
EndP	PutZone_
Proc	PutAddr		near
;; Prints the contents of es:di
	push	di
	mov	ax,es
	call	PutHexWord
	mov	al,':'
	call	Putc
	pop	di
	mov	ax,di
	jmp	PutHexWord
EndP	PutAddr
Proc	PutHexDword	near
	rol	eax,16
	call	PutHexWord
	rol	eax,16
;	jmp	PutHexWord
EndP	PutHexDword
Proc	PutHexWord
; Prints the contents of AX in hexa.
; everything but F saved
		xchg	al,ah
		call	PutHexByte
		xchg	al,ah
;		jmp	PutHexByte
EndP	PutHexWord
ENDIF
;__@
;%%OUT __@ reached
Proc	PutHexByte	near
; Prints the contents of AL in hexa.
; saves AX.
	push	cx
	mov	cl,4
	rol	al,cl
	call	PutHexNybble
	rol	al,cl
	pop	cx
;	jmp	PutHexNybble
EndP	PutHexByte
Proc	PutHexNybble near
; Prints to BIOS console the hexa nybble in low AL
; saves AX, puts 7 into BX
	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
IF 0
Proc	sPutc	near
	stosb
	retn
EndP	sPutc
Proc	sGetc	near
	lodsb
	retn
EnP	sGetc
ENDIF
Proc	PrCRLF	near
	PUTC	13
	mov	al,10
	jmp	Putc
EndP	PrCRLF
IFDEF DEBUG ;@__
Proc	Space	near
	mov al,' '
	jmp Putc
EndP	Space
ENDIF;__@
EndS	RM_Code
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; I/O subroutines ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Segment	RM_Code
Proc	Delay	near
   ;;; Delay between I/O, as in Linux.
	jmp short $+2
	ret
EndP	Delay
Proc	DisableNMI	near
	Disable_NMI
	ret
EndP	DisableNMI
Proc	EnableNMI	near
	Enable_NMI
	ret
EndP	EnableNMI
Proc	Reset_Copro
	clr	ax
	out	0f0h,al
	call	Delay
	out	0f1h,al
	ret
EndP	Reset_Copro
def_proc_KB_Ready near,Timeout_8042,cx
; What to do in case of time out from 8042
Proc	Timeout_8042	near
		db	68h		; push immediate value of timeout exc.
  Time_Out_8042	dw	KB_Ready	; default behaviour: retry indefinitely
	ret
EndP	Timeout_8042
Proc	Disable_A20	near
	mov ah,set_A20_OFF
	jmps Set_A20
EndP	Disable_A20
Proc	Enable_A20	near
	mov ah,set_A20_ON
	;jmp Set_A20
EndP	Enable_A20
Proc	Set_A20		near
	set_A20
	ret
EndP	Set_A20
EndS	RM_Code
;;;;;;;;;;;;;;;;;;;;;;; Real to Protected Mode switcher ;;;;;;;;;;;;;;;;;;;;;;;
Segment	RM_Code
Proc	GoProtect	near
; PREcondition
; * cs points to a segment so that RM_Data is at same offset as from API file
; POSTcondition
; * A20 line is enabled
; * all hardware interrupts are disabled
; * Processor enters Protected mode
; * Processor jumps into 32-bit far address contained in
	@wo@ <"Going PM, Target=">,[PM_Target_seg],<":">
	@dwo@ <>,[PM_Target_ofs],<CRLF>
	cli
	;__ <"Disabling NMI",13,10>
	call	DisableNMI
	;__ <"Enabling A20",13,10>
	call	Enable_A20
	@dwo@ <"Loading GDT at ">,[PM_GDT_base],<CRLF>
	lgdt	[PM_GDT_address]
	mov	eax,cr0		; 32 bit msw
	and	eax,1ffaffffh	; clear PG, CD, NW, AM, WP flags
	or	al,1		; protected mode (PE) bit
	SST	<"Hit any key to go PM">
	test	[byte GP_CR0],1
	mov	cr0,eax		; This is it!
 GP_CR0:
	jmp	[cs:PM_Target]
EndP	GoProtect
Proc	Ret_From_PM	near
	iret			; !
EndP	Ret_From_PM
Proc	GoReal	near
; PREconditions:
; * (!) cs points to a segment such that the PM Data area is at the
;  same offset as it is from the API file (that's klugdy) (FIXME!)
; POSTconditions:
; (just) do the switching to Real mode and jump to 16-bit far [RM_Target]
; NOTE:
; I had problems having this code in a 32 bit segment,
; as the final jump seemed to get nowhere (or get it in real 32-bit mode ?)
	;IF	__DEBUG__
	;mov eax,ALLMEMDS
	;mov fs,ax
	;ENDIF
	;STOP fs:0B8000h+,1,13,'G',0Ch
	mov	eax,cr0
	and	eax,1ffafffeh	; clear PG, CD, NW, AM, WP, and *PE* flags
	test	[byte GR_CR0],1
	mov	cr0,eax		; Back to the Real World ):)
   GR_CR0:
	;STOP fs:0B8000h+,2,13,'o',0Ch
	FlushInstr
	;STOP fs:0B8000h+,3,13,'R',0Ch
	jmp	[dword cs:RM_Target]
EndP	GoReal
EndS	RM_Code
;;;;;;;;;;;;;;;;;;; Hook from hardware IRQ to BIOS handlers ;;;;;;;;;;;;;;;;;;
;;; (when MOOSE I/O control is disabled)
Segment	RM_Code
Proc	RM_IRQ
	int 08h
	iret
	int 09h
	iret
	int 0Ah
	iret
	int 0Bh
	iret
	int 0Ch
	iret
	int 0Dh
	iret
	int 0Eh
	iret
	int 0Fh
	iret
	int 70h
	iret
	int 71h
	iret
	int 72h
	iret
	int 73h
	iret
	int 74h
	iret
	int 75h
	iret
	int 76h
	iret
	int 77h
	iret
EndP	RM_IRQ
EndS	RM_Code
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;; Public declarations ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Segment	RM_Data
  RMPartELen	= $
EndS	RM_Data
Segment	RM_UData
if1
  RMPartLen	= $
endif
EndS	RM_UData

Public RMPartLen,RMPartELen
Public RM_IRQ
Public Putc,Getc,Puts,Beep
Public cPutc,cGetc
Public GoProtect,PM_Target,PM_Target_ofs,PM_Target_seg
Public GoReal,GoRealSEG,RM_Target,RM_Target_ofs,RM_Target_seg
Public rm_ss_sp
Public __RC__,__RD__,__RU__,__RMPART__
IF DEBUG
Public DumpRegs,DumpERegs
Public PutHexNybble,PutHexByte,PutHexWord,PutHexDword,PutAddr
Public PutZone,PutWZone,PutLZone
Public PrCRLF,Space
ENDIF
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
End
