[comp.sys.ibm.pc] BIOS Disk I/O Error Interceptor

gw@sickkids.UUCP (CFI/Graham Wilson ) (08/31/88)

A while back I posted a request for information on catching DOS device
errors.  There were a few comments about interrupt 0x24.  Thanks all
for the leads.  My investigations shall now lead down that path.

Meanwhile, I made the silly mistake of mentioning that I have assembler
code to catch disk errors via BIOS calls.  A lot of requests for this
code came through.  So here it is...

**********************************************************************
**********************************************************************

;======================================================================
;
;  DISK ERROR CATCHING ASSEMBLER ROUTINES
;
;======================================================================

		public	_diskerrinit
		public	_diskerrterm

dgroup group _STACK, _DATA
assume ss:dgroup, ds:dgroup

_STACK segment word stack 'STACK'
_STACK ends

_DATA segment word public 'DATA'
_DATA ends

_TEXT segment byte public 'CODE'
assume cs:_TEXT

handler_ds	DW	?
handler_es	DW	?
save_ss		DW	?
save_sp		DW	?
local_sp	DW	?
return_ax	DW	?
handler_off	DW	?
handler_seg	DW	?
int13_off	DW	?
int13_seg	DW	?
parm_ax		DW	?
parm_bx		DW	?
parm_cx		DW	?
parm_dx		DW	?
parm_es		DW	?

;======================================================================
;
;  Interrupt handler.  Calls the handler function as:
;
;	func(error, serv, drive, head, track, sector, nsects, farbuf)
;	int	error;		/* returned error code */
;	int	serv;		/* service (2 or 3) */
;	int	drive;		/* 0 for A, 1 for B */
;	int	head;
;	int	track;
;	int	sector;
;	int	nsects;
;	char far *farbuf;
;
;======================================================================

diskioerr	proc	far
		sti
		;
		; If not floppy or not read or write op, skip handler
		;
		cmp	dl,2
		jge	disk_skip	; jmp if not floppy
		cmp	ah,2
		jl	disk_skip
		cmp	ah,3
		jle	disk_init	; jmp if read or write
		;
		; Skip handler
		;
disk_skip:	pushf
		call	dword ptr cs:int13_off
		ret	2
		;
		; Set up for handler - save parameters
		;
disk_init:	mov	cs:parm_ax,ax
		mov	cs:parm_bx,bx
		mov	cs:parm_cx,cx
		mov	cs:parm_dx,dx
		mov	cs:parm_es,es
			;
			; Call disk I/O routine
			;
disk_int13:		pushf			; simulate int
			call	dword ptr cs:int13_off
			jc	disk_handler	; jmp if error
			ret	2		; no error - return
			;
			; Set up segments for call to handler
			;
disk_handler:		mov	cs:return_ax,ax
			push	ds
			push	es
			mov	cs:save_ss,ss
			mov	cs:save_sp,sp
			mov	ds,cs:handler_ds
			mov	es,cs:handler_es
			mov	ss,cs:handler_ds
			mov	sp,cs:local_sp
			;
			; Set up stack for call to handler
			;
			push	cs:parm_bx	; buf offset
			push	cs:parm_es	; buf segment
			mov	ax,cs:parm_ax
			mov	cl,ah
			xor	ah,ah
			push	ax		; nsects
			mov	ax,cs:parm_cx
			mov	bl,ah
			xor	ah,ah
			push	ax		; sector
			xor	bh,bh
			push	bx		; track
			mov	ax,cs:parm_dx
			mov	bl,ah
			xor	bh,bh
			push	bx		; head
			xor	ah,ah
			push	ax		; drive
			xor	ch,ch
			push	cx		; service
			mov	ax,cs:return_ax
			mov	al,ah
			xor	ah,ah
			push	ax		; error
			;
			; Call handler
			;   Return codes (AX):  0 - try again
			;                       1 - ignore
			;                       2 - return with error
			;
			call	dword ptr cs:handler_off
			;
			; Restore segments
			;
			mov	sp,cs:save_sp
			mov	ss,cs:save_ss
			pop	es
			pop	ds
			or	al,al
			je	disk_again	; jmp if try again
			dec	al
			je	disk_ignore	; jmp if ignore error
			;
			; Return with error
			;
			mov	ax,cs:return_ax
			stc			; error
			ret	2		; simulate iret
			;
			; Ignore error
			;
disk_ignore:		xor	ah,ah
			clc			; no error
			ret	2		; simulate iret
			;
			; Reset drive and try again
			;
disk_again:		mov	ax,cs:parm_ax	; get drive
			mov	ah,0		; service 0
			mov	dx,cs:parm_dx
			pushf			; simulate ret
			call	dword ptr cs:int13_off
			mov	ax,cs:parm_ax
			mov	bx,cs:parm_bx
			mov	cx,cs:parm_cx
			mov	dx,cs:parm_dx
			mov	es,cs:parm_es
			jmp	disk_int13
diskioerr	endp

;======================================================================
;
;  void diskerrinit(func, sp)
;  int (far * func)();
;  char *sp;
;
;  This routine initializes the BIOS disk I/O error catcher.  It passes
;  a far pointer to a function to execute.  The second parameter points
;  to the top of the secondary stack.
;
;======================================================================

_diskerrinit	proc
		push	bp
		mov	bp,sp
		;
		; Save handler address & data segment pointer
		;
		mov	ax,[bp+argbase]
		mov	cs:handler_off,ax
		mov	ax,[bp+argbase+2]
		mov	cs:handler_seg,ax
		mov	ax,[bp+argbase+4]
		mov	cs:local_sp,ax
		;
		mov	cs:handler_ds,ds
		mov	cs:handler_es,es
		;
		; Get BIOS disk I/O interrupt
		;
		mov	al,13h
		mov	ah,35h		; DOS service
		int	21h		; get interrupt vector
		mov	cs:int13_seg,es	; save
		mov	cs:int13_off,bx
		;
		; Set BIOS disk I/O interrupt
		;
		push	ds		; save data seg
		mov	ax,cs
		mov	ds,ax		; set ds == cs
		mov	al,13h
		mov	ah,25h		; DOS service
		mov	dx,offset diskioerr
		int	21h		; set interrupt vector
		pop	ds
		mov	sp,bp
		pop	bp
		ret
_diskerrinit	endp

;======================================================================
;
;  void diskerrterm()
;
;  This routine un-initializes the BIOS disk I/O error catcher.
;
;======================================================================

_diskerrterm	proc
		push	bp
		mov	bp,sp
		;
		; Reset BIOS disk I/O interrupt
		;
		push	ds		; save data seg
		mov	al,13h
		mov	ah,25h		; DOS service
		mov	ds,cs:int13_seg
		mov	dx,cs:int13_off
		int	21h		; reset interrupt vector
		pop	ds
		mov	sp,bp
		pop	bp
		ret
_diskerrterm	endp

_TEXT ends

end

**********************************************************************
**********************************************************************

The following sample C program shows how it would be used:

------------------------------------------------------------

main()
{
	int far errhandler();
	char *sp;
	FILE *fp;

	sp = alloca(1000) + 1000;	/* sp points to TOP of stack */
	diskerrinit(errhandler,sp);
	fp = fopen("testfile","w");
	fprintf(fp,"this is data\n");
	fclose(fp);
	diskerrterm();
}

errhandler()
{
	bios_print("Disk error - fix and press key\n");
	bios_get_key();
	return( 0 );	/* try again */
}

------------------------------------------------------------

Some comments and explanations are in order.

The assembler code is MASM and the C code is MSC 4.0.

The alloca() library routine is standard in MSC 4.0 and allocates
memory FROM THE STACK.  The block is deallocated when the routine
which called alloca() exits [in this case main()].  So the block
of memory is used as a stack by an error handler without having to
worry about "error 2000 Stack Overflow".

The routines bios_print() and bios_get_key() are fictional
routines which print and get a key using BIOS system calls.  
The errors occur while DOS is busy, so calling DOS while handling
an error will most likely hang the system.  BE WARNED!!!!

There are no known bugs, and it has worked flawlessly for many
months.

The error code passed to the error handler is the same as that
returned by the BIOS int 13h.

If anyone has an intelligent question, send me e-mail and I'll
try to respond.

Graham Wilson         |   watmath  allegra     decvax
CyberFluor Inc        |        |      |           |  
179 John St, #400     | uunet!utai!utgpu!utzoo!sickkids!robot!limbo!graham
Toronto, Ont, M5T 1X4 |        |          |       |
(416) 977-5450        |  ubc-vision     linus  research