/* * Common hardware independant code for a hard disk driver. * It is included via a * * #include "common.c" * * in the driver file (ie: at.c, ps2.c ps2esdi.c, omti.c, ...) *//* Define driver entry codes */#define READ_BLK		0#define WRITE_BLK		1#define FORMAT			2/* Flag bits for extended disk table */#define DISK_LARGE_EXTENTS	0x0001#define DISK_LARGE_BLOCKS	0x0002#define DISK_LARGE_FSYS		0x0004#define DISK_NEEDS_FLUSH	0x0008/* QNX port to signal when on an interrupt */#define DISKEVENT	6		/* QNX port to signal */#asm   "DISKEVENT = 6"#asm   "SIGNAL = 71h"		/* Signal interrupt vec *//* *	Header Information (16 bytes)" *	NOTE: This must be the first thing in seg 1 */#asm "		seg 1"#asm "		export <hard_disk>"#asm "<hard_disk>:"#asm "	w	data	DISK_TYPE"#asm "	w	data	<Disk_init>"#asm "	w	data	<Blk_read>"#asm "	w	data	<Blk_write>"#asm "	w	data	<Format>"#asm "	w	data	<Multi_sector>"#asm "	w	data	<Control>"#ifdef NEW_DRIVER#asm "	w	data	<User>"#else#asm "		rmb 2"#endif/* Default disk table parameters (16 bytes). */#asm "b data DISK_TYPE"#ifdef PHYS_DRIVE_SPEC	#asm "b data PHYS_DRIVE"					/* physical drive */#else	#asm "b data 0"								/* physical drive */#endif#asm "w data 0"									/* block offset */#asm "w data 0"									/* sectors/disk */#asm "w data 0"									/* sectors/cylinder */#asm "b data 0"									/* sectors/track */#asm "b data 1"									/* Sector base */#asm "w data IO_PORT"							/* I/O Port */#asm "b data DISK_INT"							/* Interrupt */#asm "w data 0"									/* number of tracks */#asm "b data 0"									/* Control field *//* Extended default disk table parameters (16 more bytes) */#asm "w data 0"				/* Top word of offset */#asm "w data 0"				/* Top word of size */#asm "w data 0"				/* write_precomp */#asm "w data 0"				/* Number of pages */#asm "w data 0"				/* Blks per page */#asm "w data 1"				/* Write behind */#asm "w data 000bh"			/* Flags, flush, request large blocks and large xtnts */#asm "w data 1234h"			/* Signature for extension */#asm " seg 3"/* * The next three structures are in fsys and must be referenced off the * extra segment. They are the templates for the two 16 byte w data's above. */struct disk_entry {	char disk_type;	char disk_drv;	unsigned blk_offset;	unsigned num_sctrs;	unsigned sctr_cyl;	char sctr_trk;	char sctr_base;	unsigned ctl_addr;	char ctl_int;	unsigned num_tracks;	char disk_filler[1];	} ;struct ext_disk_entry {	unsigned ext_blk_offset;	unsigned ext_num_sctrs;	unsigned write_precomp;	unsigned disk_arg[3];	unsigned disk_flags;	unsigned disk_entry_id;	} ;struct activity_entry {	unsigned char user_opcode;		/* FILL, EMPTY, SEEK, CLOSE, OPEN */	unsigned char file_mode;		/* READ, WRITE */	unsigned filenumber;			/* -1 is internal operation */	unsigned activity_flags;		/* SEEKING, USE_CACHE, BITMAP, DIRECTORY search */									/* READING_BLOCK, WRITING_BLOCK */									/* accessing 1ST BLOCK of EXTENT */	unsigned user_requested_bytes;	/* From Fget, normally 512 */	long block_count;				/* Probable # sequential blocks (0 == unknown) */	unsigned activity_spare[16];	unsigned activity_ftseg;		/* segment containing file table */	char *activity_timeout;			/* Pointer to flush timer variable */	};/* * A block relative to a QNX partition is decomposed into the * following structure. */struct bdata_entry {	long block;			/* Relative to start of disk 1...n		*/	unsigned drive;		/* Physical drive 0 or 1				*/	unsigned track;	unsigned head;	unsigned sector;	} ;struct disk_size_entry {	unsigned sectors_per_cyl;	unsigned sectors_per_trk;	unsigned sector_base;	} ;/* * Two structures usefull for getting at pieces of 16/32 bit values. */struct {	unsigned char wlow;	unsigned char whigh;	} ;struct {	unsigned llow;	unsigned lhigh;	} ;struct disk_entry *disktab;			/* Pointer into FSYS off ES	*/struct ext_disk_entry *extdisktab;	/* Pointer into FSYS off ES	*/struct activity_entry *acttab;		/* Pointer into FSYS off ES	*/struct bdata_entry  bdata, fbdata;	/* These cannot be local variables	*/unsigned ctl_addr;char atp;char large_fsys;#ifdef CACHE#include "cache.c"char cache_allocated[MAX_NDRIVES];#endifchar ctrlr_initialized;char initialized[MAX_NDRIVES];struct disk_size_entry disk_size[MAX_NDRIVES];	/* Used to break BLK -> TRK, HEAD, SECTOR *//* * Initialize the hard disk controller. * This is called when the disk is mounted.  It may be called again so * only initalize the hardware on the first call for a physical drive. * dtab		- Pointer to the base of fsys's disk table. * index	- Which entry in the table to use (QNX drive - 1). * edtab	- Pointer to the base of fsys's extended disk table. */disk_init( dtab, index, edtab, atab )struct disk_entry *dtab;unsigned index;struct ext_disk_entry *edtab;struct activity_entry *atab;	{	register struct disk_entry *dp;	register struct ext_disk_entry *edp;	unsigned drive;	int i, rc;	dp = disktab = dtab;		/* Save in disktab for later calls		*/	dp += index;	edp = extdisktab = edtab;	/* Save in extdisktab for later calls	*/	edp += index;	acttab = atab;				/* Save in acttab for later calls		*/	ctl_addr = dp-}ctl_addr;	if(task_info(0, 6) >= 300) atp = 1;		/* Protected mode QNX 3.0	*//* * If O/S does not support large FSYS, * turn off flag bits for large extents and large blocks as well * just in case some utilities don't check the large fsys bit first. */	if( !(task_info(0, 4) & 0x0080) ) {		large_fsys = 0;		edp-}disk_flags = 0;		}	else		large_fsys = 1;		/*	Has the controller been initialized ? */	if (! ctrlr_initialized) {#ifdef NEW_DRIVER		if(rc = ctrlr_init(dp-}ctl_addr, dp-}ctl_int))			return(rc);#else		attach( DISKEVENT );		if(init_int(dp-}ctl_int))			return(-2);#endif		ctrlr_initialized = 1;	}	/*	Invoke the low lever debugger in testing */	/*	asm("int 73h"); */	/*	Has the specific device been initialized ? */	if ( initialized[ drive = dp-}disk_drv & 0x07 ] ) return( 0 );#ifdef CACHE	if (! cache_allocated[drive]) {		if(edp-}disk_arg[0] == 0  ||  edp-}disk_arg[0] > MAX_NPAGES)			edp-}disk_arg[0] = NPAGES;		if(edp-}disk_arg[1] == 0  ||  edp-}disk_arg[1] > MAX_PAGESIZE)			edp-}disk_arg[1] = PAGESIZE;		if(alloc_cache(drive, edp-}disk_arg[0], edp-}disk_arg[1], STUPID_DMA)) return(-1);	}	cache_allocated[drive] = 1;#endif#ifdef NEW_DRIVER	rc = device_init(drive, dp, edp);#else	rc = driver_init(drive, dp, edp);#endif	if (! rc)		initialized[drive] = 1;	return(rc);	}/* * Read a block (512 bytes) from the disk. * drv	- QNX drive number 1 to 7. * blk	- Relative to partition start 1..n. * seg	- Segment to put the data. * off	- Offset within the segment. */blk_read( drv, blk, off, seg )	unsigned drv, off, seg;	long blk;	{#ifdef CACHE	register struct cache_entry *cp;	register struct activity_entry *ap = acttab;	unsigned n, stat;#endif	if(map_blk(drv, blk, &bdata))		return(-1);	map_thn(&bdata);#ifdef CACHE	n = look_cacher(bdata.drive, bdata.block);	cp = cache_ptr;	if(n) {		transfer(seg, off, cp->cache_seg, cp->cache_off + (n-1)*512, 256);		return(0);		}	cp->start_blk = bdata.block;	if(ap-}activity_flags & 0x03)	/* Seeking or bitmap with BMCACHE mounted */		cp->num_blks = 1;	else if(n = ap-}block_count)		if(n < cp->num_blks)			cp->num_blks = n;				if(stat = driver_rw(READ_BLK, &bdata, cp->cache_off, cp->cache_seg, cp->num_blks)) {		cp->num_blks = 1;		if(stat = driver_rw(READ_BLK, &bdata, cp->cache_off, cp->cache_seg, cp->num_blks)) {			cp->num_blks = 0;			return(stat);			}		}	transfer(seg, off, cp->cache_seg, cp->cache_off, 256);	return(0);#else	return(driver_rw(READ_BLK, &bdata, off, seg, 1));#endif	}/* * Write a block (512 bytes) from to disk. * drv	- QNX drive number 1 to 7. * blk	- Relative to partition start 1..n. * seg	- Segment to get the data. * off	- Offset within the segment. */blk_write( drv, blk, off, seg )unsigned drv, off, seg;long blk;	{#ifdef CACHE	register struct cache_entry *cp;	register struct activity_entry *ap = acttab;	unsigned n, coff, stat, wrbehind;#endif	if(map_blk(drv, blk, &bdata))		return(-1);	map_thn(&bdata);#ifdef CACHE	n = look_cachew(bdata.drive, bdata.block);	cp = cache_ptr;	if(n) {		transfer(cp->cache_seg, coff = cp->cache_off + (n-1)*512, seg, off, 256);		}	else {		transfer(cp->cache_seg, coff = cp->cache_off, seg, off, 256);		cp->start_blk = bdata.block;		cp->num_blks = 1;		cp->filenum = ap-}filenumber;		n = 1;		}	if(ap-}filenumber == -1)		wrbehind = 0;	else if(ap-}activity_flags & 0x08)		wrbehind = ap-}activity_flags & 0x04;	else		wrbehind = (extdisktab+drv-1)-}disk_arg[2];			if(wrbehind == 0) {		if((stat = driver_rw(WRITE_BLK, &bdata, coff, cp->cache_seg, 1)) == 0)			cp->dirty_blks &= ~(1 << (n-1));		return(stat);		}	if(@acttab-}activity_timeout == 0)		 @acttab-}activity_timeout = 1 * 20;	/* 1 second */	return(0);#else	return(driver_rw(WRITE_BLK, &bdata, off, seg, 1));#endif	}/* * Transfer n blocks (512 bytes) from the disk. * drv		- QNX drive number 1 to 7. * blk		- Relative to partition start 1..n. * seg		- Segment to put the data. * off		- Offset within the segment. * rw		- Read_BLK=0/Write_BLK=1 transfer. * nblks	- Number of blocks to transfer */multi_sector(drv, blk, off, seg, rw, nblks)	unsigned drv, off, seg, rw, nblks;	long blk;	{	unsigned stat;	if(map_blk(drv, blk, &bdata))		return(-1);	map_thn(&bdata);	if(nblks > 255) /* limit I/O to 255 blocks */		nblks = 255;	stat = driver_rw(rw, &bdata, off, seg, nblks);	if(stat == 0)		return(nblks);	return(-1);	}/* * A control function to the driver. * func			- Function code. *					1 - Flush activity timeout. *					2 - Flush file. *					3 - Flush all. * drv			- QNX drive number 1 to 7. * off			- Offset of a data structure in the file system. */control(func, drv, off)unsigned func, drv, off;	{#ifdef CACHE	register struct disk_entry *dp;	unsigned drive, dirt;	dp = disktab + (drv-1);	drive = dp-}disk_drv & 0x07;	if(func == 1)		dirt = flush_cache( 0, -1 );	else if(func == 2)		dirt = flush_cache( 0, @(unsigned *)off);	else if(func == 3)		dirt = flush_cache( 1, -1 );	else if(func == 4)		dirt = flush_write_behind( drive, 1, -1 );	else if(func == 5)		dirt = flush_read_ahead( drive, -1 );	if(dirt) @acttab-}activity_timeout = 1 * 20;	/* 1 second */#endif	return( 0 );	}#ifdef NEW_DRIVER/* * A user defined function to the driver. * func			- Function code. *					1 - Send a SCSI command * drv			- QNX drive number 1 to 7. * off			- Offset of a data structure in the file system. */user(func, drv, off)unsigned func, drv;register char *off;	{	if (func == 1) {		off-}mtype = driver_scsi(off-}r_w_off, off-}r_w_seg);	}	else		off-}mtype = ER_USERFUNC;	return(1);	}#endif#ifdef CACHE/* * Flush blocks in a cache entry.  This is called by the cache code. * drive	- Physical drive. * blk		- Relative to start of the entire disk 1..n. * off		- Offset within the segment. * seg		- Segment to get the data. * nblks	- Number of blocks to write. */flush_blks(drive, blk, off, seg, nblks)	unsigned drive, off, seg, nblks;	long blk;	{	register unsigned i;	fbdata.drive = drive;	fbdata.block = blk;	map_thn(&fbdata);	if(driver_rw(WRITE_BLK, &fbdata, off, seg, nblks)) {		for(i = 0 ; i < nblks ; ++i) {		/* Attempt one at a time */			fbdata.block = blk + i;			map_thn(&fbdata);			driver_rw(WRITE_BLK, &fbdata, off + (512*i), seg, 1);			}		}	}#endif/* * Map a selector to a 24 bit physical address. */long seg_to_phys(seg)unsigned seg;	{	long addr;	struct seg_descriptor_entry {		unsigned	seg_limit;		unsigned	seg_base_lo;			/* bits 0  to 15 */		char		seg_base_hi;			/* bits 16 to 23 */		char		seg_flags;		unsigned	intel_reserved;			/* always zero */		} *p;	struct {		unsigned llow;		unsigned lhigh;		} ;	asm("push es");	asm("mov ax,#1*8");			/* selector 1 is GDT */	asm("mov es,ax");	p = seg & 0xfff8;	addr.llow  = p-}seg_base_lo;	addr.lhigh = p-}seg_base_hi;	asm("pop es");	return(addr);	}/* * Convert block number relative to the start of a QNX partition * to a physical location on the disk. * * Rely on the fact that the extra-segment points to the * data segment of the file system. */map_blk( drv, blk, bdata_ptr )	long blk;	unsigned drv;	struct bdata_entry *bdata_ptr;	{	register struct bdata_entry *bp = bdata_ptr;	register struct disk_entry *dp;	register struct ext_disk_entry *edp;	long l;	--drv;	/* QNX drives start at one, table starts at zero */	dp = disktab + drv;	edp = extdisktab + drv;	if(dp-}disk_type != DISK_TYPE)	return(-1); 	/* Limit blk to lie within this partition */	l.llow = dp-}num_sctrs;	l.lhigh = edp-}ext_num_sctrs;	if(blk <= 0  ||  blk > l)		return(-1);	bp->block.llow = dp-}blk_offset;	bp->block.lhigh = edp-}ext_blk_offset;	bp->block += blk - 1;	bp->drive = dp-}disk_drv & 0x07;	return(0);	}/* * Now that we have the physical block from the start of the disk * (blk 0) we can break it into a track,head and sector. Not all * controllers may need to know this (IE: SCSI sends block #'s to * the controller). */voidmap_thn( bdata_ptr )struct bdata_entry *bdata_ptr;	{	register struct bdata_entry *bp = bdata_ptr;					/* In DI */	register struct disk_size_entry *sp = &disk_size[bp->drive];	/* In SI */	if(sp->sectors_per_cyl == 0)	/* Controller takes Blocks directly	*/		return;						/* and does not need T, H and N		*/	asm("mov ax,0[di]");			/* Get absolute blk (from 0)		*/	asm("mov dx,2[di]");	asm("div 0[si]");				/* Divide by sectors/cylinder		*/	asm("mov 6[di],ax");			/* Save away track					*/	asm("mov ax,dx");				/* Now div remainder to get head	*/	asm("xor dx,dx");	asm("div 2[si]");				/* Divide by sectors/track			*/	asm("mov 8[di],ax");			/* Save away head					*/	asm("add dx,4[si]");			/* Add sector base to remainder		*/	asm("mov 10[di],dx");			/* Save away sector					*/	} /********************************************************** *              Support routines                           * **********************************************************/#asm "		seg 1"#asm "<Disk_init>:"#asm "		call	prologue"#asm "		push ds"#asm "		push es"#asm "		mov ax,ds"#asm "		mov es,ax"#asm "		mov ds,cs:0"#asm "		push 22[bp]"#asm "		push 20[bp]"#asm "		push 18[bp]"#asm "		push 16[bp]"#asm "		call <disk_init>"#asm "		add sp,#8"#asm "		pop	es"#asm "		pop ds"#asm "		jmp	Epilogue"#asm "<Blk_write>:		"#asm "		call	prologue"#asm "		push ds"#asm "		push es"#asm "		mov ax,ds"#asm "		mov es,ax"#asm "		mov ds,cs:0"#asm "		push 22[bp]"#asm "		push 20[bp]"#asm "		push 24[bp]"#asm "		push 18[bp]"#asm "		push 16[bp]"#asm "		call <blk_write>"#asm "		add sp,#10"#asm "		pop	es"#asm "		pop ds"#asm "		jmp	Epilogue"#asm "<Blk_read>:		"#asm "		call	prologue"#asm "		push ds"#asm "		push es"#asm "		mov ax,ds"#asm "		mov es,ax"#asm "		mov ds,cs:0"#asm "		push 22[bp]"#asm "		push 20[bp]"#asm "		push 24[bp]"#asm "		push 18[bp]"#asm "		push 16[bp]"#asm "		call <blk_read>"#asm "		add sp,#10"#asm "		pop	es"#asm "		pop ds"#asm "		jmp	Epilogue"#asm "<Format>:		"#asm "		call	prologue"#asm "		push ds"#asm "		push es"#asm "		mov ax,ds"#asm "		mov es,ax"#asm "		mov ds,cs:0"#asm "		push 24[bp]"#asm "		push 22[bp]"#asm "		push 20[bp]"#asm "		push 18[bp]"#asm "		push 16[bp]"#asm "		call <driver_format>"#asm "		add sp,#10"#asm "		pop	es"#asm "		pop ds"#asm "		jmp	Epilogue"#asm "<Multi_sector>:"#asm "		call	prologue"#asm "		push ds"#asm "		push es"#asm "		mov ax,ds"#asm "		mov es,ax"#asm "		mov ds,cs:0"#asm "		push 26[bp]"#asm "		push 24[bp]"#asm "		push 22[bp]"#asm "		push 20[bp]"#asm "		push 28[bp]"#asm "		push 18[bp]"#asm "		push 16[bp]"#asm "		call <multi_sector>"#asm "		add sp,#14"#asm "		pop	es"#asm "		pop ds"#asm "		jmp	Epilogue"#asm "<Control>:"#asm "		call	prologue"#asm "		push ds"#asm "		push es"#asm "		mov ax,ds"#asm "		mov es,ax"#asm "		mov ds,cs:0"#asm "		push 20[bp]"#asm "		push 18[bp]"#asm "		push 16[bp]"#asm "		call <control>"#asm "		add sp,#6"#asm "		pop	es"#asm "		pop ds"#asm "		jmp	Epilogue"#ifdef NEW_DRIVER#asm "<User>:"#asm "		call	prologue"#asm "		push ds"#asm "		push es"#asm "		mov ax,ds"#asm "		mov es,ax"#asm "		mov ds,cs:0"#asm "		push 20[bp]"#asm "		push 18[bp]"#asm "		push 16[bp]"#asm "		call <user>"#asm "		add sp,#6"#asm "		pop	es"#asm "		pop ds"#asm "		jmp	Epilogue"#endif#asm "prologue:"#asm "		pop	ax		;save return address in ax"#asm "		push bp"#asm "		push bx"#asm "		push cx"#asm "		push dx"#asm "		push si"#asm "		push di"#asm "		mov	bp,sp"#asm "		push ax"#asm "		ret		;go back to the function with everything saved"#asm "Epilogue:"#asm "		mov	sp,bp"#asm "		pop	di"#asm "		pop	si"#asm "		pop	dx"#asm "		pop	cx"#asm "		pop	bx"#asm "		pop	bp"#asm "	abs	ret"#asm "epilogue:"#asm "		mov	sp,bp"#asm "		pop	di"#asm "		pop	si"#asm "		pop	dx"#asm "		pop	cx"#asm "		pop	bx"#asm "		pop	bp"#asm "		ret"#asm "<await>:"#asm "		mov ax,#6"#asm "		int 70h"#asm "		ret"#asm "<attach>:"#asm "		mov ax,#4"#asm "		int 70h"#asm "		ret"#asm "<task_info>:"#asm "		mov ax,#14"#asm "		int 70h"#asm "		ret"#asm "<alloc_mem>:"#asm "		mov ax,#31"#asm "		int 70h"#asm "		ret"#asm "<create_seg>:"#asm "		mov ax,#33"#asm "		int 70h"#asm "		ret"#asm "export <set_int_vec>"#asm "<set_int_vec>:"#asm "		mov ax,#28"#asm "		int 70h"#asm "		ret"#asm "export <set_int_mask>"#asm "<set_int_mask>:"#asm "		mov ax,#27"#asm "		int 70h"#asm "		ret"#fast io_inio_in(port) {	asm("	mov dx,4[bp]");	asm("b	in [dx]");	asm("	xor ah,ah");	}#fast io_outio_out(port, data) {	asm("	mov dx,4[bp]");	asm("	mov al,6[bp]");	asm("b	out [dx]");	asm("	xor ah,ah");	}#fast io_winio_win(port) {	asm("	mov dx,4[bp]");	asm("w	in [dx]");	}#fast io_woutio_wout(port, data) {	asm("	mov dx,4[bp]");	asm("	mov ax,6[bp]");	asm("w	out [dx]");	asm("	xor ah,ah");	}#fast my_dsunsignedmy_ds() {	asm("	mov ax,ds");	}#fast my_esunsignedmy_es() {	asm("	mov ax,es");	}#fast my_csunsignedmy_cs() {	asm("	mov ax,cs");	}#fast set_extra_segmentset_extra_segment(seg) {	asm("	mov es,4[bp]");	}transfer(dst_seg, dst_off, src_seg, src_off, nbytes) {	asm("	push es");	asm("	push ds");	asm("	mov es,14[bp]");	asm("	mov di,16[bp]");	asm("	mov ds,18[bp]");	asm("	mov si,20[bp]");	asm("	mov cx,22[bp]");	asm("	cld");	asm("	rep movw");	asm("	pop ds");	asm("	pop es");	}/* * vector the hardware interrupt to our own interrupt handler * and enable the proper interrupt (in table) */init_int(hint)int hint;	{	extern void int_handler();	unsigned mask;		/*	**** this must be the first automatic **** */	int status = -1;#ifdef NEW_DRIVER	/*	This does not work before version 2.12pcat because of a bug in set_int_vec */	asm("cli");	if(!set_int_vec(hint|0x4000, my_cs(), &int_handler, 0))		if(!set_int_mask(1, hint))			status = 0;	asm("sti");	return(status);#else	register struct vec_entry {		unsigned vec_off;		unsigned vec_seg;		char	 vec_reserved;	/* These two are used in protected mode only */		char	 vec_flags;		} *vp;	asm("cli");	asm("push es");	if(atp) {		set_extra_segment(0x10);			/* Point to IDT	*/		vp = 8 * (hint + 0xe8);		vp-}vec_flags = 0x86;				/* Interrupt gate	*/		}	else {		set_extra_segment(0);		vp = 4 * (hint + (hint >= 8 ? 0xf0-8 : 0x08));		}	vp-}vec_seg = my_cs();	vp-}vec_off = &int_handler;	/*	Enable the interrupt */	mask = 1 << hint;	mask = ~mask;	if(hint >= 8) {		asm("b in 0a1h");		asm("and al,-1[bp]");			/* Hi-byte of 'mask' */		asm("jmp .");		asm("b out 0a1h");		asm("b in 21h");		asm("and al,#0fbh");			/* Int 2 on master */		asm("jmp .");		asm("b out 21h");		}	else {		asm("b in 21h");		asm("and al,-2[bp]");			/* Lo-byte of 'mask' */		asm("jmp .");		asm("b out 21h");	}	asm("pop es");	asm("sti");	return(0);#endif	}/* * For debugging only */#ifdef DEBUGint row = 0, col = 0;voiddisplay_msg( text )	char *text;	{	register char *sp, *dp;	sp = text;	asm("		push es");	while(*sp) {		if(*sp == '\n') {			++row;			col = 0;			}		else if(*sp == 0x0c)	row = col = 0;		else {			if(col >= 80) {				++row;				col = 0;				}			if(row >= 25)	row = 0;			dp = (row * 80 + col) * 2;			if(atp)				asm("mov ax,#28h");			else				asm("mov ax,#0b000h");			asm("mov es,ax");			@dp = *sp;			@(dp+1) = 0x0f;			if(atp)				asm("mov ax,#30h");			else				asm("mov ax,#0b800h");			asm("mov es,ax");			@dp = *sp;			@(dp+1) = 0x04;			++col;			}		++sp;		}	asm("		pop es");	}		char buf1[6];voiddisplay_hex( value )	unsigned value;	{	register int i;	for(i = 3 ; i >= 0 ; --i, value >>= 4)		buf1[i] = "0123456789ABCDEF"[value & 0xf];	buf1[4] = ' ';	buf1[5] = '\0';	display_msg( buf1 );	}voiddisplay_pause( n )	unsigned n;	{	unsigned i;	while( n-- ) for( i = 0xffff ; i ; --i ) ;	}#endif#asm "		export TEMP"#asm "		export SAVE"#asm "		seg 3"#asm "TEMP:"#asm "		rmb 40"#asm "SAVE:"#asm "		rmb 2"