;       SCCSID = @(#)dinfo.asm	6.2 91/05/08
SCCSID	equ	"@(#)dinfo.asm	6.2 91/05/08"
	PAGE	,132
TITLE	DISK_INFO - Internal Get Disk Info
NAME	DISK_INFO

;
; Low level routine for returning disk drive information from a local
; or NET device.
;
;	DISK_INFO
;
;   Modification history:
;
;	30 Mar 1983   ARR   Initial version.
;	 7 Oct 1986   BWS   Added fast disk free space code.

;
.xlist
include dosseg.inc
include defdbug.inc
include basemaca.inc
include osmaca.inc
include fnofs.inc
include oldmaca.inc
.xcref
include buffer.inc
include curdir.inc
include dossymc.inc
include dpb.inc
include error.inc
include tcbdata.inc
include mult.inc
include mi.inc
include protmode.inc
include vpb.inc
.cref
.list

	ModName , FNO_DINFO_ASM

	ReferTask	CurrTCB,WORD
;### PTR B720732 	ReferTask	ThisCDS,DWORD
	ReferTask	ExtErr_Locus,BYTE

	ReferGDT	GDT_VPB,WORD

	extrn	UnPack:NEAR

	EXTRNFAR	GetBuffrB
	EXTRNFAR	DecVPBRef
	EXTRNFAR	IncVPBRef
	EXTRNFAR	RlseBuffr
	EXTRNFAR	WhatVolume


	FARCODE HIGH4
HIGH4CODE SEGMENT
    ASSUME  SS:TASKAREA


Break <DISK_INFO - Get Disk Drive Information>
;***	DISK_INFO - Get Disk Drive Information
;	DISK_INFO_NoCDS - entry point when we already have dpb
;
;	The most interesting information obtained is the number of
;	free allocation units.	For local requests, a check is made
;	to see whether a valid count is available.  If not, the FAT
;	is scanned, looking for free clusters.
;
;	DISK_INFO_NoCDS is to be called via the far16 DISK_INFO_NoCDS
;	procedure (see below).
;
;	ENTRY	[THISCDS] Points to the Macro List Structure of interest
;			(It MAY NOT be NUL, error not detected)
;			(DISK_INFO only)
;		ds:si -> dpb (DISK_INFO_NoCDS only)
;
;	EXIT	DX = Number of free allocation units
;		BX = Total Number of allocation units on disk
;		CX = Sector size
;		AL = Sectors per allocation unit
;		AH = FAT ID BYTE
;		Carry set if error (currently user FAILed to I 24)
;
;	USES	All but DS
;
;	PSEUDOCODE
;	      WhatVolume
;	      IF error
;		 return
;	      IncVPBRef
;	      UnPack
;	      IF error
;		 return
;	      IF invalid free count in VPB
;		 IF !slowfreespace
;		    DiskFreeSpace
;		 ELSE
;		    Unpack
;	      return values
;
Procedure   DISK_INFO,<near,faronly>
	SegAssert SS,<DS>
    ASSUME  DS:TASKAREA, ES:NOTHING


	MOV	[ExtErr_Locus],ERRLOC_DISK

;
; Determine volume in drive; we may have buffered data for it.
;
	mov     di,TASKAREA:CurrTCB	        ; (di) = current TCB     ;### PTR B720732
	les	di,TASKAREA:[di].TCBThisCDS     ; (es:di) = CDS pointer  ;### PTR B720732
;### PTR B720732 	les	di,ThisCDS
	lds	si,es:[di].cd_devptr
    assume	ds:nothing

Entry DISK_INFO_NoCDS,near,local	; already have dpb

	SaveReg <si,ds>
	CALLFAR WhatVolume
	RestoreReg  <es,di>
	TaskContext <ds>

	JC	Crit_Leave

	mov	bp,es:[di].dpb_hVPB
	VPBContext es,bp
	CALLFAR IncVPBRef		; prevent VPB from evaporating under us

	SaveReg <di>
	XOR	BX,BX			; (BX) = 0
	call	UnPack			; Get FAT ID byte into DI
	mov	ax,di
	RestoreReg <di>
	JC	Crit_Leave_Free
	xchg	al,ah			; (AH) = FAT ID byte

	chkvselhd es,bp
	MOV	CX,ES:[bp].vpb_fsd.vpdFAT_max_cluster
;
; Examine the current free count.  If it indicates that we have an invalid
; count, do the expensive calculation.
;
	MOV	DX,ES:[bp].vpb_fsd.vpdFAT_free_cnt ; get free count
	CMP	DX,-1			; is it valid?
	JZ	DoScan
;
; Check to see if it is in a reasonable range.	If so, trust it and return.
; Otherwise, we need to blast out an internal error message and then recompute
; the count.
;
	CMP	DX,CX			; is it in a reasonable range?
	JB	GotVal			; yes, trust it.
	DBPRT	<>,0,<Internal error: MaxClus <= FreeClus\n>

DoScan:

IFNDEF SlowFreeSpace

	CALL	DiskFreeSpace
	JC	Crit_Leave_Free		; Error
	MOV	BX,ES:[bp].vpb_fsd.vpdFAT_max_cluster

ELSE ; SlowFreeSpace

	mov	bx, 2			; kcd 6/13/86
	XOR	DX,DX
	DEC	CX
SCANFREE:
	invoke	UnPack
	JC	Crit_Leave_Free
	JNZ	NOTFREECLUS
	INC	DX			; A free one
NOTFREECLUS:
	INC	BX			; Next cluster
	LOOP	SCANFREE
	DEC	BX			; BX was next cluster.	Convert to...
ENDIF; SlowFreeSpace

ReturnVals:
	DEC	BX			; ...count
	chkvselhd es,bp
	MOV	AL,ES:[bp].vpb_fsd.vpdFAT_cluster_mask
	INC	AL			; Sectors/cluster
	MOV	CX,ES:[bp].vpb_fsi.vpi_cbSector ; Bytes/sector

IFNDEF	ExerciseBuf			; Define to cause rescan every time
	MOV	ES:[bp].vpb_fsd.vpdFAT_free_cnt,DX
ENDIF
	CLC

Crit_Leave_Free:
	pushf
	CALLFAR DecVPBRef
	popf

Crit_Leave:
	ret
;
; We have correctly computed everything previously.  Load up registers for
; return
;
GotVal: MOV	BX,CX			; get cluster count
	JMP	ReturnVals

EndProc DISK_INFO

; far16 interface point for DISK_INFO_NoCDS

Procedure DISK_INFO_NoCDS,<far16>
	call	DISK_INFO_NoCDS
	RETP
EndProc DISK_INFO_NoCDS

IFNDEF SlowFreeSpace

BREAK <DiskFreeSpace - Get number of free clusters on disk>
;***	DiskFreeSpace - Get number of free clusters on disk
;
;	Read the FAT one sector at a time.  For each FAT sector, count the
;	number of zero entries.  This is easy for 16-bit FATs, since an entry
;	never crosses a sector boundary.  For 12-bit FATs, there are three
;	possible states:
;
;	    0) entry starts at beginning of sector
;	    1) first nibble of entry is in previous sector
;	    2) first byte of entry is in previous sector
;
;	We use a transition table (dfsStateMap) to control the state, and
;	a count table (dfsCount) to know how many entries to scan for each
;	state.
;
;	For 16-Bit FATs, we start in state 0 and never leave it.  For 12-Bit
;	FATs, we start in state 0 but the transition table depends on the
;	sector size.  So, initialize the transition table and the count table
;	at procedure entry.
;
;
;	ENTRY:	(ES:BP) = VPB
;
;	EXIT:	(DX) = Number of free allocation units
;
;	USES:	DX

    ASSUME  DS:NOTHING,ES:NOTHING

Procedure   DiskFreeSpace,near

	SaveReg <AX,BX,CX,DI,SI>
	SaveReg <DS>

	MOV	BX,BP			; (BX) = offset of VPB

	LocalVar    dfsVPBOffset,WORD	; Offset portion of VPB pointer
	LocalVar    dfsLWO,WORD		; Offset in sector of last word
	LocalVar    dfsSector,WORD	; Sector of FAT to read
	LocalVar    dfsFat16,BYTE	; TRUE => 16-bit FAT
	LocalVar    dfsStateMap,6	; State transition table
	LocalVar    dfsCount,6		; State count table

	EnterProc

	MOV	dfsVPBOffset,BX		; Save VPB pointer offset

	MOV	AX,ES:[BX].vpb_fsd.vpdFAT_first_FAT
	MOV	dfsSector,AX		; First sector of FAT

	MOV	AX,ES:[BX].vpb_fsi.vpi_cbSector ; (AX) = bytes per sector
	MOV	DX,AX
	DEC	DX
	DEC	DX			; (DX) = offset of last word in sector
	MOV	dfsLWO,DX		; Save for fetching last word

	MOV	DX,ES:[BX].vpb_fsd.vpdFAT_max_cluster ; (DX) = max cluster + 1
	XOR	BX,BX			; (BX) = 0 (count of free clusters)
	CMP	DX,4096-10		;
	JB	dfs1			; 12-Bit FAT

;*  16-Bit FAT initialization

	SHR	AX,1			    ; (AX) = cluster entries per sector
	MOV	WORD PTR dfsCount[0],AX	    ; Count[0] = c;
	MOV	WORD PTR dfsStateMap[0],BX  ; StateMap[0] = 0;
	MOV	dfsFat16,-1		    ; 16-Bit FAT
	JMP	dfs1_9			    ; Finish initialization

;*  12-Bit FAT initialization

dfs1:	MOV	dfsFat16,BL		; 12-Bit FAT

	SaveReg <DX>			; Save Max clusters + 1
	SHL	AX,1			; (AX) = nibbles per sector
	MOV	DX,BX			; (DX:AX) = nibbles per sector
	MOV	CX,3			; (CX) = nibbles per 12-Bit FAT entry
	DIV	CX			; (AX) = FAT entries per sector
					; (DX) = spare nibbles per sector
	DEC	DX			; Set flags
	RestoreReg <DX>			; (DX) = Max clusters + 1

	JZ	dfs1_2			; 1 spare nibble per sector
	JG	dfs1_5			; 2 spare nibbles per sector

;*  0 spare nibbles in each FAT sector
;
;   NOTE:   This is highly unlikely, as this means that the sector size is
;	    a multiple of 1.5.	However, the code is small, so it doesn't
;	    hurt us to be robust.

	MOV	WORD PTR [dfsStateMap.0],BX ; StateMap[0] = 0;
	JMP	dfs1_7

;*  1 spare nibble in each FAT sector

dfs1_2: MOV	WORD PTR dfsStateMap[0],2  ; StateMap[0] = 1;
	MOV	WORD PTR dfsStateMap[2],4  ; StateMap[1] = 2;
	MOV	WORD PTR dfsStateMap[4],BX ; StateMap[2] = 0;
	MOV	WORD PTR dfsCount[2],AX	   ; Count[1] = c;
	JMP	dfs1_7

;*  2 spare nibbles in each FAT sector

dfs1_5: MOV	WORD PTR dfsStateMap[0],4  ; StateMap[0] = 2;
	MOV	WORD PTR dfsStateMap[2],BX ; StateMap[1] = 0;
	MOV	WORD PTR dfsStateMap[4],2  ; StateMap[2] = 1;
	INC	AX			   ; (AX) = c+1;
	MOV	WORD PTR dfsCount[2],AX	   ; Count[1] = c+1;
	DEC	AX			   ; (AX) = c;

;*  Fill in rest of Count array

dfs1_7: MOV	WORD PTR dfsCount[0],AX ; Count[0] = c;
	INC	AX			; (AX) = c+1;
	MOV	WORD PTR dfsCount[4],AX ; Count[2] = c+1;

;*  NOTE: We scan all of the entries of the FAT, even though the first two
;	  entries are not for clusters.  This works because these entries
;	  are always non-zero.	This removes the necessity of having a
;	  special case for the first sector of the FAT.

dfs1_9: INC	DX			; (DX) = max cluster + 2

	MOV	DI,BX			; (DI) = Initial state (0)

;*  (BX) = free clusters
;   (DX) = clusters left to scan
;   (DI) = state
;   (ES) = VPB segment

dfs2:	SaveReg <BX,CX,DX,DI,SI,BP,DS>
	MOV	AL,0			; (AL) = 0 ==> must read sector
	MOV	SI,1			; (SI) != 0 ==> read FAT sector
	MOV	DX,dfsSector		; (DX) = sector to read
; BUGBUG - This is OK for now since the FAT is at the beginning of the disk
	xor	cx,cx			; High word of sector # is 0
	MOV	BP,dfsVPBOffset		; (ES:BP) = VPB ptr
	CALLFAR GetBuffrB		; Get FAT sector into a buffer
	RestoreReg <DS,BP,SI,DI,DX,CX,BX>
    ASSUME  DS:NOTHING
	JC	dfsx			; Read failed

	MOV	SI,TASKAREA:CurrTCB	; (SI) = current TCB
	LDS	SI,TASKAREA:[SI].TCBCurBuf ; (DS:SI) = buffer with FAT sector
	ADD	SI,BUFINSIZ		; (DS:SI) = data area of buffer

	INC	dfsSector		; Increment sector for next iteration

	XCHG	AX,CX			; (AX) = last word of prev FAT sector
	MOV	CX,WORD PTR dfsCount[DI] ; (CX) = entries per FAT sector
	SaveReg <SI>
	TEST	dfsFat16,-1		; Set (ZF) according to FAT type
	CALL	CountClusters
	RestoreReg <SI>

	ADD	SI,dfsLWO		; (DS:SI) = last word of FAT sector
	MOV	CX,DS:[SI]		; (CX) = last word of FAT sector, used
					;	 by next iteration
	MOV	DI,WORD PTR dfsStateMap[DI] ; (DI) = next state

	OR	DX,DX
	JNZ	dfs2			; More clusters to count

	MOV	DX,BX			; (DX) = free clusters

dfsx:	PUSHF				; Save error flag
	CALLFAR RlseBuffr		; Free buffer
	POPF

	LeaveProc

	RestoreReg  <DS>
	RestoreReg  <SI,DI,CX,BX,AX>

	ret

EndProc	    DiskFreeSpace


BREAK <CountClusters - Count free clusters>
;***	CountClusters - Count free clusters
;
;	Count the number of zero entries in a FAT sector.  We stop after
;	scanning the entire sector or when we come to the end of the FAT.
;
;
;	ENTRY:	(AX)	= Last word of previous FAT sector
;		(BX)	= Number of free clusters counted so far
;		(CX)	= Cluster to scan (max for this FAT sector)
;		(DX)	= Number of clusters left to scan (total)
;		(DI)	= FAT state (always 0 for 16-Bit FAT)
;			     0 = Cluster starts on word boundary
;			     2 = 1st nibble of cluster is in *Last*
;			     4 = 1st byte of cluster is in *Last*
;		(DS:SI) = Buffer with FAT sector
;		(ZF)	= Clear => 12-bit FAT
;			  Set	=> 16-bit FAT
;
;		CLD - direction flag is clear
;
;	EXIT:	(BX)	= updated number of free clusters
;		(DX)	= updated number of clusters remaining to scan
;
;	USES:	AX,BX,CX,DX,SI

    ASSUME  DS:NOTHING,ES:NOTHING

Procedure   CountClusters,near

	JZ	cc4			; 12-Bit FAT counting

;*  16-Bit FAT counting
;
;   (CX) = cluster entries per FAT sector
;   (DX) = number of clusters entries still to be scanned

	CMP	CX,DX
	JBE	cc1			; Scan entire FAT sector

	MOV	CX,DX			; Scan remaining clusters

cc1:	SUB	DX,CX			; (DX) = clusters remaining to scan

cc2:	LODSW				; (AX) = cluster id
	OR	AX,AX
	JNZ	cc3			; Not a free cluster

	INC	BX			; Count free cluster

cc3:	LOOP	cc2			; More to scan

	JMP	ccx			; Go to common exit


;*  12-Bit FAT counting
;
;   (DX) = max cluster count
;   (DI) = FAT state

cc4:	CMP	CX,DX
	JBE	cc5			; Scan full FAT sector

	MOV	CX,DX			; Scan remaining clusters

cc5:	SUB	DX,CX			; (DX) = clusters remaining to scan
	CMP	DI,2			; Set flags based on State

;*  Jump to state handler
;   (BX)    = count of free clusters
;   (CX)    = number of clusters to scan
;   (DX)    = number of clusters remaining to scan (returned to caller)
;   (DI)    = FAT state
;   (DS:SI) = ptr next bit of FAT sector to read

	JB	cc6			; State 0
	JE	cc9			; State 2  (AX) = C...

;*  State 4: low-order byte in high byte of (AX)
;
	LODSB				; (AX) = 23C1
	XCHG	AH,AL			; (AX) = C123
	JMP	cc7			; Enter common code

;*  State 0: 12-bit entry aligned at start of FAT sector
;
cc6:	LODSW				; (AX) = C123
cc7:	TEST	AX,0FFFh
	JNZ	cc8			; Not a free cluster

	INC	BX			; Count free cluster

cc8:	DEC	CX			;
	JCXZ	ccx			; Done scanning

;*  State 2: low-order nibble in high nibble of (AX)
;
cc9:	LODSB				; (AX) = C.AB
	ROL	AX,4			; (AX) = .ABC
	TEST	AX,0FFFh
	JNZ	cc10			; Not a free cluster

	INC	BX			; Count free cluster

cc10:	LOOP	cc6			; More to scan

;*  (BX) = count of free clusters found so far
;   (DX) = number of clusters left to scan

ccx:	ret				; Return to caller

EndProc CountClusters

ENDIF ;SlowFreeSpace

HIGH4CODE ENDS
	END
