;	SCCSID = @(#)mscode.asm 1.2 85/07/23
;
; MSCODE.ASM -- MSDOS code
;

[list -]
;.xcref
%include "entryseg.nas"
%include "dosseg.nas"
%include "dossym.mac"
%include "devsym.mac"
%include "ifssym.mac"
%include "fastopen.mac"
%include "fastxxxx.mac"
%include "codesw.mac"
%include "lmacros2.mac"
%include "lstruct.mac"
;.cref
[list +]

%ifndef Kanji
 %iassign Kanji 0
%endif
%ifndef Debug
 %iassign Debug 0
%endif

section DOSCODECODE

    I_need  InDos,BYTE			; TRUE => we are in dos, no interrupt
    I_need  OpenBuf,128 		; temp name buffer
    I_need  ExtErr,WORD 		; extended error code
    I_need  User_SS,WORD		; stack segment from user
    I_need  User_SP,WORD		; stack pointer from user
    I_need  DskStack,BYTE		; stack segment inside DOS
    I_need  ThisCDS,DWORD		; Currently referenced CDS pointer
    I_need  ThisDPB,DWORD		; Currently referenced DPB pointer
    I_need  Err_Table_21		; allowed return map table for errors
    I_need  FailErr,BYTE		; TRUE => system call is being failed
    I_need  ExtErr_Action,BYTE		; recommended action
    I_need  ExtErr_Class,BYTE		; error classification
    I_need  ExtErr_Locus,BYTE		; error location
    I_need  I21_Map_E_Tab,BYTE		; mapping extended error table
    I_need  User_In_AX,WORD		; initial input user AX
    I_need  FOO,WORD			; return address for dos 2f dispatch
    I_need  DTAB,WORD			; dos 2f dispatch table
    I_need  HIGH_SECTOR,WORD		; >32mb
    I_need  IFS_DRIVER_ERR,WORD 	; >32mb
    I_need  FastOpenFlg,BYTE		;
    I_need  FastSeekFlg,BYTE		;
    I_need  CURSC_DRIVE,BYTE		;
    I_need	first_hmcb, word

section DOSDATACODE	; in DOSDATA

BREAK <NullDev -- Driver for null device>

procedure   SNULDEV,FAR
ASSUME DS:NOTHING,ES:NOTHING,SS:NOTHING
	OR	word [ES:BX + REQSTAT],STDON	; Set done bit
entry INULDEV
	RET				; MUST NOT BE A RETURN!
EndProc SNULDEV

BREAK <AbsDRD, AbsDWRT -- INT int_disk_read, int_disk_write handlers>

section DOSCODETABLE
Public MSC001S,MSC001E
MSC001S label byte
	%IF	IBM
; Codes returned by BIOS
DOS_ERRIN:
	DB	2			; NO RESPONSE
	DB	6			; SEEK FAILURE
	DB	12			; GENERAL ERROR
	DB	4			; BAD CRC
	DB	8			; SECTOR NOT FOUND
	DB	0			; WRITE ATTEMPT ON WRITE-PROTECT DISK
DOS_ERROUT:
; DISK ERRORS RETURNED FROM INT 25 and 26
	DB	80H			; NO RESPONSE
	DB	40H			; Seek failure
	DB	2			; Address Mark not found
	DB	10H			; BAD CRC
	DB	4			; SECTOR NOT FOUND
	DB	3			; WRITE ATTEMPT TO WRITE-PROTECT DISK

DOS_NUMERR	EQU	$-DOS_ERROUT
	%ENDIF
MSC001E label byte

TABLE	ENDS
section DOSCODECODE

;   AbsSetup - setup for abs disk functions

Procedure   AbsSetup,NEAR
 ASSUME DS:NOTHING,ES:NOTHING,SS:DOSGroup
	STI
	CLD
	PUSH	DS
	Context DS
	CALL	GETBP_DOS
	JC	errdriv 		;PM. error drive			;AN000;
	MOV	word [ES:BP + dpb_free_cnt],-1 ; do not trust user at all.
errdriv:
	POP	DS
ASSUME	DS:NOTHING
	retc

	MOV	word [ss:HIGH_SECTOR],0 	;>32mb	from API			;AN000;
	CALL	RW32_CONVERT		;>32mb convert 32bit format to 16bit	;AN000;
	retc

%ifndef BUF2
	invoke	SET_RQ_SC_PARMS 	;LB. set up SC parms			;AN000;
%endif
	PUSH	DS
	PUSH	SI
	PUSH	AX
	Context DS
OPENBUF equ OpenBuf	; NASM port label
	MOV	SI,OFFSET OPENBUF wrt DOSGROUP
	MOV	[SI],AL
	ADD	BYTE PTR [SI],"A"
	MOV	WORD PTR [SI+1],003AH	; ":",0
	MOV	AX,0300H
	CLC
	INT	int_IBM 		; Will set carry if shared
	POP	AX
	POP	SI
	POP	DS
ASSUME	DS:NOTHING
	retnc
	MOV	word [ss:ExtErr],error_not_supported
	return
EndProc AbsSetup

; Interrupt 25 handler.  Performs absolute disk read.
; Inputs:	AL - 0-based drive number
;		DS:BX point to destination buffer
;		CX number of logical sectors to read
;		DX starting  logical sector number (0-based)
; Outputs:	Original flags still on stack
;		Carry set
;		    AH error from BIOS
;		    AL same as low byte of DI from INT 24

%if _RELOCATEDOSCODE
relocated _i25_i26
	jc relocatedi26
%endif
relocated i25
	procedure   ABSDRD,FAR
 ASSUME DS:NOTHING,ES:NOTHING,SS:NOTHING

extern doscode_getdosdata
extern temp_var

	CLI
	push ds
	push ax
	call doscode_getdosdata
	mov ds, ax
 assume ds:DOSGROUP
INDOS equ InDos	; NASM port label
	inc byte [INDOS]
	pop ax
	pop word [temp_var]		; rescue user ds

user_SS equ User_SS	; NASM port label
	MOV	[user_SS],SS
user_SP equ User_SP	; NASM port label
	MOV	[user_SP],SP
DSKSTACK equ DskStack	; NASM port label
	PUSH	ds
	POP	SS
	MOV	SP,OFFSET DSKSTACK wrt DOSGROUP
ASSUME	SS:DOSGROUP
	mov ds, word [temp_var]
 assume ds:nothing
	invoke	Save_World		      ;>32mb save all regs		;AN000;
	PUSH	ES
	CALL	AbsSetup
	JC	ILEAVE
%ifn IBMCOPYRIGHT
; Here is a gross temporary fix to get around a serious design flaw in
;  the secondary cache.  The secondary cache does not check for media
;  changed (it should).  Hence, you can change disks, do an absolute
;  read, and get data from the previous disk.  To get around this,
;  we just won't use the secondary cache for absolute disk reads.
;                                                      -mw 8/5/88
	EnterCrit   critDisk
	MOV	byte [ss:CURSC_DRIVE],-1	      ; invalidate SC			;AN000;
	LeaveCrit   critDisk
%endif
	invoke	DSKREAD
TLEAVE:
	JZ	ILEAVE

	%IF	IBM
; Translate the error code to ancient 1.1 codes
	PUSH	ES
	 PUSH	CS			; => DOSCODE
	 POP	ES
 assume es:DOSCODEGROUP
	XOR	AH,AH			; Nul error code
	MOV	CX,DOS_NUMERR		; Number of possible error conditions
	MOV	DI,OFFSET DOS_ERRIN	; Point to error conditions
	REPNE	SCASB
	JNZ	LEAVECODE		; Not found
	MOV	AH,[ES:DI+DOS_NUMERR-1]	; Get translation
LEAVECODE:
	POP	ES
 assume es:nothing
	%ENDIF
	MOV	[ss:IFS_DRIVER_ERR],AX	;>32mb save error
	STC
ILEAVE:					; ! CF has error status
	POP	ES
 assume es:nothing
	invoke	Restore_World		     ;>32mb				;AN000;
 assume ds:nothing, es:nothing
	CLI
	MOV	AX,[ss:IFS_DRIVER_ERR]	     ;>32mb restore error		;AN000;
	push ds
	push si
	lds si, [ss:user_SP]		; ds:si -> user stack
 assume ds:nothing
	dec si				; ! preserce CF
	dec si				; make space for a word on the stack
	pop word [si]			; si
	dec si
	dec si				; ! preserve CF
	pop word [si]			; ds
	dec si
	dec si				; ! preserve CF
	mov word [si], ss		; => DOSDATA
	push ds
	pop ss
	mov sp, si	; -> user stack (DOS ds, ds, si, ip, cs, fl)
ASSUME	SS:NOTHING
	pop ds
 assume ds:DOSGROUP
	dec byte [INDOS]		; ! preserve CF
	pop ds				; ds first
 assume ds:nothing
	pop si				; then si
	STI

entry doscode_retf
	RET				; This must not be a RETURN!
EndProc ABSDRD

; Interrupt 26 handler.  Performs absolute disk write.
; Inputs:	AL - 0-based drive number
;		DS:BX point to source buffer
;		CX number of logical sectors to write
;		DX starting  logical sector number (0-based)
; Outputs:	Original flags still on stack
;		Carry set
;		    AH error from BIOS
;		    AL same as low byte of DI from INT 24

relocated i26
	procedure   ABSDWRT,FAR
ASSUME	DS:NOTHING,ES:NOTHING,SS:NOTHING

	CLI
	push ds
	push ax
	call doscode_getdosdata
	mov ds, ax
 assume ds:DOSGROUP
	inc byte [INDOS]
	pop ax
	pop word [temp_var]		; rescue user ds

	MOV	[user_SS],SS
	MOV	[user_SP],SP
	PUSH	ds
	POP	SS
	MOV	SP,OFFSET DSKSTACK wrt DOSGROUP
ASSUME	SS:DOSGROUP
	mov ds, word [temp_var]
 assume ds:nothing
	invoke	Save_World		      ;>32mb save all regs		;AN000;
	PUSH	ES
	CALL	AbsSetup
	JC	ILEAVE

	EnterCrit   critDisk
	MOV	byte [ss:CURSC_DRIVE],-1	      ; invalidate SC			;AN000;
	CALL	Fastxxx_Purge		      ; purge fatopen			;AN000;
	LeaveCrit   critDisk

	invoke	DSKWRITE
	JMP	TLEAVE
EndProc ABSDWRT

; Inputs:
;	AL = Logical unit number (A = 0)
; Function:
;	Find Drive Parameter Block
; Outputs:
;	ES:BP points to DPB
;	[THISDPB] = ES:BP
;	Carry set if unit number bad or unit is a NET device.
;		Later case sets extended error error_I24_not_supported
; No other registers altered

Procedure GETBP_DOS,NEAR
	DOSAssume   CS,<DS>,"GetBP_DOS"
	ASSUME	ES:NOTHING

	PUSH	AX
	ADD	AL,1			; No increment; need carry flag
	JC	SkipGet
	invoke	GetThisDrv
	JNC	SkipGet 		   ;PM. good drive			;AN000;
	XOR	AH,AH			   ;DCR. ax= error code 		;AN000;
error_not_dos_disk equ error_not_DOS_disk	; NASM port equate
	CMP	AX,error_not_dos_disk	   ;DCR. is unknown media ?		;AN000;
	JZ	SkipGet 		   ;DCR. yes, let it go 		;AN000;
	STC				   ;DCR.				;AN000;
	MOV	[ExtErr],AX		   ;PM. invalid drive or Non DOS drive	;AN000;
	MOV	word [IFS_DRIVER_ERR],0201H	   ;PM. other errors/unknown unit	;AN000;
SkipGet:
	POP	AX
	retc
THISCDS equ ThisCDS	; NASM port label
	LES	BP,[THISCDS]
	TEST	word [ES:BP + curdir_flags],curdir_isnet   ; Clears carry
	JZ	GETBP_CDS
	MOV	word [ExtErr],error_not_supported
	STC
	return

GETBP_CDS:
	LES	BP,[ES:BP + curdir_devptr]

	entry	GOTDPB
	DOSAssume   CS,<DS>,"GotDPB"
; Load THISDPB from ES:BP

THISDPB equ ThisDPB	; NASM port label
	MOV	WORD PTR [THISDPB],BP
	MOV	WORD PTR [THISDPB+2],ES
	return
EndProc GetBP_DOS

BREAK <SYS_RET_OK SYS_RET_ERR CAL_LK ETAB_LK set system call returns>

 assume nocheck, ss:DOSGROUP

;
; These are the general system call exit mechanisms.  All internal system
; calls will transfer (jump) to one of these at the end.  Their sole purpose
; is to set the user's flags and set his AX register for return.
;

procedure   SYS_RETURN,NEAR
	ASSUME	DS:NOTHING,ES:NOTHING
entry	SYS_RET_OK
	invoke	FETCHI_CHECK		; TAG checking for FETCHI
	invoke	get_user_stack
	AND	word [SI + user_F],~ f_Carry ; turn off user's carry flag
	JMP	SHORT DO_RET		; carry is now clear

entry	SYS_RET_ERR
	XOR	AH,AH			; hack to allow for smaller error rets
	invoke	ETAB_LK 		; Make sure code is OK, EXTERR gets set
ErrorMap equ errorMap	; NASM port label
	CALL	ErrorMap
entry	From_GetSet
	invoke	get_user_stack
	OR	word [SI + user_F],f_Carry	; signal carry to user
	STC				; also, signal internal error
DO_RET:
	MOV	[SI + user_AX],AX 	; Really only sets AH
	return

	entry	FCB_RET_OK
	entry	CPMFunc
	XOR	AL,AL
	return

	entry	FCB_RET_ERR
	XOR	AH,AH
exterr equ ExtErr	; NASM port label
	mov	[ss:exterr],AX
	CALL	ErrorMap
	MOV	AL,-1
	return

	entry	errorMap
	PUSH	SI
ERR_TABLE_21 equ Err_Table_21	; NASM port label
	MOV	SI,OFFSET ERR_TABLE_21 wrt DOSGROUP
FAILERR equ FailErr	; NASM port label
	CMP	byte [ss:FAILERR],0		; Check for SPECIAL case.
	JZ	EXTENDED_NORMAL 	; All is OK.
EXTERR equ ExtErr	; NASM port label
	MOV	word [ss:EXTERR],error_FAIL_I24 ; Ooops, this is the REAL reason
	MOV	SI,OFFSET ERR_TABLE_21 wrt DOSGROUP
EXTENDED_NORMAL:
	invoke	CAL_LK			; Set CLASS,ACTION,LOCUS for EXTERR
	POP	SI
	return

EndProc SYS_RETURN

; Inputs:
;	SI is OFFSET in DOSGROUP of CLASS,ACTION,LOCUS Table to use
;		(DS NEED not be DOSGROUP)
;	[EXTERR] is set with error
; Function:
;	Look up and set CLASS ACTION and LOCUS values for GetExtendedError
; Outputs:
;	[EXTERR_CLASS] set
;	[EXTERR_ACTION] set
;	[EXTERR_LOCUS] set  (EXCEPT on certain errors as determined by table)
; Destroys SI, FLAGS

	procedure   CAL_LK,NEAR
ASSUME	DS:NOTHING,ES:NOTHING

	PUSH	DS
	PUSH	AX
	PUSH	BX
	Context DS		; DS:SI -> Table
	MOV	BX,[EXTERR]	; Get error in BL
TABLK1:
	LODSB
	CMP	AL,0FFH
	JZ	GOT_VALS	; End of table
	CMP	AL,BL
	JZ	GOT_VALS	; Got entry
	ADD	SI,3		; Next table entry
	JMP	TABLK1

GOT_VALS:
	LODSW			; AL is CLASS, AH is ACTION
	CMP	AH,0FFH
	JZ	NO_SET_ACT
EXTERR_ACTION equ ExtErr_Action	; NASM port label
	MOV	[EXTERR_ACTION],AH     ; Set ACTION
NO_SET_ACT:
	CMP	AL,0FFH
	JZ	NO_SET_CLS
EXTERR_CLASS equ ExtErr_Class	; NASM port label
	MOV	[EXTERR_CLASS],AL      ; Set CLASS
NO_SET_CLS:
	LODSB			; Get LOCUS
	CMP	AL,0FFH
	JZ	NO_SET_LOC
EXTERR_LOCUS equ ExtErr_Locus	; NASM port label
	MOV	[EXTERR_LOCUS],AL
NO_SET_LOC:
	POP	BX
	POP	AX
	POP	DS
 assume ds:nothing
	return
EndProc CAL_LK

; Inputs:
;	AX is error code
;	[USER_IN_AX] has AH value of system call involved
; Function:
;	Make sure error code is appropriate to this call.
; Outputs:
;	AX MAY be mapped error code
;	[EXTERR] = Input AX
; Destroys ONLY AX and FLAGS

	procedure   ETAB_LK,NEAR
ASSUME	DS:NOTHING,ES:NOTHING

	PUSH	DS
	PUSH	SI
	PUSH	CX
	PUSH	BX
	Context DS
	MOV	[EXTERR],AX		; Set EXTERR with "real" error
I21_MAP_E_TAB equ I21_Map_E_Tab	; NASM port label
	MOV	SI,OFFSET I21_MAP_E_TAB
	MOV	BH,AL			; Real code to BH
USER_IN_AX equ User_In_AX	; NASM port label
	MOV	BL,BYTE PTR [USER_IN_AX + 1]	; Sys call to BL
TABLK2:
	cs LODSW
	CMP	AL,0FFH 		; End of table?
	JZ	NOT_IN_TABLE		; Yes
	CMP	AL,BL			; Found call?
	JZ	GOT_CALL		; Yes
	XCHG	AH,AL			; Count to AL
	XOR	AH,AH			; Make word for add
	ADD	SI,AX			; Next table entry
	JMP	TABLK2

NOT_IN_TABLE:
	MOV	AL,BH			; Restore original code
	JMP	SHORT NO_MAP

GOT_CALL:
	MOV	CL,AH
	XOR	CH,CH			; Count of valid err codes to CX
CHECK_CODE:
	cs LODSB
	CMP	AL,BH			; Code OK?
	JZ	NO_MAP			; Yes
	LOOP	CHECK_CODE
NO_MAP:
	XOR	AH,AH			; AX is now valid code
	POP	BX
	POP	CX
	POP	SI
	POP	DS
 assume ds:nothing
	return

EndProc ETAB_LK

BREAK <DOS 2F Handler and $default NET 2F handler>

%IF installed

;
; SetBad sets up info for bad functions
;
Procedure   SetBad,NEAR
 ASSUME DS:NOTHING,ES:NOTHING,SS:NOTHING
	call doscode_getdosdata
	push ds
	mov ds, ax
 assume ds:DOSGROUP
ExtErr_LOCUS equ ExtErr_Locus	; NASM port label
errLoc_UNK equ errLOC_Unk	; NASM port equate
	MOV	byte [ExtErr_LOCUS],errLoc_UNK
	pop ds
 assume ds:nothing
set_ax_1_CY:
	MOV	AX,error_invalid_function	; ALL NET REQUESTS get inv func
	STC
	ret
EndProc SetBad
;
; BadCall is the initial routine for bad function calls
;
relocated badcallentry
procedure   BadCall,FAR
	call	SetBad
	ret
EndProc BadCall
;
; OKCall always sets carry to off.
;
%if _RELOCATEDOSCODE
relocated _okcallentry_badcallentry
	jc relocatedbadcallentry
%endif
relocated okcallentry
Procedure   OKCall,FAR
 ASSUME DS:NOTHING,ES:NOTHING,SS:NOTHING
	CLC
	ret
EndProc OKCall

; INT 2F handler works as follows:
;   PUSH    AX
;   MOV     AX,multiplex:function
;   INT     2F
;   POP     ...
; The handler itself needs to make the AX available for the various routines.

relocated msdos_i2F
 ASSUME DS:NOTHING,ES:NOTHING,SS:NOTHING
	STI
multNET equ MultNET	; NASM port equate
	CMP	AH,multNET
	JNZ	INT2FSHR
TestInstall:			; used for 2F.11, 2F.10, 2F.14
	test	AL,AL		; (NC)
	JZ	Leave2F		; subfunction 00 --> return NC, ax unmodified
BadFunc:
	CALL	SetBad		; other subfunction, return CY ax = 1

entry	Leave13		; for msbio/ms96tpi.nas and msbio/msdisk.nas
entry	Leave2F
	push bp
	mov bp, sp
	push ax
	lahf
; bp + 0 = saved bp
; bp + 2 = ip
; bp + 4 = cs
; bp + 6 = fl
	mov byte [bp + 6], ah
	pop ax
	pop bp
	iret

%if _RELOCATEDOSCODE
 extern relocatedi6C
relocated _i31_i6C
	jc relocatedi6C
%endif
relocated i31
	call set_ax_1_CY
	jmp Leave2F

INT2FSHR:
multSHARE equ MultSHARE	; NASM port equate
	CMP	AH,multSHARE		; is this a share request
	JZ	TestInstall		; yes, check for installation

INT2FNLS:
	CMP	AH,NLSFUNC		; is this a DOS 3.3 NLSFUNC request
	JZ	TestInstall		; yes check for installation

INT2FDOS:
 ASSUME DS:NOTHING,ES:NOTHING,SS:NOTHING
multDOS equ MultDOS	; NASM port equate
	CMP	AH,multDOS
	JZ	DispatchDOS
	cmp ah, 4Ah
	je hma_access
	IRET				; This assumes that we are at the head
					;  of the list (there is no downlink).

DispatchDOS:
	cmp al, 61h
	je get_first_umcb		; (NC)
	PUSH	word [cs:FOO]			; push return address
DTab equ DTAB	; NASM port label
	PUSH	word [cs:DTab]			; push table address
	PUSH	AX			; push index
	PUSH	BP
	MOV	BP,SP
; stack looks like:
;   0	BP
;   2	DISPATCH
;   4	TABLE
;   6	RETURN
;   8	LONG-RETURN
;   c	FLAGS
;   e	AX

	MOV	AX,[BP+0Eh]		; get AX value
	POP	BP
	Invoke	TableDispatch
	JMP	BadFunc 		; return indicates invalid function

Procedure   INT2F_etcetera,NEAR

extern first_umcb

get_first_umcb:		; (NC)
	call doscode_getdosdata
	push ds
	mov ds, ax
 assume ds:DOSGROUP
	mov ax, [first_umcb]
	pop ds
 assume ds:nothing
	jmp Leave2F


hma_access:
 assume ds:nothing, es:nothing
	cmp al, 1
	jb .iret
	je .query
	cmp al, 4
	jb .access
	jne .iret
%if _RELOCATEDOSCODE && _DOSCODEHMA
	; je .get_first_hmcb

.get_first_hmcb:
	push ds
	push bx
	push di
	push ax
	call getfirsthmcb
 assume ds:nothing
		; OUT:	ds = FFFFh
		;	bx = 0
		;	NC if DOS manages the HMA,
		;	 ds:di -> first HMCB, allocated to DOS (owner = -2)
		;	 A20 is assumed on because our cs is in the HMA
		; CHG:	ds, di, bx, ax
	jc @F
	xor ax, ax		; ax = 0
	 push ds
	 pop es			; es:di -> first HMCB
 assume es:nothing
	pop bx			; discard ax
	pop bx			; discard di
	db __TEST_IMM16		; (skip pop twice)
@@:
		; if we are not managing the HMA, return
		;  as if the function is not supported
	pop ax			; restore ax
	pop di			; restore di
	pop bx			; unconditionally: restore bx, ds, iret
	pop ds
%endif
 assume ds:nothing
.iret:
	iret

.query:
%if _RELOCATEDOSCODE && _DOSCODEHMA
	call find_largest_free_hmcb
%else
	xor bx, bx
	mov di, -1
	mov es, di
%endif
	iret


.access:
	push cx
	push dx
	cmp al, 3		; MS-DOS v7 style access ?
	je .access_new		; yes, dx and cx as passed -->
				; MS-DOS v5 style access, allocate like dl=0
				;  with owner = int 2Fh caller cs
.alloc_legacy:
	xor dx, dx		; = function 0 (allocate low)
	lframe 0
	lpar word, iret_fl
	lpar word, iret_cs
	lpar word, iret_ip
	lpar word, cx
	lpar word, dx
	lenter
	mov cx, word [bp + ?iret_cs]
				; owner = int 2Fh caller's cs
	lleave
.access_new:
	push ax
	push ds
	push bx			; must be last here ! hma_alloc modifies this
%if _RELOCATEDOSCODE && _DOSCODEHMA
	cmp dl, 2
	je hma_free
	jb hma_alloc
%endif
		; unknown function, return bx = unchanged, es:di = all-1s
.access_invalid:
	mov di, -1
	mov es, di		; es:di = all-1s
 assume es:nothing
.access_return:
	pop bx			; must be first ! hma_alloc modifies this
	pop ds
 assume ds:nothing
	pop ax
	pop dx
	pop cx
	iret


%if _RELOCATEDOSCODE && _DOSCODEHMA
		; INP:	dl = 0 if to allocate low
		;	dl = 1 if to allocate high
		;	dl is never >= 2 when branched here
		;	ss:sp -> bx, ds, ax, dx, cx on stack
		;	 (bx must be first !)
		;	cx = owner to set (if 0, use 1)
		;	bx = size to allocate (byte granularity)
		; OUT:	es:di = all-1s if couldn't allocate,
		;	 bx = unmodified
		;	es:di -> allocated data block if could allocate,
		;	 bx = actually allocated size in bytes,
		;	  rounded up to paragraph boundary
		; REM:	MS-DOS v7.10 returns bx unchanged if no alloc.
@@:
	inc cx			; cx = 1, avoid zero owner
hma_alloc:
	jcxz @B			; jump back and increment cx if it was zero -->

	lea ax, [bx + 15]
	and ax, ~15		; round up to para boundary
				; ZR if too large !
	jz hma_access.access_invalid
				; return bx = unchanged, es:di = all-1s
	call find_largest_free_hmcb
 assume es:nothing
	jc hma_access.access_return
				; return bx = unchanged, es:di = all-1s

	cmp ax, bx		; free block is large enough ?
	ja hma_access.access_invalid
				; no -->
	pop ds			; discard bx on stack (must be first !)
 assume ds:nothing
	push ax			; push ax instead (actual allocation size)
	 push es
	 pop ds			; => HMA, ds:di -> behind HMCB to split
 assume ds:nothing

	push si
	mov si, 0		; ! preserve flags, si = 0
		; ds:di -> low HMCB data,
		;  size = bx + 16 >= ax + 16
		; ax = how much to alloc
		; bx = current data size minus 16
		; cx = owner to set
		; dl = function
		; si = 0
	mov word [di - HMCB_size + hmcbName], si
	mov word [di - HMCB_size + hmcbName + 2], si
	mov word [di - HMCB_size + hmcbName + 4], si
	mov word [di - HMCB_size + hmcbName + 6], si
				; clear the name of low HMCB

	jne .split		; NZ if free > requested
	mov word [di - HMCB_size + hmcbOwner], cx
				; free == requested, just allocate here
				; sign, size, and next already set!
	jmp .full

.split:
	sub bx, HMCB_size
	sub bx, ax		; bx = current size - 16 - how much to alloc
				;  = remainder size
	test dl, dl
	jnz .high
.low:
	xchg ax, bx		; bx = how much to alloc (no HMCB size)
				; ax = remainder size
				; di -> behind HMCB
	xchg word [di - HMCB_size + hmcbOwner], cx
				; allocate it,
				;  and load cx = 0 (owner meaning free)
.high:
		; if branched to .high:
		;  bx = remainder size (size for low HMCB)
		;  ax = how much to alloc (size for high HMCB)
		;  cx = owner to set (owner for high MCB)
		;  have to set es:di -> high HMCB data later
		; else if fell through from .low:
		;  bx = how much to alloc (size for low HMCB)
		;  ax = remainder size (size for high HMCB)
		;  cx = 0 (owner for high HMCB)
		;  es:di -> low HMCB data, to allocate
		; common:
		;  dl = mode (0 if low, 1 if high)
		;  es:di -> behind low HMCB
		;  owner of low HMCB set as desired
	 push bx		; push bx
	lea bx, [di + bx]	; bx -> new (high) HMCB
	  push word [di - HMCB_size + hmcbNext]
				; get old next
	mov word [bx + hmcbSignature], "MS"
	mov word [bx + hmcbOwner], cx
				; set owner (0 if low, input cx if high)
	mov word [bx + hmcbSize], ax
				; set size of high HMCB
	  pop word [bx + hmcbNext]; set new next
	mov word [bx + hmcbName], si
	mov word [bx + hmcbName + 2], si
	mov word [bx + hmcbName + 4], si
	mov word [bx + hmcbName + 6], si
				; clear the name
	mov word [di - HMCB_size + hmcbNext], bx
				; -> next (created at top of block)
	 pop word [di - HMCB_size + hmcbSize]	; but pop memory
				; allocate size of low HMCB
	test dl, dl
	jz @F
	lea di, [bx + HMCB_size]; -> top HMCB data
@@:
.full:
	pop si
j_hma_access.access_return:
	jmp hma_access.access_return


hma_free:
	push di
	call getfirsthmcb
 assume ds:nothing
	jc .none
	 push di
	mov di, es		; => user es
	dec bx			; = -1
	cmp di, bx		; match ?
	 pop bx
	jne .none		; no -->
	pop di
	push di
	test di, 15		; valid HMCB ?
	jnz .none		; no -->
	sub di, HMCB_size	; ds:di -> HMCB to free
	jbe .none		; reject too low HMCB !
	cmp word [di + hmcbSignature], "MS"
	jne .none
	cmp di, bx		; is first HMCB or below ?
				;  (do not allow to free the first HMCB !)
	jbe .none
	; MS-DOS v7.10 has a je here to handle this
.loop:
	test bl, 15		; valid HMCB ?
	jnz .none		; no -->
	cmp word [bx + hmcbSignature], "MS"
	jne .none
	mov ax, word [bx + hmcbNext]
	cmp ax, di
	je .found_below
	cmp ax, bx		; next > current ?
	jbe .none		; no -->
	test ax, ax
	xchg bx, ax		; bx -> next HMCB
	jnz .loop
	jmp .done
.none:
	push si
	push ax
	mov si, doscode_msg.error_common	; access with cs
	call doscode_disp_msg_cs
	mov si, doscode_msg.hmcb.5
	call doscode_disp_msg_cs
	mov ax, bx
	call DumpHMCB
	mov si, doscode_msg.hmcb.7
	call doscode_disp_msg_cs
	pop ax
	pop si
	pop di
	 push si
	 push ax
	mov ax, di
	call DumpHMCB
	mov si, doscode_msg.empty
 extern flag_no_halt_hmcb_free
	mov ax, flag_no_halt_hmcb_free
	call doscode_halt_query
	pop ax
	pop si
	db __TEST_IMM8		; skip pop
.done:
	pop di
	jmp j_hma_access.access_return

.found_below:
	and word [di + hmcbOwner], 0
				; mark input HMCB as free
	cmp word [bx + hmcbOwner], 0
				; is prior free ?
	jne .merge_di_to_next	; no, merge di and subsequent -->
	mov di, bx		; yes, set di from bx and merge subsequent
				;  (the first iteration will merge input HMCB)
.merge_di_to_next:
	mov bx, [di + hmcbNext]	; -> next
	test bx, bx		; any next ?
	jz .done		; no, all free ones merged -->
	test bl, 15		; validity
	jnz .none
	cmp bx, di		; validity
	jbe .none
	cmp word [bx + hmcbSignature], "MS"
				; validity
	jne .none
	cmp word [bx + hmcbOwner], 0
				; is next also free ?
	jne .done		; no, all free ones merged -->
	mov ax, [bx + hmcbSize]
	add ax, HMCB_size	; = size to add to prior HMCB
	add word [di + hmcbSize], ax
				; add to first free HMCB in chain
	add word [di + hmcbNext], ax
				; add, may carry
	jnc .merge_di_to_next	; valid next offset -->
	jz .merge_di_to_next	; valid end of chain indicator -->
	jmp .none		; error


		; OUT:	CY if not managing HMA,
		;	or <= 16 bytes free,
		;	or HMCB chain corrupted,
		;	 es:di = FFFFh:FFFFh
		;	 bx = 0
		;	NC if found >= 16 bytes free,
		;	 es:di -> largest free memory block
		;	 bx = size of block available
		; REM:	The size returned for MS-DOS v7 is the HMCB memory
		;	 block size minus 16 to allow an optimisation when
		;	 allocating, which is that a new HMCB is always
		;	 created. Unlike that, MS-DOS v5 returns a size
		;	 which spans the entire trailing space up to below
		;	 FFFFh:10000h. lDOS is now reverted to v5 style,
		;	 meaning the full HMCB block size is returned.
		;	Unlike v5, the free block may still occur in the
		;	 middle of the HMA (not at the very tail) though.
		; REM:	The returned memory block is the largest free block.
		;	 If multiple free blocks are of the same size,
		;	 the first one is returned.
		; CHG:	-
find_largest_free_hmcb:
	push ax
	push ds
	push cx
	call getfirsthmcb
 assume ds:nothing
	jc .error
	xor cx, cx		; init for .error_dump
.loop:
	test di, 15		; valid HMCB ?
	jnz .error_dump		; no -->
	cmp word [di + hmcbSignature], "MS"
	jne .error_dump		; no -->
	cmp word [di + hmcbOwner], 0
	jne .next
	cmp word [di + hmcbSize], bx
	jbe .next
	lea ax, [di + HMCB_size]; -> memory block
	mov bx, word [di + hmcbSize]
.next:
	mov cx, word [di + hmcbNext]
				; cx -> next
	jcxz .end		; was last ? -->
	cmp cx, di		; valid next offset ?
	jbe .error_dump		; not valid -->
	xchg di, cx		; di -> next, cx -> prior
	jmp .loop		; go loop
.end:
	xchg di, ax		; di -> largest first free HMA memory block
	test bx, bx		; (NC)
	jnz .done
.error:
	xor bx, bx		; bx = 0
	mov di, ds		; es:di will be all-1s
	stc			; signal error
.done:
	 push ds
	 pop es
 assume es:nothing
	pop cx
	pop ds
 assume ds:nothing
	pop ax
	retn

.error_dump:
 extern doscode_halt_query
 extern doscode_disp_msg_cs
 extern doscode_disp_al
 extern doscode_disp_al_hex
 extern doscode_disp_ax_hex
 extern doscode_disp_blank
 extern doscode_msg.hmcb.1
 extern doscode_msg.hmcb.3
 extern doscode_msg.hmcb.4
 extern doscode_msg.hmcb.5
 extern doscode_msg.hmcb.7
 extern doscode_msg.hmcb.first
 extern doscode_msg.empty
 extern doscode_msg.error_common
 extern doscode_msg.crlf

	push si
	push ax
	mov si, doscode_msg.error_common	; access with cs
	call doscode_disp_msg_cs
	mov si, doscode_msg.hmcb.1
	call doscode_disp_msg_cs
	mov ax, cx
	call DumpHMCB
	mov si, doscode_msg.hmcb.3
	call doscode_disp_msg_cs
	mov ax, di
	call DumpHMCB
	mov si, doscode_msg.empty
 extern flag_no_halt_hmcb
	mov ax, flag_no_halt_hmcb
	call doscode_halt_query
	pop ax
	pop si
	jmp .error


DumpHMCB:
	push cx
	call doscode_disp_ax_hex
	mov si, doscode_msg.hmcb.first
	test ax, ax
	jz .end
	mov si, ax
	call doscode_disp_blank
	lodsb
	xchg al, ah
	lodsb
	xchg al, ah		; sign
	call doscode_disp_ax_hex
	call doscode_disp_blank
	call doscode_disp_blank
	mov cx, 3
@@:
	lodsw			; owner, size, next
	call doscode_disp_ax_hex
	call doscode_disp_blank
	loop @B
	call doscode_disp_blank
	mov cl, 8
@@:
	lodsb			; name
	call doscode_disp_al_hex
	call doscode_disp_blank
	loop @B

	mov cl, 8
	sub si, cx
	mov al, '"'
	call doscode_disp_al
@@:
	lodsb			; name
	inc ax			; 7Fh to 80h, 20h to 21h
	cmp al, 32		; 80h..FFh..20h ?
	jle @F			; yes, non-printable-ASCII, end -->
	dec ax			; restore
	call doscode_disp_al
	loop @B
@@:
	mov si, doscode_msg.hmcb.4
.end:
	call doscode_disp_msg_cs
	pop cx
	retn


		; INP:	word [DOSDATA:first_hmcb] set
		;	 and cs == DOSCODE_HMA_SEGMENT
		;	 if DOS manages the HMA
		; OUT:	ds = FFFFh
		;	bx = 0
		;	NC if DOS manages the HMA,
		;	 ds:di -> first HMCB, allocated to DOS (owner = -2)
		;	 A20 is assumed on because our cs is in the HMA
		; CHG:	ds, di, bx, ax
getfirsthmcb:
	call doscode_getdosdata
	mov ds, ax		; => DOSDATA
 assume ds:DOSGROUP
	mov di, cs		; => DOSCODE
	mov ax, DOSCODE_HMA_SEGMENT
				; expected DOSCODE segment
	cmp di, ax		; is it ?
	mov di, [first_hmcb]	; ds:di -> first HMCB, if any
	mov bx, -1
	mov ds, bx		; => HMA
 assume ds:nothing
	inc bx			; = 0, preserve CF !
	jb @F			; if not managing HMA -->
	cmp word [di + hmcbOwner], ax
				; NC (ae) if valid, CY (b) if invalid
@@:
	retn			; CY if DOS is not in the HMA
%endif


entry	DosGetGroup
 assume ds:nothing
	push ax
	call doscode_getdosdata
	mov ds, ax
 assume ds:DOSGROUP
	pop ax
	return

	entry	DOSInstall
 assume ds:nothing
	MOV	AL,0FFh
	return
EndProc INT2F_etcetera

%ENDIF
;Input: same as ABSDRD and ABSDWRT
;	 ES:BP -> DPB
;Functions: convert 32bit absolute RW input parms to 16bit input parms
;Output: carry set when CX != -1 and drive is more than 32mb
;	 carry clear, parms ok
;
Procedure   RW32_CONVERT,NEAR
 ASSUME DS:NOTHING,ES:NOTHING,SS:DOSGROUP
	CMP	CX,-1			     ;>32mb  new format ?		;AN000;
	JZ	new32format		     ;>32mb  yes			;AN000;
	PUSH	AX			     ;>32mb  save ax			;AN000;
	PUSH	DX			     ;>32mb  save dx			;AN000;
	MOV	AX,[ES:BP + dpb_max_cluster]   ;>32mb  get max cluster #		;AN000;
	MOV	DL,[ES:BP + dpb_cluster_mask]  ;>32mb				;AN000;
		; lDOS note: very suspicious, test intended ?
	CMP	DL,0FEH 		     ;>32mb  removable ?		;AN000;
	JZ	letold			     ;>32mb  yes			;AN000;
	mov dh, 0
	inc dx				; sectors per cluster
	MUL	DX			     ;>32mb  dx:ax= max sector #	;AN000;
	test	DX,DX			     ;>32mb  > 32mb ?			;AN000;
letold:
	POP	DX			     ;>32mb  retore dx			;AN000;
	POP	AX			     ;>32mb  restore ax 		;AN000;
	JZ	old_style		     ;>32mb  no 			;AN000;
	MOV	word [ss:IFS_DRIVER_ERR],0207H	     ;>32mb  error			;AN000;
	STC				     ;>32mb				;AN000;
	return				     ;>32mb				;AN000;
new32format:
	MOV	DX,WORD PTR [BX + SECTOR_RBA+2];>32mb				;AN000;
	MOV	[ss:HIGH_SECTOR],DX	     ;>32mb				;AN000;
	MOV	DX,WORD PTR [BX + SECTOR_RBA]  ;>32mb				;AN000;
	MOV	CX,[BX + ABS_RW_COUNT]	     ;>32mb				;AN000;
	LDS	BX,[BX + BUFFER_ADDR]	     ;>32mb				;AN000;
old_style:				     ;>32mb				;AN000;
	CLC				     ;>32mb				;AN000;
	return				     ;>32mb				;AN000;
EndProc RW32_CONVERT


;Input: None
;Functions: Purge Fastopen/seek Cache Buffers
;Output: None
;
;
Procedure   Fastxxx_Purge,NEAR
 ASSUME DS:NOTHING,ES:NOTHING,SS:DOSGROUP
	PUSH	AX			      ; save regs.			;AN000;
	PUSH	SI								;AN000;
	PUSH	DX								;AN000;
FastSeekflg equ FastSeekFlg	; NASM port label
	TEST	byte [ss:FastSeekflg],Fast_yes	      ; fastseek installed ?		;AN000;
	JZ	topen			      ; no				;AN000;
	MOV	AH,FastSeek_ID		      ; set fastseek id 		;AN000;
	JMP	SHORT dofast		      ; 				;AN000;
topen:
FastOpenflg equ FastOpenFlg	; NASM port label
	TEST	byte [ss:FastOpenflg],Fast_yes	      ; fastopen installed ?		;AN000;
	JZ	nofast			      ; no				;AN000;
	MOV	AH,FastOpen_ID		      ; set fastseek installed		;AN000;
dofast:
	MOV	AL,FONC_purge		      ; purge				;AN000;
	MOV	DL,[ES:BP + dpb_drive]	      ; set up drive number		;AN000;
	invoke	Fast_Dispatch		      ; call fastopen/seek		;AN000;
nofast:
	POP	DX								;AN000;
	POP	SI			      ; restore regs			;AN000;
	POP	AX			      ; 				;AN000;

	return				      ; exit				;AN000;
EndProc Fastxxx_Purge


 extern amis_intlist, amis_sign
		; INP:	ah = our multiplex number (others don't go here)
		;	al = function number
		;	ss:sp -> iret stack frame
relocated i2D, amis
 assume ds:nothing, es:nothing, ss:nothing
	cmp al, 0
	je .installationcheck
		; function 1: private entrypoint, not supported
		; function 2: uninstall, not supported
		; function 3: request pop-up, not supported
	cmp al, 4
	je .determineinterrupts
		; function 5: get hotkeys, not supported
	cmp al, 6
	je .getdevice
	cmp al, 10h
	je .getver
	cmp al, 11h
	je checkid
	mov al, 0
	iret

.installationcheck:
	mov al, 0FFh
	mov cx, 500h + 38		; version number
	mov di, amis_sign + DOSENTRYADJUSTOFFSET
.iret_dx_seg:
	mov dx, DOSENTRY - DOSENTRYADJUSTSEGMENT
	iret

.determineinterrupts:
	mov bx, amis_intlist + DOSENTRYADJUSTOFFSET
	jmp .iret_dx_seg

.getver:
	mov al, 0FFh
	mov cx, 500h + 26
	iret

 extern CONHEADER
.getdevice:
	mov ax, 12
		; low byte is number of device headers.
		; (5 * AUX, 4 * PRN, CON, CLOCK, block)
		; ! hardcoded here
		; flags in high byte:
		; 100h = program loaded from config file
		; 200h = device headers not linked to DOS device chain
		; 400h = reentrant devices
		; rest undefined
	mov dx, seg CONHEADER + DOSENTRYDEVICEBASE
	mov bx, CONHEADER - DOSENTRYDEVICEBASE * 16
	iret


		; INP:	ah = multiplex number
		;	al = 11h
		;	dx:si -> AMIS id structure, consisting of:
		;	 word	reserved, zero
		;	 word	id sequential number
		;	 byte	string length
		;	 N byte	id string
		; OUT:	al = 00h if function not supported
		;	al >= 0F0h if id accepted,
		;	 bx = cx = dx = reserved, 0 for now
		;	else id rejected
		;	 bx = cx = dx = reserved, 0 for now
		;	ah = multiplex number
		; CHG:	al, bx, cx, dx, si, di
		;	preserves: ah, ds, es, bp
checkid:
	push ds
	push es
	push ax			; ! ax must be last in the stack frame
	mov ds, dx
 assume ds:nothing
	mov ax, [si + aidSequential]
				; ax = sequential number
	lea si, [si + aidString]
				; ds:si -> requested AMIS id string
	 push cs
	 pop es
 assume es:DOSCODEGROUP
	mov bx, idtable		; es:bx -> our table
.loop:
	mov di, [es:bx + idtString]
				; es:di -> counted string
	xor cx, cx
	mov cl, byte [si]	; cx = length
	inc cx			; = length including count byte
	push si
	repe cmpsb		; text match ?
	pop si			; restore -> id string
	jne .next		; no -->
	lea di, [bx + idtMinimumSeq]
	scasw			; compare ax, idtMinimumSeq
	jb .below_pop
	scasw			; compare ax, idtMaximumSeq
	pop ax
	ja .above
	mov al, 0F0h		; accepted
	jmp .ret

.next:
	add bx, IDTABLE_size	; -> next table entry
	cmp bx, idtable.end	; was last ?
	jb .loop		; no, try next -->
.notfound:
	pop ax
	mov al, 0E0h		; id not found
.ret:
	xor bx, bx
	xor dx, dx
	xor cx, cx		; clear reserved registers
	pop es
	pop ds			; restore segregs from the stack frame
 assume ds:nothing, es:nothing
	iret			; bye

.below_pop:
	pop ax
	mov al, 0E1h		; id is below accepted
	jmp .ret

.above:
		; no pop ax here
	mov al, 0E2h		; id is above accepted
	jmp .ret

section DOSCODETABLE

	struc AMISID
aidReserved:	resw 1
aidSequential:	resw 1
aidString:	resb 1
	endstruc

%include "idtable.asm"
