 /********************************************************\  *              IBM XT Hard Disk Driver                   * *                                                        * * Copyright (c) Quantum Software Systems Ltd. 1983,1986  * *               (UNPUBLISHED)                            * \********************************************************//* * Define physical Drive characteristics for 10 Mb hard disk */#define NUM_CYLINDERS	306#define NUM_HEADS		4#define SCTRS_PER_TRACK	17#asm "NUM_CYLINDERS		= 306"#asm "NUM_HEADS			= 4"#asm "SCTRS_PER_TRACK	= 17"/* * Note: Only DMA Channels 1 or 3 may be used * * Further: DMA Channel 1 can only be used in 1st 64K *          (QNX guarantees that FSYS is in 1st 64K) */#define DMA_CH			3/* * Define index within QNX driver table (0..7) * * By convention, 0 = floppy *                1 = hard disk *                2 = ramdisk *                3-7 are for custom drivers * *	Each driver supports one or more QNX drives (1..8) */#define TYPE_HARD	  1#asm   "TYPE_HARD	= 1"/* * Internal constants */#define DMA				0#define HARD_DISK_EVENT	6#define FORMAT			2#define FBLK_SIZE		512/* * Hard Disk I/O assignments (offsets from controller base address) */#define DATA			0#define STATUS			1#define RESET			1#define SELECT			2#define CONTROL			3/* * Control Port Bit assignments */#define DMA_ENABLE		0x01		/* 1 = enable */#define INT_ENABLE		0x02		/* 1 = enable *//* * Control field is used on an XT to define stepping rate * *	0 - Drive default (3 msec)		4 - 200 usec *	1 - N/A							5 - 70 usec (buffered) *	2 - N/A							6 - 3 msec *	3 - N/A, 17 usec on some drvs	7 - 3 msec */#asm "CONTROL_FIELD = 5"/* * Status Bits */#define REQUEST			0x01#define DIRECTION		0x02#define COMMAND_MODE	0x04#define BUSY			0x08#define DRQ				0x10#define IRQ				0x20/* * Sense of Status bit when TRUE */#define REQUEST_ON		REQUEST#define DIRECTION_IN	DIRECTION#define COMMAND_MODE_ON	COMMAND_MODE#define BUSY_ON			BUSY#define	DRQ_ON			DRQ			/*	DRQ is raised whenever REQUEST										is raised in DATA mode */#define IRQ_ON			IRQ			/*	interrupts are whenever REQUEST										is raised and direction is IN										to host in COMMAND mode *//* * Xebec Controller Commands */#define RESTORE			0x01#define FORMAT_TRACK	0x06#define READ_SECTOR		0x08#define	WRITE_SECTOR	0x0a#define SENSE_STATUS	0x03#define TEST_READY		0x00#define INITIALIZE		0x0c#define	ERROR_MASK		0x02/* * Constants for imbedded assemble code */#asm "BLK_OFFSET		= 2"#asm "NUM_SCTRS			= 4"#asm "SCTR_CYL			= 6"#asm "SCTR_TRK			= 8"#asm "SCTR_BASE			= 9"#asm "EXT_BLK_OFFSET	= 0"#asm "EXT_NUM_SCTRS		= 2" /*****************************************************\ *	Header Information (16 bytes)"                     * *	NOTE: The following data must be 1st data in file  * \*****************************************************/#asm "		seg 1"#asm "		export <hard_disk>"#asm "<hard_disk>:"#asm "	w	data	TYPE_HARD"		/* Index in driver table */#asm "	w	data	<Disk_init>"#asm "	w	data	<Blk_read>"#asm "	w	data	<Blk_write>"#asm "	w	data	<Format>"#asm "		rmb 6"/* *	default disk table parameters (16 bytes)" */#asm "b data TYPE_HARD"#asm "b data 0"											/* physical drive */#asm "w data 0"											/* block offset */#asm "w data NUM_CYLINDERS*NUM_HEADS*SCTRS_PER_TRACK"	/* sectors/disk */#asm "w data NUM_HEADS*SCTRS_PER_TRACK"					/* sectors/cylinder */#asm "b data SCTRS_PER_TRACK"							/* sectors/track */#asm "b data 0"											/* Sector base */#asm "w data 320h"										/* I/O Port */#asm "b data 5"											/* Interrupt */#asm "w data NUM_CYLINDERS"								/* number of tracks */#asm "b data CONTROL_FIELD"								/* 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"#asm "w data 0"#asm "w data 0"#asm "w data 0003h"			/* Flags, request large blocks and large xtnts */#asm "w data 1234h"			/* Signature for extension *//* * Local Variables */#asm " seg 3"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 control_field;	} ;struct ext_disk_entry {	unsigned ext_blk_offset;	unsigned ext_num_sctrs;	unsigned write_precomp;	char disk_ext_filler[6];	unsigned disk_flags;	unsigned disk_entry_id;	} ;struct xebec_parameters {	unsigned num_cylinders;	char     num_heads;	unsigned reduced_write_cylinder;	unsigned write_precomp_cylinder;	char     ecc_data_burst_length;	} ;static struct disk_entry *disktab;static struct ext_disk_entry *extdisktab;static int intflag = 0;static int large_fsys;static unsigned ctl_addr;static unsigned drive;static unsigned track, head, sector;static unsigned count;static char mode = 0;static char atp;static unsigned precomp[2];static unsigned control_field[2];static unsigned control;static unsigned mask_off;static unsigned mask_on;static char buf[16];struct {	unsigned char lo;	unsigned char hi;	} ;struct {	unsigned llow;	unsigned lhigh;	} ;/* * Initialize the hard disk controller. * *  disk_init() is called once, when the driver is mounted. * *  The MOUNT command reads this core file into memory, and causes FSYS *  to call disk_init() with pointers to disk_table[]. The values in *  disk_table[] are initially the ones in the template at the beginning *  of this file, but are over-ridden with any mount options. By the time *  disk_init() is called, all values have been defined. */static char initialized[2];static disk_init(dtab, index, edtab)struct disk_entry *dtab;unsigned index;	 /* 0..7 */struct ext_disk_entry *edtab;	{	register struct disk_entry *dp;	struct ext_disk_entry *edp;	struct xebec_parameters *xp;	int i;	char drv;/* 	This code had to be moved up to here because	where it was initially was after some	atp checks were made	MAS 8/11/88*//* * Test for Protected mode operation (QNX 3.xx) */	if(task_info(0, 6) >= 300)		atp = 1;/* * Memorize addresses of the disk_table[] and ext_disk_table[] * in the data space of FSYS (es:) */	disktab  = dtab;	extdisktab = edtab;/* * Calculate pointers to the entries corresponding to current drive */	dp = disktab + index;	edp = extdisktab + index;/* * Put physical drive and I/O controller address in global variables */	drive = dp-}disk_drv & 0x1;	ctl_addr = dp-}ctl_addr;/* * If we are re-mounting the disk, then we only have to reprogram drive parms */	if(initialized[drive] != 0)		goto drive_parms;/*	The setting of atp was originally here,	too far down MAS 08/11/88*//* * 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) == 0) {		large_fsys = 0;		edp-}disk_flags = 0;		}	else		large_fsys = 1;/* * Enable the hardware if its the 1st time in */	if(initialized[0] == 0  &&  initialized[1] == 0) {		attach(HARD_DISK_EVENT);		mask_off = 1 << (dp-}ctl_int);		mask_on = ~mask_off;		init_int(dp-}ctl_int + 8, mask_on);		}/* * Use "user_data" as optional control field parameter */	control_field[drive] = dp-}control_field;/* * Older File Systems may not support extended disk parameters, check for  * valid structure, then get write precomp value if defined */	if(edp-}disk_entry_id == 0x1234) {		precomp[drive] = edp-}write_precomp;		}/* * disable interrupt and DMA for now */drive_parms:	io_out(ctl_addr + CONTROL, 0);/* * Reset Controller */	buf[0] = INITIALIZE;	buf[1] = (drive << 5);	buf[2] = 0;	buf[3] = 0;	buf[4] = 0;	buf[5] = control_field[drive];/* * Set up parameter block for the controller */	xp = &buf[6];	xp->num_cylinders = swap_bytes(dp-}num_tracks);	if(dp-}sctr_trk)		xp->num_heads = dp-}sctr_cyl/dp-}sctr_trk;	else		xp->num_heads = NUM_HEADS;	xp->reduced_write_cylinder = swap_bytes(dp-}num_tracks);	xp->write_precomp_cylinder = precomp[drive];	xp->ecc_data_burst_length  = 11;/* * send the Initialize command and wait for status (no interrupt) */	send_command(14);	wait_results(0);	initialized[drive] = 1;	}/* * Read a block (512 bytes) from the disk corresponding to * the block number (1..NUM_BLKS) into memory in segment 'seg' * at address 'dbuf' */static blk_read(drv, blk, dbuf, seg, ext_blk) {	drive = drv - 1;	if(map_sector(drive, blk, ext_blk)) /* results in head, track, sector */		return(-1);	mode = READ_SECTOR;	count = 1;	return(r_w_blk(dbuf, seg, 1));	}/* * Write a block (512 bytes) to the disk corresponding to * the block number (1..NUM_BLKS) from memory in segment 'seg' * at address 'dbuf' */static blk_write(drv, blk, dbuf, seg, ext_blk) {	drive = drv - 1;	if(map_sector(drive, blk, ext_blk)) /* results in head, track, sector */		return(-1);	mode = WRITE_SECTOR;	count = 1;	return(r_w_blk(dbuf, seg, 1));	}static r_w_blk(dbuf, seg, use_dma) {	unsigned stat;	register char *p;	int i;	buf[0] = mode;	buf[1] = (drive << 5) | head;	buf[2] = ((track >> 2) & 0xc0) | sector;	buf[3] = track & 0xff;	buf[4] = count;				/* num sectors */	buf[5] = control;/* * Disable hard disk interrupt */	asm("cli");	asm("b in 21h");	asm("jmp .");	asm("or al,<mask_off>");	asm("b out 21h");	asm("sti");/* * Setup DMA mode if required */	if(use_dma) {		/*		 * It seems some adapters GLITCH the DMA lines,		 * so it is necessary to hold off enabling the CPU DMA controller		 * until AFTER the card is enabled		 */		io_out(ctl_addr + CONTROL, DMA_ENABLE | INT_ENABLE);		dma_setup(seg, dbuf, mode);		}	else {		io_out(ctl_addr + CONTROL, INT_ENABLE);		}/* * Send the command to the controller */	send_command(6);/* * Inform interrupt handler than interrupts are allowed now */	intflag = 1;/* * Enable hard disk interrupt now */	asm("cli");	asm("b in 21h");	asm("jmp .");	asm("and al,<mask_on>");	asm("b out 21h");	asm("sti");/* * Wait for interrupt from controller with result of operation */	stat = wait_results(1);/* * Disable interrupts and DMA so another card can use them */	io_out(ctl_addr + CONTROL, 0);	io_out(DMA+10, DMA_CH | 0x04);		/* Set mask register */	if(stat) { /* An error occurred, lets find out why */		buf[0] = SENSE_STATUS;		send_command(6);		i = 0;		while((io_in(ctl_addr + STATUS) & BUSY) == BUSY_ON) { /* read all data */			stat = getbyte();			if(i < 16)				buf[i++] = stat;			}		stat = buf[0] & 0x3f;		if(stat == 0)			stat = -1;		return(stat);		}	return(0);	}format(drv, trk, hd, sctrs, stagger)unsigned drv, trk, hd, sctrs;char *stagger;	{	unsigned stat;	struct disk_entry *dp;	char *p;	dp = disktab;	dp += (drv - 1);	if(sctrs != 17)		return(-2);	mode   = FORMAT_TRACK;	drive = dp-}disk_drv & 0x01;	ctl_addr = dp-}ctl_addr;	track  = trk;	head   = hd;	sector = 0;	control = control_field[drive] | 0x80; /* suppress retries *//* * Determine stagger factor from table passed by fdformat */	p = stagger + 1;	count = 1;	while(count < 16  &&  @p++ != 2)		++count;	stat = r_w_blk(stagger, my_es(), 1);	return(stat);	}/* * Wait for controller to be un-busy, then select the controller, * and send out a 'n' byte message to controller * * NOTE: that in some cases, it has been recorded to take as long *       as 6 msec. to get the attention of the drive/controller */static send_command(n)int n;	{	int i;	while((io_in(ctl_addr + STATUS) & BUSY) == BUSY_ON)		;	io_out(ctl_addr + SELECT, 0);	while((io_in(ctl_addr+STATUS) & (BUSY|REQUEST|DIRECTION)) != (BUSY_ON|REQUEST_ON))		;	/* wait */	for(i = 0; i < n; ++i)		putbyte(buf[i]);	}/* * Wait for Disk controller to request a byte, then output * the next byte of a message */static putbyte(data)unsigned data;	{	while((io_in(ctl_addr + STATUS) & REQUEST) != REQUEST_ON)		 ;	io_out(ctl_addr + DATA, data);	}/* * Wait for Disk controller to send a byte, then input the byte */static getbyte()	{	unsigned i;	while(1) {		i = io_in(ctl_addr + STATUS);		if((i & BUSY) != BUSY_ON)			return(0);		if((i & (REQUEST|DIRECTION)) == (REQUEST_ON|DIRECTION_IN))			return(io_in(ctl_addr + DATA));		}	}/* * Setup DMA channel to transfer data to/from memory */struct gtd_ent {	unsigned gdt_limit;	unsigned gdt_base_low;	unsigned char gdt_base_high;	unsigned char gdt_flags;	unsigned gdt_spare;	};static dma_setup(seg, buffer, mode)unsigned seg, buffer, mode;	{	long addr;	struct gdt_ent *p;	if(mode == FORMAT_TRACK)		return;	if(atp) {		asm("push es");		asm("mov ax,#8"); /* access GDT */		asm("mov es,ax");		p = seg;		addr.lhigh = p-}gdt_base_high;		addr.llow = p-}gdt_base_low;		addr += buffer; /* rely on fact that 64K boundary is NOT crossed */		asm("pop es");		}	else {		addr = (((long) seg) << 4) + buffer;		}	asm("cli");	io_out(DMA+10, DMA_CH | 0x04);		/* Set mask register */	/*	 * clear First/Last FF	 */	io_out(DMA+12, 0);	/*	 * Setup DMA address	 * assume FSYS is always in the first 64K	 */	io_out(DMA+(DMA_CH*2), addr.llow.lo);	io_out(DMA+(DMA_CH*2), addr.llow.hi);	io_out(DMA_CH == 1 ? 0x83 : 0x82, addr.lhigh.lo);	/*	 * clear First/Last FF	 */	io_out(DMA+12, 0);	/*	 * read 512 bytes	 */	io_out(DMA+(DMA_CH*2+1), 0xff);	io_out(DMA+(DMA_CH*2+1), 0x01);	/*	 * Setup DMA transfer mode	 */	io_out(DMA+11, (mode == WRITE_SECTOR) ? 0x48 + DMA_CH : 0x44 + DMA_CH);	/*	 * Enable DMA channel	 */	io_out(DMA+10, DMA_CH);	asm("sti");	}/* * Block until a disk interrupt occurs. * When the interrupt occurs, return with status. */static wait_results(use_interrupt) {	unsigned stat;	/*	 * Wait for disk interrupt if a read or write operation	 */	if(use_interrupt)		await(HARD_DISK_EVENT);	/*	 * wait for controller to indicate status is available	 */	while((io_in(ctl_addr + STATUS) & (COMMAND_MODE | REQUEST | DIRECTION))				!= (COMMAND_MODE_ON | REQUEST_ON | DIRECTION_IN))				;	/* wait */	/*	 * mask off all but error bits	 */	stat = io_in(ctl_addr + DATA) & ERROR_MASK;	return(stat);		}/* * Convert block number into head, track, and sector * * Rely on the fact that the extra-segment points to the * data segment of the file system. 'disktab' is the address * of the disk table in that address space. */map_sector(drv, blk)long blk;int drv;	{	register struct disk_entry *dp;		/* Will be in register DI */	register struct ext_disk_entry *edp;	/* Will be in register SI */	long limit;	dp = disktab + drv;	edp = extdisktab + drv;	if(dp-}disk_type != TYPE_HARD)		return(-1);	if(dp-}sctr_cyl == 0  ||  dp-}sctr_trk == 0)		return(-1);	if(large_fsys == 0) /* O/S with small fsys give garbage in top word */		blk &= 0x0000ffffL;	limit.llow = dp-}num_sctrs;	limit.lhigh = edp-}ext_num_sctrs;	if(blk <= 0  ||  --blk >= limit)		return(-1);	asm("mov ax,BLK_OFFSET[es:di]");	asm("mov dx,EXT_BLK_OFFSET[es:si]");	asm("add ax,16[bp]");	asm("adc dx,18[bp]");	asm("div SCTR_CYL[es:di]");		/* assume 16 bit result in AX */	asm("mov <track>,ax");	asm("mov ax,dx");				/* now div remainder to get head */	asm("xor dx,dx");	asm("mov cl,SCTR_TRK[es:di]");	asm("xor ch,ch");	asm("div cx");	asm("mov <head>,ax");	asm("mov al,SCTR_BASE[es:di]");	asm("xor ah,ah");	asm("add ax,dx");				/* remainder from last div in DX */	asm("mov <sector>,ax");	drive = dp-}disk_drv & 0x01;		/* Only 2 drives supported by controller */	ctl_addr = dp-}ctl_addr;	control = control_field[drive];	return(0);	} /********************************************************** *              Support routines                           * **********************************************************/static swap_bytes(word)unsigned word;	{	return((word >> 8) | (word << 8));	}#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 20[bp]"#asm "		push 18[bp]"#asm "		push 16[bp]"#asm "		call <disk_init>"#asm "		add sp,#6"#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 24[bp]"#asm "		push 22[bp]"#asm "		push 20[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 24[bp]"#asm "		push 22[bp]"#asm "		push 20[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 <format>"#asm "		add sp,#10"#asm "		pop	es"#asm "		pop ds"#asm "		jmp	Epilogue"#asm "<my_es>:"#asm "		mov ax,es"#asm "		ret"#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"static delay() {	;	}static io_in(port) {	asm("	mov dx,14[bp]");	asm("b	in [dx]");	asm("	xor ah,ah");	}static io_out(port, data) {	asm("	mov dx,14[bp]");	asm("	mov al,16[bp]");	asm("b	out [dx]");	asm("	xor ah,ah");	}/* * Vector the hardware interrupt to our own interrupt handler * and enable the proper interrupt (in table) */static init_int(vector, port_mask) {	if(task_info(0, 6) >= 300) {			/* Protected mode QNX 3.0 */		asm("cli");		asm("push es");		asm("mov ax,#10h");		asm("mov es,ax");		asm("mov bx,14[bp]");		/* The following missing line added  MAS 08/11/88 */		asm("w add bx,#00e0h");		asm("shl bx");		asm("shl bx");		asm("shl bx");		asm("mov 0[es:bx],#diskint");		asm("mov 2[es:bx],cs");		/* The value 86 changed to 86h MAS 08/11/88 */		asm("b mov 5[es:bx],#86h");	        /* Interrupt Gate */		}	else {									/* Non-protected mode */		asm("cli");		asm("push es");		asm("mov ax,#0h");		asm("mov es,ax");		asm("mov bx,14[bp]");		asm("shl bx");		asm("shl bx");		asm("mov 0[es:bx],#diskint");		asm("mov 2[es:bx],cs");		}	asm("b in 21h");	asm("jmp .");	asm("and al,16[bp]");	asm("b out 21h");	asm("pop es");	asm("sti");	}#asm "SIGNAL = 71h"#asm "HARDDISKEVENT = 6"#asm " seg 1"#asm "diskint: "#asm " push ax"#asm " push ds"#asm " mov ds,cs:0"#asm " mov al,#20h"#asm "b out 20h"#asm "b cmp <intflag>,#0"#asm " jne sig"#asm " pop ds"#asm " pop ax"#asm " iret"#asm "sig:"#asm "b mov <intflag>,#0"#asm " pop ds"#asm " push bp"#asm " push bx"#asm " push cx"#asm " push dx"#asm " push si"#asm " push di"#asm " push es"#asm " push ds"#asm " mov ax,#HARDDISKEVENT"#asm " int SIGNAL			;signal will not return here"#asm "		export TEMP"#asm "		export SAVE"#asm "		seg 3"#asm "TEMP:"#asm "		rmb 40"#asm "SAVE:"#asm "		rmb 2"/* * Replace with an #if 0 after debugging is finished. */#if 0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