/* Etherboot routines for PCBIOS firmware.
 *
 * Body of routines taken from old pcbios.S
 */

#ifdef PCBIOS

#include "etherboot.h"
#include "realmode.h"
#include "segoff.h"

char bios_version[30] = "EthrLink ver1.1 - 29.05.2006";
#define CF ( 1 << 0 )

/**************************************************************************
CURRTICKS - Get Time
Use direct memory access to BIOS variables, longword 0040:006C (ticks
today) and byte 0040:0070 (midnight crossover flag) instead of calling
timeofday BIOS interrupt.
**************************************************************************/
#if defined(CONFIG_TSC_CURRTICKS)
#undef CONFIG_BIOS_CURRTICKS
#else
#define CONFIG_BIOS_CURRTICKS 1
#endif
#if defined(CONFIG_BIOS_CURRTICKS)
unsigned long currticks (void) 
{
	static uint32_t days = 0;
	uint32_t *ticks = VIRTUAL(0x0040,0x006c);
	uint8_t *midnight = VIRTUAL(0x0040,0x0070);

	/* Re-enable interrupts so that the timer interrupt can occur
	 */
	RM_FRAGMENT(rm_currticks,
		"sti\n\t"
		"nop\n\t"
		"nop\n\t"
		"cli\n\t"
	);	

	real_call ( rm_currticks, NULL, NULL );

	if ( *midnight ) {
		*midnight = 0;
		days += 0x1800b0;
	}
	return ( days + *ticks );
}
#endif	/* CONFIG_BIOS_CURRTICKS */


/**************************************************************************
GET_GEOMETRY - Read a NAND Flash disk geometry
**************************************************************************/
unsigned char nand_get_geometry ( int drive, int *cylinder, int *head, int *sector)
{
	unsigned long tmp;
	struct {
		reg16_t ax;
		reg16_t cx;
		reg16_t dx;
		segoff_t buffer;
	} PACKED in_stack;
	struct {
		reg16_t bx;
		reg16_t cx;
		reg16_t dx;
		segoff_t buffer;
		reg16_t flags;
	} PACKED out_stack;
	reg16_t ret_ax;

	RM_FRAGMENT(rm_pcbios_get_geometrty,
		"sti\n\t"
		"popw %ax\n\t"
		"popw %cx\n\t"
		"popw %dx\n\t"
		"popw %bx\n\t"
		"popw %es\n\t"
		"int $0x13\n\t"
		"pushfw\n\t"
		"pushw %di\n\t"
		"pushw %es\n\t"
		"pushw %dx\n\t"
		"pushw %cx\n\t"
		"pushw %bx\n\t"
		"cli\n\t"
	);

	in_stack.ax.h = 0x08; /* INT 13,8 - Read disk parameters */
	in_stack.dx.l = drive;

	ret_ax.word = real_call ( rm_pcbios_get_geometrty, &in_stack, &out_stack );
	
	*cylinder = out_stack.cx.h + ((out_stack.cx.l & 0xc0) << 2) + 1;
    *head = out_stack.dx.h  + 1;
    *sector = out_stack.cx.l & 0x3f;

/*
	//printf ("Nand geometry parameters before: C/H/S = %d/%d/%d.\n", *cylinder, *head,*sector);
	tmp = (*cylinder)*(*head)*(*sector);
	//printf ("tmp=%x.\n", tmp);

	*cylinder = ((tmp/255)/63);
	//printf ("cylinder=%x.\n", *cylinder);

	*head = 255;//((tmp /(*cylinder))/63);
	*sector = 63;//((tmp /(*cylinder))/(*head));
	
*/
	return ( out_stack.flags.word & CF ) ? ret_ax.word : 0;
}


/**************************************************************************
FLASH_FIND - Detect NAND Flash disk number
**************************************************************************/
unsigned char nand_flash_find (void)
{
	int i;
	unsigned char *drive_detect_addr;

	struct {
		reg16_t ax;
		reg16_t dx;
	} PACKED in_stack;
	struct {
		reg16_t flags;
	} PACKED out_stack;
	reg16_t ret_ax;

	RM_FRAGMENT(rm_pcbios_flash_detect,
		"sti\n\t"
		"popw %ax\n\t"
		"popw %dx\n\t"
		"int $0x13\n\t"
		"pushfw\n\t"
		"cli\n\t"
	);
    
#ifdef CM_IGLX
	
	//return (0x80 + elink.drive - 'C');
	drive_detect_addr = ((unsigned char *)phys_to_virt(DRIVE_DETECT_ADDR));
	return *drive_detect_addr;


#else
    for (i = 0x80;  i < 0x90;  i++)
    {
		int c, h, s;

		in_stack.ax.h = 0x92; /* INT 13,0x92  - Flash detection */
		in_stack.dx.l = (i & 0xFF);

		ret_ax.word = real_call ( rm_pcbios_flash_detect, &in_stack, &out_stack );
		if (ret_ax.h == 0xb8)
			return i;

		/*  The detection doesn't work in manufacturing mode*/
		if (nand_get_geometry (i, &c, &h, &s))
			continue;
		if ((s != 63) || (h != 4))	    /*	This is not Nand flash  */
			continue;
		return i;
    }
    return 0xff;
#endif
}

/**************************************************************************
FLASH_IOCTL - Sends Flash control commands
**************************************************************************/
unsigned char nand_flash_ioctl (int drive, int cmd)
{
	struct {
		reg16_t ax;
		reg16_t cx;
		reg16_t dx;
	} PACKED in_stack;
	struct {
		reg16_t flags;
	} PACKED out_stack;
	reg16_t ret_ax;

	RM_FRAGMENT(rm_pcbios_flash_ioctl,
		"sti\n\t"
		"popw %ax\n\t"
		"popw %cx\n\t"
		"popw %dx\n\t"
		"int $0x13\n\t"
		"pushfw\n\t"
		"cli\n\t"
	);

#ifndef CM_IGLX

	in_stack.ax.h		= 0x01; /* INT 13,0x01  - Flash ioctl */
	in_stack.dx.l		= (drive & 0xFF);
	switch (cmd)
	{
		/*  Sync data command  */
		case 1:
			in_stack.cx.word	= 0xB801;
			break;
		/*  Disable data flush by timer interrupt */
		case 2:
			in_stack.cx.word	= 0xB802;
			break;
		/*  Enable data flush by timer interrupt */
		case 3:
			in_stack.cx.word	= 0xB803;
			break;
		/* Read satus */
		default : in_stack.cx.word	= 0x0001;
	}


	ret_ax.word = real_call ( rm_pcbios_flash_ioctl, &in_stack, &out_stack );
#endif
	
	return 1;
}

/**************************************************************************
FLASH_READ - Read NAND Flash sector
**************************************************************************/

unsigned char nand_flash_read ( int drive, int cylinder, int head, int sector,int sector_num,char *buf ) 
{
	struct {
		reg16_t ax;
		reg16_t cx;
		reg16_t dx;
		segoff_t buffer;
	} PACKED in_stack;
	struct {
		reg16_t flags;
	} PACKED out_stack;
	reg16_t ret_ax;

	RM_FRAGMENT(rm_pcbios_flash_read,
		"sti\n\t"
		"popw %ax\n\t"
		"popw %cx\n\t"
		"popw %dx\n\t"
		"popw %bx\n\t"
		"popw %es\n\t"
		"int $0x13\n\t"
		"pushfw\n\t"
		"cli\n\t"
	);

	led_ioctl(1,2048);

	in_stack.ax.h = 2; /* INT 13,2 - Read disk sector */
	in_stack.ax.l = sector_num; /* Read one sector */
	in_stack.cx.h = (char)(cylinder & 0xff);//cylinder & 0xff;
	in_stack.cx.l = (char)(((cylinder & 0x300) >> 2) | (sector & 0x3f));//( ( cylinder >> 8 ) & 0x3 ) | sector;
	in_stack.dx.h = head;
	in_stack.dx.l = drive;
	in_stack.buffer.segment = SEGMENT ( buf );
	in_stack.buffer.offset = OFFSET ( buf );

	ret_ax.word = real_call ( rm_pcbios_flash_read, &in_stack, &out_stack );
	/*
	int i=0;
	for (i=0;i<512;i++)
		buf[i]=65;
	for (i=0;i<512;i++)
		buf[i+512]=66;
	printf ("Segment=%x, offset=%x (buf[0]=%d,buf[1]=%d).\n",SEGMENT ( buf ),OFFSET ( buf ),buf[0],buf[1]);
	*/
	return ( out_stack.flags.word & CF ) ? ret_ax.word : 0;
}


/**************************************************************************
FLASH_WRITE - Write NAND Flash sector
**************************************************************************/

unsigned char nand_flash_write ( int drive, int cylinder, int head, int sector,int sector_num,char *buf ) 
{
	struct {
		reg16_t ax;
		reg16_t cx;
		reg16_t dx;
		segoff_t buffer;
	} PACKED in_stack;
	struct {
		reg16_t flags;
	} PACKED out_stack;
	reg16_t ret_ax;

	RM_FRAGMENT(rm_pcbios_flash_write,
		"sti\n\t"
		"popw %ax\n\t"
		"popw %cx\n\t"
		"popw %dx\n\t"
		"popw %bx\n\t"
		"popw %es\n\t"
		"int $0x13\n\t"
		"pushfw\n\t"
		"cli\n\t"
	);

	//return 1 ;
	led_ioctl(1,2048);

	in_stack.ax.h = 3; /* INT 13,2 - Write disk sector */
	in_stack.ax.l = sector_num; /* Read one sector */
	in_stack.cx.h = (char)(cylinder & 0xff);//cylinder & 0xff;
	in_stack.cx.l = (char)(((cylinder & 0x300) >> 2) | (sector & 0x3f));//( ( cylinder >> 8 ) & 0x3 ) | sector;
	in_stack.dx.h = head;
	in_stack.dx.l = drive;
	in_stack.buffer.segment = SEGMENT ( buf );
	in_stack.buffer.offset = OFFSET ( buf );

	//printf ("Segment=%x, offset=%x.\n",SEGMENT ( buf ),OFFSET ( buf ));
	ret_ax.word = real_call ( rm_pcbios_flash_write, &in_stack, &out_stack );
	return ( out_stack.flags.word & CF ) ? ret_ax.word : 0;
}

/**************************************************************************
NOR_FLASH_IOCTL - Perform NOR Flash block IO
**************************************************************************/

unsigned char nor_flash_ioctl ( int function, unsigned int address,unsigned int count,char *buf) 
{
	struct {
		reg16_t ax;
		reg16_t cx;
		segoff_t buffer;
		segoff_t address;
	} PACKED in_stack;
	struct {
		reg16_t flags;
	} PACKED out_stack;
	reg16_t ret_ax;

	RM_FRAGMENT(rm_pcbios_nor_flash_ioctl,
		"sti\n\t"
		"popw %ax\n\t"
		"popw %cx\n\t"
		"popw %bx\n\t"
		"popw %es\n\t"
		"popw %si\n\t"
		"popw %di\n\t"
		"int $0x15\n\t"
		"pushfw\n\t"
		"cli\n\t"
	);

	led_ioctl(1,0);
	if (function <=0 || function >=4 )
		return 0;

	in_stack.ax.h = 0xe0; /* INT 15,e0,2 - Read nor block*/
	in_stack.ax.l = function; 
	in_stack.cx.word = count;
	in_stack.buffer.segment = SEGMENT ( buf );
	in_stack.buffer.offset = OFFSET ( buf );
	in_stack.address.segment = ((0xffff0000 & address) >> 16);
	in_stack.address.offset = (0xffff & address);

	//printf ("Segment=%x, offset=%x.\n",SEGMENT ( buf ),OFFSET ( buf ));
	ret_ax.word = real_call ( rm_pcbios_nor_flash_ioctl, &in_stack, &out_stack );
	return ( out_stack.flags.word & CF ) ? 0 : ret_ax.word;
}

/**************************************************************************
NOR_FLASH_IOCTL - Perform NOR Flash block IO
**************************************************************************/

unsigned char nor_flash_unlock () 
{
#ifdef CM_IVC3
	struct {
		reg16_t ax;
		reg16_t cx;
	} PACKED in_stack;
	struct {
		reg16_t flags;
	} PACKED out_stack;
	reg16_t ret_ax;


	RM_FRAGMENT(rm_pcbios_nor_flash_unlock,
		"sti\n\t"
		"popw %ax\n\t"
		"popw %cx\n\t"
		"int $0x15\n\t"
		"pushfw\n\t"
		"cli\n\t"
	);


	in_stack.ax.h = 0xe0; /* INT 15,e0,10 - Unlock nor flash*/
	in_stack.ax.l = 0x10; 
	in_stack.cx.word = 0x4567;
	//printf ("Segment=%x, offset=%x.\n",SEGMENT ( buf ),OFFSET ( buf ));
	ret_ax.word = real_call ( rm_pcbios_nor_flash_unlock, &in_stack, &out_stack );
#endif 

#ifdef CM_I986
	struct {
		reg16_t ax;
		reg16_t bx;
	} PACKED in_stack;
	struct {
		reg16_t flags;
	} PACKED out_stack;
	reg16_t ret_ax;

	RM_FRAGMENT(rm_pcbios_nor_flash_unlock,
		"sti\n\t"
		"popw %ax\n\t"
		"popw %bx\n\t"
		"int $0x15\n\t"
		"pushfw\n\t"
		"cli\n\t"
	);


	in_stack.ax.h = 0xe1; /* INT 15,e1,aa - Unlock nor flash*/
	in_stack.ax.l = 0xaa; 
	in_stack.bx.word = 0xabcd;
	//printf ("Segment=%x, offset=%x.\n",SEGMENT ( buf ),OFFSET ( buf ));
	ret_ax.word = real_call ( rm_pcbios_nor_flash_unlock, &in_stack, &out_stack );
#endif

#ifdef CM_IGLX
	
#endif

	return 1;
}

/**************************************************************************
NOR_FLASH_IOCTL - Perform NOR Flash block IO
**************************************************************************/

void  bios_clear_screen () 
{
	int i;
	

	struct {
		reg16_t ax;
		reg16_t bx;
		reg16_t cx;
		reg16_t dx;
	} PACKED in_stack;
	struct {
		reg16_t flags;
	} PACKED out_stack;
	reg16_t ret_ax;

	RM_FRAGMENT(rm_pcbios_set_cursor,
		"sti\n\t"
		"popw %ax\n\t"
		"popw %bx\n\t"
		"popw %cx\n\t"
		"popw %dx\n\t"
		"int $0x10\n\t"
		"pushfw\n\t"
		"cli\n\t"
	);

	RM_FRAGMENT(rm_pcbios_clear_screen,
		"sti\n\t"
		"popw %ax\n\t"
		"popw %bx\n\t"
		"popw %cx\n\t"
		"popw %dx\n\t"
		"int $0x10\n\t"
		"pushfw\n\t"
		"cli\n\t"
	);
/*
	for (i=0;i<25;i++)
		putchar('\n');
*/
	in_stack.ax.h = 0x06; /* INT 10,6 - Scroll up*/
	in_stack.ax.l = 0x00; 
	in_stack.bx.h = 0x07; 
	in_stack.cx.h = 0x00; 
	in_stack.cx.l = 0x00; 
	in_stack.dx.h = 0x19; 
	in_stack.dx.l = 0x50; 
	
	ret_ax.word = real_call ( rm_pcbios_clear_screen, &in_stack, &out_stack );

	in_stack.ax.h = 0x02; /* INT 10,2 - Set cursor*/
	in_stack.bx.h = 0x00; 
	in_stack.dx.h = 0x00; 
	in_stack.dx.l = 0x00; 
	ret_ax.word = real_call ( rm_pcbios_set_cursor, &in_stack, &out_stack );
	return ;

}

#ifdef CM_IVC3
void led_ioctl (int function,int delay_count) 
{
	int i;
	static int value = 0xf3;
	static int count =0;

	struct {
		reg16_t ax;
		reg16_t dx;
	} PACKED in_stack;
	struct {
		reg16_t flags;
	} PACKED out_stack;
	reg16_t ret_ax;

	RM_FRAGMENT(rm_pcbios_set_togle,
		"sti\n\t"
		"popw %ax\n\t"
		"popw %dx\n\t"
		"inb %dx,%al\n\t"
		"xorb $0x0f,%al\n\t"
		"outb %al,%dx\n\t"
		"pushfw\n\t"
		"cli\n\t"
	);

	RM_FRAGMENT(rm_pcbios_set_on,
		"sti\n\t"
		"popw %ax\n\t"
		"popw %dx\n\t"
		"outb %al,%dx\n\t"
		"pushfw\n\t"
		"cli\n\t"
	);


	if (function == 1){ /*togle*/

		if (!delay_count)
			count = 0;
		if (count == 0){
			count = delay_count;
			value = (value & 0xfb) + (value ^ 0x0f) & 0x04;
			in_stack.dx.word	= 0x084f; 
			in_stack.ax.l		= value;
			ret_ax.word = real_call ( rm_pcbios_set_on, &in_stack, &out_stack );
		}
		else
			count--;
		
		return;
	}
	
	if (function == 2){ /* turn 0N */
		in_stack.dx.word	= 0x084f; 
		in_stack.ax.l		= 0xf3;
		ret_ax.word = real_call ( rm_pcbios_set_on, &in_stack, &out_stack );
		return;
	}
	
	if (function == 3){ /* turn OFF */
		in_stack.dx.word	= 0x084f; 
		in_stack.ax.l		= 0xff;
		ret_ax.word = real_call ( rm_pcbios_set_on, &in_stack, &out_stack );
	}
	return ;
}
#endif 

#ifdef CM_I986
//void led_ioctl_cm_i986 (int function,int delay_count) 
void led_ioctl (int function,int delay_count) 
{
	uint16_t value;
	static uint16_t gpio_base = 0;
	static int count =0;

	struct {
		reg16_t dx;
	} PACKED in_stack;
	struct {
		reg16_t flags;
	} PACKED out_stack;
	reg16_t ret_ax;

	// XOR bit 4 (18 /19 in 32 bit register)
	RM_FRAGMENT(rm_pcbios_set_togle,
		"sti\n\t"
		"popw %dx\n\t"
		"inb %dx,%al\n\t"
		"xorb $0x08,%al\n\t"
		"outb %al,%dx\n\t"
		"pushfw\n\t"
		"cli\n\t"
	);

	RM_FRAGMENT(rm_pcbios_set_on,
		"sti\n\t"
		"popw %dx\n\t"
		"inb %dx,%al\n\t"
		"andb $0xf3,%al\n\t"
		"outb %al,%dx\n\t"
		"pushfw\n\t"
		"cli\n\t"
	);

	RM_FRAGMENT(rm_pcbios_set_off,
		"sti\n\t"
		"popw %dx\n\t"
		"inb %dx,%al\n\t"
		"orb $0x0c,%al\n\t"
		"outb %al,%dx\n\t"
		"pushfw\n\t"
		"cli\n\t"
	);

	if (!gpio_base){
		// dev_fn = 5 high_bits dev + 3 low_bits func
		pcibios_read_config_word (0,0xf8,0x58,&gpio_base);
		gpio_base &= 0xffe0; /* allighn to 5 bit */
		gpio_base += 0x0e;
	}



	if (function == 1){ /*togle*/

		if (!delay_count)
			count = 0;
		if (count == 0){
			count = delay_count;
			in_stack.dx.word	= gpio_base; 
			ret_ax.word = real_call ( rm_pcbios_set_togle, &in_stack, &out_stack );
		}
		else
			count--;
		
		return;
	}
	
	if (function == 2){ /* turn 0N */
		in_stack.dx.word	= gpio_base; 
		ret_ax.word = real_call ( rm_pcbios_set_on, &in_stack, &out_stack );
		return;
	}
	
	if (function == 3){ /* turn OFF */
		in_stack.dx.word	= gpio_base; 
		ret_ax.word = real_call ( rm_pcbios_set_off, &in_stack, &out_stack );
	}
	return ;
	
}

#endif 

#ifdef CM_IGLX
void led_ioctl (int function,int delay_count) 
{

	static uint32_t value = 0x800;
	static int count =0;
	static int state =0;

	struct {
		reg32_t eax;
		reg16_t dx;
	} PACKED in_stack;
	struct {
		reg16_t flags;
	} PACKED out_stack;
	reg16_t ret_ax;

	
	RM_FRAGMENT(rm_pcbios_set_led,
		"sti\n\t"
		"popl %eax\n\t"
		"popw %dx\n\t"
		"outl %eax,%dx\n\t"
		"pushfw\n\t"
		"cli\n\t"
	);


	if (function == 1){ /*togle*/

		if (!delay_count)
			count = 0;
		if (count == 0){
			count = delay_count;
			if (state){
				value = 0x8000000;
				state =0;
			}else {
				value = 0x800;
				state =1;
			}

			in_stack.dx.word	= 0xec80; 
			in_stack.eax.dword	= value;
			ret_ax.word = real_call ( rm_pcbios_set_led, &in_stack, &out_stack );
			
		}
		else
			count--;
		
		return;
	}
	
	if (function == 2){ /* turn 0N */
		in_stack.dx.word	= 0xec80; 
		in_stack.eax.dword		= 0x8000000;
		ret_ax.word = real_call ( rm_pcbios_set_led, &in_stack, &out_stack );
		return;
	}
	
	if (function == 3){ /* turn OFF */
		in_stack.dx.word	= 0xec80; 
		in_stack.eax.dword		= 0x800;
		ret_ax.word = real_call ( rm_pcbios_set_led, &in_stack, &out_stack );
	}
	return ;	
}

#endif 



/**************************************************************************
Perform NOR chipset reset for cm-i686
**************************************************************************/

void  bios_reset_cm_i986 () 
{
	int i;
	

	struct {
		reg16_t dx;
	} PACKED in_stack;
	struct {
		reg16_t flags;
	} PACKED out_stack;
	reg16_t ret_ax;

	RM_FRAGMENT(rm_pcbios_reset,
		"sti\n\t"
		"popw %dx\n\t"
		"inb %dx,%al\n\t"
		"andb $0xf9,%al\n\t"
		"outb %al,%dx\n\t"
		"orb  $0x2,%al\n\t"
		"outb %al,%dx\n\t"
		"orb  $0x4,%al\n\t"
		"outb %al,%dx\n\t"
		"pushfw\n\t"
		"cli\n\t"
	);

	
	
	in_stack.dx.word = 0xCF9; 
	ret_ax.word = real_call ( rm_pcbios_reset, &in_stack, &out_stack );
	return ;

}

void  bios_reset_cm_iglx () 
{
	

	struct {
		reg16_t ax;
		reg16_t dx;
	} PACKED in_stack;
	struct {
		reg16_t flags;
	} PACKED out_stack;
	reg16_t ret_ax;

	RM_FRAGMENT(rm_pcbios_reset_glx,
		"sti\n\t"
		"popw %ax\n\t"
		"popw %dx\n\t"
		"outb %al,%dx\n\t"
		"pushfw\n\t"
		"cli\n\t"
	);

	
	in_stack.dx.word = 0x92; 
	in_stack.ax.word = 0x01; 
	ret_ax.word = real_call ( rm_pcbios_reset_glx, &in_stack, &out_stack );
	return ;

}

/**************************************************************************
INT15 - Call Interrupt 0x15
**************************************************************************/
int int15 ( int ax )
{
	struct {
		reg16_t ax;
	} PACKED in_stack;
	struct {
		reg16_t flags;
	} PACKED out_stack;
	reg16_t ret_ax;

	RM_FRAGMENT(rm_int15,
		"sti\n\t"
		"popw %ax\n\t"
		"stc\n\t"
		"int $0x15\n\t"
		"pushf\n\t"
		"cli\n\t"
	);

	in_stack.ax.word = ax;
	ret_ax.word = real_call ( rm_int15, &in_stack, &out_stack );

	/* Carry flag clear indicates function not supported */
	if ( ! ( out_stack.flags.word & CF ) ) return 0;
	return ret_ax.h;
}

#ifdef	POWERSAVE
/**************************************************************************
CPU_NAP - Save power by halting the CPU until the next interrupt
**************************************************************************/
void cpu_nap ( void )
{
	RM_FRAGMENT(rm_cpu_nap,
		"sti\n\t"
		"hlt\n\t"
		"cli\n\t"
	);	

	real_call ( rm_cpu_nap, NULL, NULL );
}
#endif	/* POWERSAVE */

#if	(TRY_FLOPPY_FIRST > 0)
/**************************************************************************
DISK_INIT - Initialize the disk system
**************************************************************************/
void disk_init ( void )
{
	RM_FRAGMENT(rm_disk_init,
		"sti\n\t"
		"xorw %ax,%ax\n\t"
		"movb $0x80,%dl\n\t"
		"int $0x13\n\t"
		"cli\n\t"
	);
	
	real_call ( rm_disk_init, NULL, NULL );
}

/**************************************************************************
DISK_READ - Read a sector from disk
**************************************************************************/
unsigned int pcbios_disk_read ( int drive, int cylinder, int head, int sector,
				char *buf ) {
	struct {
		reg16_t ax;
		reg16_t cx;
		reg16_t dx;
		segoff_t buffer;
	} PACKED in_stack;
	struct {
		reg16_t flags;
	} PACKED out_stack;
	reg16_t ret_ax;

	RM_FRAGMENT(rm_pcbios_disk_read,
		"sti\n\t"
		"popw %ax\n\t"
		"popw %cx\n\t"
		"popw %dx\n\t"
		"popw %bx\n\t"
		"popw %es\n\t"
		"int $0x13\n\t"
		"pushfw\n\t"
		"cli\n\t"
	);

	in_stack.ax.h = 2; /* INT 13,2 - Read disk sector */
	in_stack.ax.l = 1; /* Read one sector */
	in_stack.cx.h = cylinder & 0xff;
	in_stack.cx.l = ( ( cylinder >> 8 ) & 0x3 ) | sector;
	in_stack.dx.h = head;
	in_stack.dx.l = drive;
	in_stack.buffer.segment = SEGMENT ( buf );
	in_stack.buffer.offset = OFFSET ( buf );
	ret_ax.word = real_call ( rm_pcbios_disk_read, &in_stack, &out_stack );
	return ( out_stack.flags.word & CF ) ? ret_ax.word : 0;
}
#endif /* TRY_FLOPPY_FIRST */

#endif /* PCBIOS */
