/*
 *	file	: kmsgdump.S
 *	version	: 0.4.4/2002042801
 *	author	: Willy Tarreau  <willy AT meta-x.org>
 *	
 * This code is called from the Bios when the processor has been
 * externally reset and the NVRAM has been set to tell the bios
 * to quickly branch to the pointer at [0x40:0x67].
 * It executes in real mode, and starts from 0:0x700.
 *
 * Its purpose is to dump the last kernel messages onto a floppy disk
 * or to print them on a parallel printer.
 *
 * Useful information are stored at the following hexadecimal adresses :
 *	0:0700 - 0FAF     = this code followed by the stack
 *      0:0FB0 - 0FEF     = bios stack frame at reboot time
 *	0:0FF0 ( 8 bits ) = destination drive number (bios number)
 *	0:0FF1 ( 8 bits ) = track number (<256 !)
 *	0:0FF2, bit 7     = caller mode (0=panic, 1=SysRq)
 *	        bit 6     = output format (0=RAW, 1=FAT)
 *	        bits 5->3 = behaviour when called from panic() :
 *	                       000 = no dump, just halt
 *	                       001 = no dump, just reboot
 *	                       010 =    dump, then halt
 *	                       011 =    dump, then reboot
 *	                       100 = go to manual operation, just like SysRq.
 *	                       others undefined, so don't use please. Thanks.
 *	0:0FF3 (13 bytes) = not used yet
 *	
 *	0:1000 - 4FFF    = kernel messages buffer contents (16 kB).
 *
 * Warning: This code is 16-bits, while the rest of the kernel is 32-bits.
 *          I couldn't find a way to link this file together with 32-bits
 *          files, just because of relative references which caused
 *          relocation issues. As a last resort, every variable is referenced
 *          "variable-base+CODEORIGIN" which is absolute and links well  :-/
 *          If someone finds a better way to do this, I'd like to learn from
 *          him.
 */
#include <linux/version.h>
#include <asm/kmsgdump.h>
	
#define	_pause		jmp	.+2
.text
.code16
	
.global	kmsgdump
.global	kmsgdump_end

/*
 * I use this only for development, it's a bios bootstrap.
 * You only have to define UNDER_DEVEL and the binary object
 * can be copied to a raw diskette and will boot from it.
 * Moreover, the code contains the KMSGDUMP identifier so that
 * the diskette is useable as-is.
 */
#ifdef UNDER_DEVEL
bootstrap:	
	jmp	0f
	.byte	0x90
	.ascii	"KMSGDUMP"
0:	
	cli
	xorw	%ax, %ax
	movw	%ax, %ss
	movw	$0x7C00	, %sp
	pushw	%ss
	popw	%es
	movw	%cs, %ax
	movw	%ax, %ds
	movw	$1000,%cx
	movw	$(kmsgdump-bootstrap+0x7c00), %si
	movw	$CODEORIGIN, %di
	cld
	rep
	movsb
/*	movw	$MASK_SYSRQMODE, MODEFLAGS*/
	movw	$(MASK_PANICDUMP|MASK_PANICBOOT|MASK_OUTPUTFAT|MASK_SAFEDUMP), MODEFLAGS

# read remaining sectors
	
	movw	$0x0203, %ax
	movw	$(bootstrap-kmsgdump+CODEORIGIN+0x200), %bx
	movw	$0x0002, %cx
	movw	$0x0000, %dx
	int	$0x13
	
# pre-read messages
	mov	$0x220, %ax		/* ah=2 (read), al=32 (32 sectors) */
	mov	$(BEGINOFMESSAGES), %bx
	mov	$0x0005, %cx
	mov	$0, %dx			/* head always begins with 0 */
	int	$0x13
	
	ljmp	$0, $CODEORIGIN
#endif		/* UNDER_DEVEL */
	
kmsgdump:
	cli
	xorw	%ax, %ax
	movw	%ax, %ds
	movw	%ax, %es
	movw	%ax, %ss
	movw	$(TOPOFSTACK), %sp
	call	hardware_reset
	sti
	testb	$(MASK_SYSRQMODE | MASK_PANICMAN), MODEFLAGS
	jz	automatic

/* Here is the manual mode loop (either SysRq or Panic with MANUAL flag set).
 * Each second, a short beep is sent so that the user knows the computer
 * is waiting for him/her to press a key.
 * When 'B' is hit, the system immediately reboots. When 'H' is hit, the
 * system halts, and when another key is hit, the system attempts a dump
 * and beeps two more times in case of success. It then goes on waiting
 * for a key in case the user wants another dump.
 */
manual_dump:
/* reinitialize display to color_80x25 */
	movw	$0xe, %ax	/* force a switch to graph mode to help in reinitialization */
	int	$0x10
	movw	$VIDEOMODE, %ax
	int	$0x10
/* hide the cursor */
	movb	$1, %ah
	movw	$0xFFFF,%cx
	int	$0x10
/* redraw the screen */
	xorw	%cx, %cx
	call	clearwindow
/* display title */
	movw	$MKWORD(0,22), %dx
	movw	$(title-kmsgdump+CODEORIGIN), %si
	call	displayline
/* display separation and help line */
	movw	$MKWORD(HELPLINE, 0), %dx
	call	movecursor
	movw	$MKWORD(0x0A, 177), %ax	/* write a line of hashes */
	movb	$0, %bh
	movw	$(RIGHTCOL+1), %cx	/* a full line */
	int	$0x10
	movw	$MKWORD(HELPLINE, 12), %dx	/* display help at column 12 */
	movw	$(helpline-kmsgdump+CODEORIGIN), %si
	call	displayline
	
# now we'll have to print the messages buffer in the lower window.
redrawmessages:	
	movw	$MKWORD(HELPLINE+2,0), %cx	/* first line of the messages window */
	pushw	%cx		/* save this, it will serve us later */
	call	clearwindow
	popw	%dx		/* restore top of window */
	mov	$(BEGINOFMESSAGES), %si	/* SI points to the beginning of the messages */
	xorw	%di,%di			/* first line */
	jmp	findline
1:	lodsb
	or	%al,%al			/* end of data ? */
	jnz	3f		
	movw	%di, currentline-kmsgdump+CODEORIGIN	/* we fix the end at this point. */
	jmp	waitevent		/* and don't show anything */
3:	cmp	$10, %al
	jnz	1b		/* while not LF, look for an end of line */
	inc	%di		/* count one more line */
findline:	
	cmpw	currentline-kmsgdump+CODEORIGIN, %di
	jb	1b
showmessages:
	cmpw	$(LOG_BUF_LEN+BEGINOFMESSAGES), %si	/* end of buffer reached */
	jnb	waitevent
	call	displayline
	cmp	$BOTTOMLINE, %dh	/* stop displaying when screen is full */
	ja	waitevent
	or	%al,%al			/* or when end of messages is reached */
	jnz	showmessages
# redisplay current parameters after each pressed key
waitevent:
	cmpb	$0, mustredraw-kmsgdump+CODEORIGIN	/* if we must redraw the messages window, let's do it */
	movb	$0, mustredraw-kmsgdump+CODEORIGIN
	jnz	redrawmessages

/* now, we'll display current settings (drive, format, status ...) */
	movw	$MKWORD(2, 8), %dx	/* row 2, col 8 */
	movw	$(msgprinter-kmsgdump+CODEORIGIN), %si
	call	displayline

	movb	DRIVENUM,%al
	addb	$'A', %al		/* A and B if < 0x80 */
	test	$0x80, %al
	jz	1f
	subb	$(0x80-'C'+'A'), %al	/* C and D if >= 0x80 */
1:	movb	%al, unitname-kmsgdump+CODEORIGIN		/* write the drive letter into the string */
	movw	$MKWORD(2, 40), %dx	/* row 2, col 40*/
	movw	$(msgunit-kmsgdump+CODEORIGIN), %si
	call	displayline
/* SI is already positionned at msgFAT, so we avoid "movw $msgFAT,%si" */
	testb	$(MASK_OUTPUTFAT), MODEFLAGS
	jnz	2f
	movw	$(msgRAW-kmsgdump+CODEORIGIN), %si
2:	call	displayline
	movw	$MKWORD(3, 8), %dx	/* row 3, col 8 */
	movw	$(kversion-kmsgdump+CODEORIGIN), %si
	call	displayline

1:	movb	$STATUS_WAITING, %al
	call	showstatus
8:	call	waitkey
	jnz	1f		/* a key has been hit */
	call	beep		/* no key hit: let's beep once a second */
	jmp	waitevent	/* loop while no key is pressed */
1:	cmpb	$0x50, %ah	/* down arrow */
	jnz	5f
7:	incw	currentline-kmsgdump+CODEORIGIN	/* go one line below */
1:	movb	$1, mustredraw-kmsgdump+CODEORIGIN
	mov	$1, %ah		/* we check if another key has been hit to speed up scrolling */
	int	$0x16
	jnz	8b		/* handle this new key */
	jmp	waitevent
5:	cmpb	$0x48, %ah	/* up arrow */
	jnz	6f
	subw	$1,currentline-kmsgdump+CODEORIGIN	/* one line back */
	jc	7b		/* aie, negative line. Let's correct this */
	jmp	1b
6:	cmpb	$'b', %al	/* 'B' stands for 'boot' */
	jz	do_reboot
	cmpb	$'h', %al	/* 'H' stands for 'halt' */
	jz	do_halt
	cmpb	$'f', %al	/* 'F' stands for 'change Format' */
	jnz	3f
	xorw	$(MASK_OUTPUTFAT), MODEFLAGS
	jmp	waitevent
3:	cmpb	$'u', %al	/* 'U' stands for 'change Unit' */
	jnz	4f
	xorb	$1, DRIVENUM	/* altern first/second drive */
	jmp	waitevent
4:	cmpb	$'t', %al	/* 'T' stands for 'prinTer' */
	jnz	5f
	call	probeprinter
	jmp	waitevent
5:	cmpb	$'i', %al	/* 'T' stands for 'prinTer' */
	jnz	6f
	movw	$MKWORD(HELPLINE+2,0), %cx	/* first line of the messages window */
	pushw	%cx		/* save this, it will serve us later */
	call	clearwindow
	popw	%dx		/* restore top of window */
	movw	$(aboutmsg-kmsgdump+CODEORIGIN), %si
5:	call	displayline
	or	%al, %al
	jnz	5b	
	jmp	waitevent
6:	cmpb	$'p', %al	/* 'P' stands for 'Print' */
	jnz	7f
	call	printbuffer
	jmp	8f		/* display same return codes as for diskette */
7:	cmpb	$'d', %al	/* 'D' stands for 'dump' */
	jnz	waitevent
	call	do_dump
8:	movb	$STATUS_WERR, %al	/* assume a dump error for now */
	jc	9f		/* on error, we return immediately after ony one beep */
	movb	$STATUS_DUMPOK, %al /* else we say that's good */
	call	beep		/* make 3 audible beeps once successfully dumped */
	call	beep
9:	call	showstatus
	call	beep
	movw	$16,%cx		/* wait 1 second after the message is displayed */
0:	call	sleep54
	loop	0b
	jmp	waitevent

/* clears the screen from the upper left corner defined by CX to the bottom right of the screen */
clearwindow:
	movw	$MKWORD(BOTTOMLINE,RIGHTCOL), %dx	/* bottom of screen */
	movw	$0x0600, %ax	/* clear the messages window */
	movb	$ATTRIB, %bh
	int	$0x10
	ret

/* probes the next printer, and if not found, the next one, ... until
   we find one which is OK, or we fall back to the current one.
*/
probeprinter:
	movb	$STATUS_PROBING, %al
	call	showstatus
	movw	$3, %cx		/* we allow to automatically probe 3 printers */
0:	movb	currentlpt-kmsgdump+CODEORIGIN, %al
	incw	%ax
	cmpb	$'3', %al
	jbe	1f	
	mov	$'1', %al
1:	movb	%al, currentlpt-kmsgdump+CODEORIGIN
	subb	$'1', %al
	movzbw	%al, %dx	/* printer number */
	movb	$1, %ah		/* initialize printer */
	int	$0x17
	testb	$0x2F, %ah	/* status = OK ? */
	loopnz	0b		/* no, probe next printer */
2:	ret
	
/* prints all the buffer on the current printer. On error, CF is set. */
printbuffer:		
	mov	$(BEGINOFMESSAGES), %si	/* SI points to the beginning of the messages */
	movb	$STATUS_PRINTING, %al
	call	showstatus
	movb	currentlpt-kmsgdump+CODEORIGIN, %al
	subb	$'1', %al
	movzbw	%al, %dx	/* printer number */
0:	cmpw	$(BEGINOFMESSAGES+LOG_BUF_LEN), %si
	jae	9f		/* end of buffer */
	lodsb
	or	%al, %al	/* end of data */
	jz	9f
	cmp	$10, %al	/* line feed : we'll insert a carriage return */
	jnz	1f
	mov	$MKWORD(0, 13), %ax	/* print carriage return */
	int	$0x17
	mov	$10, %al	/* line feed */
1:	movb	$0, %ah		/* print character */
	int	$0x17
	testb	$0x2F, %ah	/* status = OK ? */
	jz	0b		/* fetch next char */
	stc
	ret
9:	movw	$MKWORD(0, 12), %ax	/* form feed*/
	int	$0x17
	clc
	ret
	
/* Automatic dump mode, without user operation. The function was called
 * from panic(). Mode flags also allow an immediate reboot without a dump,
 * which is the same than playing with "panic_timeout" in case of a kernel
 * panic.
 */
automatic:
	testb	$(MASK_PANICDUMP), MODEFLAGS
	jz	1f
	testb	$(MASK_SAFEDUMP), MODEFLAGS
	jz	0f
	/* a safe dump is requested. we must ensure the diskette contains a "KMSGDUMP"
	   label before scratching it. */
	xorw	%ax,%ax		/* reinitialize drive */
	movzbw	DRIVENUM, %dx
	int	$0x13

	movw	$0x201, %ax	/* read 1 sector */
	movw	$(FREEMEMORY), %bx	/* pointer to the buffer */
	movw	$0x1, %cx	/* from sector 1 */
	call	int13retry
	jc	1f		/* read error, don't dump */
	cmpl	$0x47534d4b, FREEMEMORY+3	/* "KMSG" */
	jnz	1f
	cmpl	$0x504d5544, FREEMEMORY+7	/* "DUMP" */
	jnz	1f
0:	call	do_dump
1:	testb	$(MASK_PANICBOOT), MODEFLAGS
	jnz	do_reboot
	jmp	do_halt

/* beep():
 * Make a 54 ms beep, and silently pause during 54 ms
 */
beep:	call	sound_on
	call	sleep54
	call	sound_off
	call	sleep54
	ret
	
sleep54:
	pushw	%ax
	movw	CLOCKTICKS, %ax
	incw	%ax
0:	cmpw	CLOCKTICKS, %ax
	jnb	0b
	popw	%ax
	ret
			
/* waitkey():
 * Wait at most 3 second for a key press.
 * If no key is pressed during that time, 0 is returned in AX and ZF is set.
 * If a key is pressed, its lower case ascii code is returned in AL, its scan
 * code in AH and the ZF is cleared.
 */
waitkey:
	pushw	%cx
	movw	CLOCKTICKS, %cx
	addw	$55, %cx	/* 55/18.2 = 3 seconds */
0:	movb	$1, %ah		/* function="tell if a key was pressed" */
	int	$0x16
	jnz	1f
	cmpw	CLOCKTICKS, %cx
	jnb	0b
	xorw	%ax,%ax		/* timeout, no key */
	jmp	2f
1:	movb	$0, %ah
	int	$0x16
	cmpb	$'A',%al
	jb	2f
	cmpb	$'Z',%al
	ja	2f
	orb	$0x20,%al
2:	popw	%cx
	orw	%ax,%ax		/* ZF=(AX==0) */
	ret

	
/* hardware_reset():
   Reconfigure what can be wrong now in hardware :
     - interrupt controller
     - timers
     - keyboard
     - disk units
*/
hardware_reset:	
/* PIC reconfiguration consists in mapping PIC#1 IRQ 0 to software INT 8.
 * Although not needed, PIC#2 is initialized in case we meet buggy chipsets.
 */

	movb	$0x11, %al	/* reconfiguration */
	out	%al, $0x20
#	out	%al, $0xA0
	_pause
	movb	$8, %al		/* map irq 0 to int 0x08 */
	out	%al, $0x21
#	movb	$0x70, %al	/* map irq 8 to int 0x70 */
#	out	%al, $0xA1
	_pause
	movb	$4, %al		/* master mode for PIC#1 */
	out	%al, $0x21
#	movb	$2, %al		/* slave mode for PIC#2 */
#	out	%al, $0xa1
	_pause
	movb	$1, %al		/* 8086 mode */
	out	%al, $0x21
#	out	%al, $0xA1
	_pause
	movb	$0xFF, %al	/* all IRQs are masked at the moment */
	out	%al, $0x21
#	out	%al, $0xA1
	_pause

/* Disable internal speaker output if it was enabled */

	call	sound_off
	
/* PIT reconfiguration : set Timer0 clock to 18.2 Hz */

	movb	$0x34, %al	/* timer 0 in interrupt mode */
	out	%al, $0x43
	_pause
	movb	$0, %al		/* divide by 65536 (1193182 Hz -> 18.2 Hz) */
	out	%al, $0x40	/* LSB first */
	_pause
	out	%al, $0x40	/* MSB next */
	_pause
	
/* PIT reconfiguration (cont'd) : set Timer2 clock to 880 Hz for the buzzer */

	movb	$0xb6, %al	/* timer 2 in square wave mode */
	out	%al, $0x43
	_pause
	movw	$1355, %ax	/* divider set to 1193182/1355 = 880 Hz */
	out	%al, $0x42	/* LSB first */
	_pause
	movb	%ah, %al
	out	%al, $0x42	/* MSB next */
	_pause
	
	movb	$0xBC, %al	/* only irq 0,1,6 unmasked */
	out	%al, $0x21
	_pause

/* Keyboard reset: The keyboard may be misconfigured. We will send it a
   reconfiguration (max speed) so that the Bios will at least reconfigure it
   and its controller (i8042).
*/

	mov	$0x305, %ax	/* set speed */
	xorw	%bx, %bx	/* full speed */
	int	$0x16

/* Reset the minimum so that int 13 works */
	movw	$0x3F2, %dx
	movb	$0x0, %al
	outb	%al, %dx
	_pause
	_pause
	movb	$0xC, %al
	outb	%al, %dx
	_pause
	_pause

	xorw	%ax, %ax
	xorw	%dx, %dx
	int	$0x13
	ret		

/* sound_off(): disable internal speaker output. */
sound_off:
	push	%ax
	in	$0x61, %al
	andb	$0xFC, %al
	jmp	1f		/* same code below */

/* sound_on(): enable internal speaker output. */
sound_on:
	push	%ax
	in	$0x61, %al
	orb	$3, %al
1:	_pause
	out	%al, $0x61
	pop	%ax
	ret

/* HALT the CPU */
do_halt:
	movb	$STATUS_HALTING, %al
	call	showstatus
0:	cli
	hlt
	jmp	0b		/* just in case of NMIs */


/* REBOOT the CPU */
do_reboot:
	movb	$STATUS_BOOTING, %al
	call	showstatus
	cli
/* Make sure [40:72] doesn't contain 0x1234, which the Bios will interprete as
   Ctrl-Alt-Del. */
	movw	$0, 0x472
	ljmp	$0xFFFF,$0	/* branch to bios boot strap */

/*
 * do_dump():
 * This function dumps the message buffer onto a disk. CF is set if an error
 * occured.
 *
 */
do_dump:
	movb	DRIVENUM, %dl
	xorw	%ax, %ax
	int	$0x13		/* reinitialize drive */
	xorw	%si, %si	/* assume first data sector = 0 */
	testb	$(MASK_OUTPUTFAT), MODEFLAGS
	jz	1f
	movw	$(FATSIZE*2+2), %si	/* when FAT is used, data go further */
1:	call	makefat
	jc	2f
	call	dumpdata
2:	ret

/*
 * Shows, in the status line, the message matching the status code in AL.
 * AX, BX, DX and FLAGS contents are lost. CX and SI are kept.
 */
showstatus:
	cbw
	pushw	%si
	pushw	%cx
	testb	$(MASK_SYSRQMODE | MASK_PANICMAN), MODEFLAGS
	jz	2f	/* is not in interactive mode, don't show anything */
	pushw	%ax
	movw	$MKWORD(3, 40), %dx
	movw	$(msgstatus-kmsgdump+CODEORIGIN), %si
	call	displayline
	popw	%si
	cmpw	$STATUS_MAXMSG, %si
	ja	2f
	shlw	$4, %si
	addw	$(statwait-kmsgdump+CODEORIGIN), %si
1:	call	displayline
2:	popw	%cx
	popw	%si
	ret
/*
 * sets the cursor to the begin of the line in DH, col in DL, and displays the message from SI
 * which ends with \n, \r or \0. On return, AL contains the byte that ended the line, and SI
 * points to the next byte.
 * if LF is encountered, the cursor is positionned at the beginning of the next line and
 * LF is returned in AL, the cursor position in DX.
 * If DH is set to -1, then the cursor isn't moved. The new cursor position is returned into
 * DX so it's possible to recall displayline immediately.
 */
displayline:
	cmpb	$-1,%dh
	jz	0f
	call	movecursor
0:	cld
1:	movb	$ATTRIB, %bl	/* use standard text attribute by default */
	lodsb
	cmp	$0377, %al	/* a 0377 char means next one will be highlit */
	jnz	3f
	movb	$HIGHLIGHT, %bl
	lodsb	
3:	cmp	$13, %al	/* CR is ignored */
	jz	1b
	or	%al, %al	/* end of messages */
	jz	2f
	cmp	$10, %al	/* LF: position onto next line and return from function */
	jnz	5f
	pushw	%ax
	call	findcursor	/* DX gets the current cursor position */
	movb	$255, %dl	/* if we pass the end of the line, we'll begin on a new row */
	call	movecursor
	popw	%ax		/* restore LF in AX */
	jmp	2f		/* and we return */
5:	movb	$0, %bh		/* current page = 0 */
	movw	$1, %cx		/* 1 char */
	movb	$9, %ah		/* write char with attribute */
	int	$0x10
	call	findcursor	/* DX gets the current cursor position */
	inc	%dx		/* next position */
	call	movecursor
	jmp	1b
movecursor:
	cmp	$RIGHTCOL, %dl	/* did we override the end of the line ? */
	jbe	1f		/* no */
	inc	%dh		/* yes, so position onto the next line */
	movb	$0, %dl
1:	movb	$2, %ah
4:	movb	$0, %bh
	int	$0x10
2:	ret
findcursor:
	mov	$3, %ah
	jmp	4b

#if defined(UNDER_DEVEL) & defined(DEBUG)
showhex:
	pushfw
	pushaw
	movw	%ax, %si
	movw	$4, %cx
0:	pushw	%cx
	rolw	$4, %si
	movw	%si, %ax
	andb	$0xF, %al
	addb	$'0', %al
	cmpb	$'9', %al
	jbe	1f
	addb	$7, %al
1:	movb	$0xE, %ah
	int	$0x10
	popw	%cx
	loop	0b
#ifdef STEP_BY_STEP	
	movb	$0, %ah
	int	$0x16
#endif
	popaw
	popfw
	ret
#endif


/*
 * makefat():
 * writes a 18-sectors/track FAT on the diskette, at logical sector 0.
 * CF is set if an error occurs.
 * SI is preserved.
 *
 */
makefat:
	cld
	pushw	%si
	movb	$STATUS_FORMATING, %al
	call	showstatus
/* make DI point to the first free memory */
	movw	$(FREEMEMORY), %di
	pushw	%di		/* will be used later */
/* initialize the data area : 1 boot, 1 fat, 1 root */
	movw	$((FATSIZE+2)<<8), %cx
	cld
	xorw	%ax, %ax
	rep
	stosw
	popw	%di
	pushw	%di
/* make boot sector */
	movw	$(bootsect-kmsgdump+CODEORIGIN), %si
	movw	$(rootsect-bootsect), %cx
	rep
	movsb

/* make fat1 (which is also fat2) */
	movw	$(FREEMEMORY+0x200), %di
	movw	$2, %bx		/* first usable cluster is always 2 */
	movw	$(LOG_BUF_LEN >> 9), %cx	/* #of sectors per FAT */

	addw	%bx, %cx	/* decide now of the "end of chain" */
	incw	%bx
	movb	$0xF0, %al	/* FAT ID */
	stosb
	movw	$-1, %ax	/* beginning of FAT */
	stosw
0:	movw	%bx, %ax
	stosb
	incw	%bx		/* next cluster */
	cmpw	%cx, %bx	/* did we reach the last cluster ? */
	jb	1f
	movw	$0xFFF, %bx	/* yes: mark "end of chain" */
1:	shlb	$4, %ah
	movb	$0, %al
	orw	%bx, %ax
	rolw	$4, %ax
	stosw			/* write 2 bytes at once */
	incw	%bx		/* next cluster */
	testb	$0x10, %bh	/* are we above the "end of chain" magic number ? */
	jz	0b		/* not yet, let's go on for 2 more clusters */

/* make root sector */
	movw	$(FREEMEMORY+0x200+FATSIZE<<9), %di
	movw	$(datasect-rootsect), %cx
	rep
	movsb
	movw	$(FIRSTCLUSTER), 14(%di)	/* file begins at cluster 2 */
	movw	$(LOG_BUF_LEN), 16(%di)	/* file length in bytes */

/* in memory, we now have :
 * BOOT | FAT | ROOT.
 * We'll use a trick to write the two FATs : 
 * we first write (BOOT+FAT), and then append (FAT+ROOT)
 */
	popw	%di			/* beginning of the area to write */
	movw	$(1+FATSIZE), %cx	/* one boot sector + N FAT sectors */
	xorw	%si, %si		/* write onto the first sector */
	pushw	%cx			/* fortunately, it will be the same size after... */
	call	wrsect			/* let's write the boot sector onto the disk */
	popw	%cx			/* 1+FATSIZE, let's save one byte of code */
	jc	0f	
/* now write fat2 + root */
	movw	$(FREEMEMORY+0x200), %di
	call	wrsect		/* let's write the boot sector onto the disk */
0:	popw	%si
	ret

/*
 * dumpdata():
 * writes <LOG_BUF_LEN> bytes of data on drive DRIVENUM, from logical sector %si.
 * CF is set if an error occured.
 *
 */
dumpdata:
	clc
	pushw	%si
	movb	$STATUS_DUMPING, %al
	call	showstatus
	movw	$(LOG_BUF_LEN >> 9), %cx	/* #of sectors that will be written */
	movw	$(BEGINOFMESSAGES), %di
	call	wrsect
	popw	%si
	ret

/* this function calls int 13 and if any error occurs, it will retry twice.
 * all registers are kept except flags.
 */
int13retry:
	pushaw
	clc
	int	$0x13
	jnc	0f
	popaw
	pushaw
	clc
	int	$0x13
0:	popaw
	ret
	
/*
 * wrsect():
 * starts writing CX sectors of data from ES:DI to the diskette at logical sector SI.
 * first logical sector is numbered 0. This function can write more than one track at
 * a time, and is limited to the buffer's address in ES segment.
 * all registers are modified, particularly :
 *  - SI which contains the first sector that should be written next time
 *  - DI which points to next data to be written
 *  - CX which tells how many sectors should be still written in case of CF=1
 */
wrsect:
0:	jcxz	9f	/* when there are no more sectors to write, we return */
	pushw	%cx	/* save this number of remaining sectors to write */
	movw	%si, %ax

	movw	$(NBHEADS*NBSECT), %cx	/* we'll compute the starting track number */
	divb	%cl
	xchgb	%al, %ch		/* al = 0, ch = track number */
	xchgb	%ah, %al		/* ah = 0, al = head*nbsect + sect */
	movb	$(NBSECT), %cl
	divb	%cl
	movb	%al, %dh		/* dh = head number */
	xchgb	%cl, %ah		/* ah = NBSECT, cl = sector number (0-based) */
	subb	%cl, %ah		/* ah = number of sectors before end of track */
	popw	%bx			/* number of sectors to write */
	movzbw	%ah, %ax		/* max #of sectors left on this track */
	cmpw	%ax, %bx		/* does it fit ? */
	ja	1f			/* no, let's use AX */
	movw	%bx, %ax		/* take all the remainder */
1:
	subw	%ax, %bx		/* subtract these sectors from the rest */
	addw	%ax, %si		/* increment the start sector for the next round */
	pushw	%bx			/* save the actual number of sectors to be written */
	movb	$3, %ah			/* 3=write, AL is already set to #sectors */
	incb	%cl			/* CL was 0-based and needs to be 1-based */
	movw	%di, %bx		/* pointer to beginning of data */
	movb	DRIVENUM, %dl
	/* at this point, AL=#sectors, AH=3(write), BX=buffer addr, */
	/* CL=starting sector, CH=track, DL=drivenum, DH=head num   */
	/* let's compute the next DI now */
	pushw	%ax
	shlw	$9, %ax			/* AL*512 = data size */
	addw	%ax, %di
	popw	%ax
	call	int13retry
	popw	%cx			/* restore the number of remaining sectors */
	jnc	0b			/* if no error, let's continue */
9:	ret

/* data needed to make a tiny FAT file system. */
bootsect:
	/* Warning : DOS expects first byte to be either EB or E9 (jump) ! */
	.byte	0xEB,(bootcode-bootsect-2),0x90
	.ascii	"KMSGDUMP"
	.hword	512	/* 512 bytes/sector */
	.byte	1	/* 1 sector/cluster */
	.hword	1	/* 1 reserved sector */
	.byte	2	/* 2 FATs */
	.hword	16	/* 16 root directory entries (1 root sector) */

	.hword	NBCLUSTERS	/* total clusters */
	.byte	0xF0	/* media descriptor */
	.hword	FATSIZE	/* # of sectors per FAT */
	.hword	NBSECT	/* # of sectors/track */
	.hword	NBHEADS	/* # of heads */
	/* now some code to redirect the boot to the first hard disk. This way,
	   after an automatic dump, even if the diskette is left in the drive and
	   the bios doesn't automatically go to the hard disk, the diskette will
	   try to do it itself.
	 */
bootcode:
	/* first we'll copy the boot code elsewhere. */
	cli
	cld
	xorw	%ax,%ax
	movw	%ax,%es
	movw	%ax,%ds
	movw	$0x7c00,%si	/* current source */
	movw	$0x8000,%di	/* new destination */
	movw	$256,%cx	/* 256 words = this entire sector */
	pushw	%si
	repz
	movsw
	popw	%bx		/* current code will be erased */
	jmp	.+3+0x8000-0x7c00	/* jump to fresh copy of this code */
	incw	%cx		/* cx=1 : read from sector 1 (cx was 0, so now it's 1).*/
	movw	$0x80, %dx	/* 0x80 = first hard drive */
	movw	$0x201,%ax	/* 2=read, 1=1 sector */
	int	$0x13
	jnc	bootsect-(0x8000-0x7c00)	/* jump to new code if no error */
	int	$0x19		/* or try to reboot */
	jmp	.		/* hang if int 19 returns. */
	
rootsect:	.ascii	"MESSAGESTXT"
		.byte	0x20	/* archived file attribute */
datasect:	/* end of FAT-specific data */
	
mustredraw:	.byte	0
currentline:	.hword	65535	/* go directly to last line */
title:		.asciz	"Linux Kernel Messages Dumper - v0.4.4"
helpline:	.asciz	"> \377Boot \377Dump \377Format \377Halt \377Info \377Print prin\377Ter \377Unit \377U\377p \377D\377o\377w\377n <"
/* it's important to respect the same order for the following parts. */
msgprinter:	.ascii	"Printer : \377L\377P\377T\377"
currentlpt:	.asciz	"1"
msgunit:	.ascii	"Drive Unit and Format : \377" /* no zero string -> will display drive letter */
unitname:	.asciz	"A/"
msgFAT:		.asciz	"\377F\377A\377T\3771\3772"
msgRAW:		.asciz	"\377R\377A\377W  "
kversion:	.ascii	"Kernel version : "
		.asciz	UTS_RELEASE
msgstatus:	.asciz	"Status : "

/* these messages must be *exactly* 15 chars long, and terminated by a 0 (ie: 16 bytes tot) */
statwait:	.asciz	"Press a key    "
statform:	.asciz	"Formating FAT12"
statdump:	.asciz	"Dumping msgs   "
statdmpd:	.asciz	"Messages dumped"
statboot:	.asciz	"Rebooting...   "
stathalt:	.asciz	"System halted. "
statprob:	.asciz	"Probing printer"
statprnt:	.asciz	"Printing msgs  "
statwerr:	.asciz	"Dump failed !  "
aboutmsg:	.ascii	"KMSGDUMP 0.4.4 - (C) 1999-2002 - Willy Tarreau <willy AT meta-x.org>\n"
		.ascii	"<\377B> reboots, <\377D> dumps messages onto the floppy, <\377F> changes floppy\n"
		.ascii	"format, <\377H> halts the system, <\377P> prints the messages on the selected\n"
		.ascii	"printer, <\377T> selects a printer, <\377U> changes the floppy unit, \377U\377p/\377D\377o\377w\377n arrows\n"
		.ascii	"scroll messages up/down, and <\377I> shows this help. When an action completes\n"
		.ascii	"successfully (print, dump), you should hear 3 short beeps.\n"
		.ascii	"When dumping data onto a floppy, be warned that FLOPPY CONTENTS WILL BE \377L\377O\377S\377T.\n"
		.asciz	"Hit <\377U\377p> key to list kernel messages again."
/* just a public label at the end of the code so we know its lenght. */
kmsgdump_end:
