/*
 * flash.S86  -  FlashCard flash EPROM programmer
 *
 * Copyright (C) 1997-2007 Gero Kuhlmann   <gero@gkminix.han.de>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * $Id: flash.S86,v 1.12 2007/01/08 22:02:20 gkminix Exp $
 *
 *====================================================================
 */
#define DEF_STACK_SIZE 1
#define DEF_HEAP_SIZE 1

#include <common.i86>
#include <netboot.i86>
#include <net/bootp.i86>
#include <system/nbloader.i86>
#include <system/bootrom.i86>
#include <system/pnp.i86>
#include <system/pci.i86>
#include "flash.i86"

.file __FILE__
.line __LINE__



/*
 *====================================================================
 *
 * Define stack and heap size
 */

/* Stack size is common for all netboot image loader programs */
__stack_size = MIN_STK_SIZE


/* We dont need a heap */
__heap_size = 0




/*
 *====================================================================
 *
 * Definitions local to this module
 */
#define ASK_TIMEOUT	20		/* number of seconds to wait for key */
#define ASK_KEY		0x1B		/* ASCII code for escape key */




.line __LINE__
/*
 *====================================================================
 *
 * Define some external routines from the i386 library
 */
	.text

	.extern	nbentry			# routines in nbentry.S86
	.extern	doerr
	.extern	nopxe

	.extern	ldrlen			# routines in ldrec.S86
	.extern	ldradr
	.extern	ldrfnd

	.extern	btptag			# routines in bootp.S86

	.extern	prnstr			# routines in scnio.S86
	.extern	prcrlf
	.extern	prndwd
	.extern	prnwrd
	.extern	prnbyt
	.extern	prnnum
	.extern	prnchr

	.extern	keywait			# routines in kbdio.S86
	.extern	keyget

	.extern	getmem			# routines in misc.S86
	.extern	movmem

	.extern	play



/*
 *====================================================================
 *
 * Boot image loader header
 */

	jmp	nopxe			# PXE entry point

	.org	NBLD_OFFSET

nbld_header:
	.word	__text_end		# total size of text segment
	.word	__bss_end		# total size of data segment
	.word	__heap_size		# minimum heap size
	.word	__stack_size		# minimum stack size
	.word	nbentry			# boot image entry routine
	.word	start			# the main program
	.word	vmagic			# vendor ID
	.word	sigmsg			# copyright message
	.word	istruct_list		# interface list



/*
 *====================================================================
 *
 * Now start the game
 */

start:	mov	si,offset start_snd
	call	play

/*
 * Next get the address of the boot rom image, and determine the size of
 * the bootrom loader.
 */

	mov	si,offset recerr
	mov	al,ROMIMAGENUM + LOADER_TAG
	call	ldrfnd			# find load record for rom image
	jc	jmperr
	call	ldradr			# get address of rom image
	jc	jmperr
	mov	[imgaddr],eax
	mov	bx,ax			# convert linear image address into
	and	bx,0x000F		# seg:ofs pointer, it should start at
	jnz	jmperr			# a paragraph boundary
	shr	eax,4
	mov	word ptr [imgptr + 0],bx
	mov	word ptr [imgptr + 2],ax

	mov	eax,fs:boot_ld_ilength[di]
	test	eax,0xFFFE01FF		# maximum for ROM image size is 128 kB
	jnz	jmperr			# it also has to be a multiple of 512
	mov	[imgsize],eax		# bytes

	call	getmem			# get amount of lower memory
	shl	eax,10
	mov	edx,fs:boot_ld_mlength[di]
	mov	ebx,edx
	add	ebx,[imgaddr]
	cmp	ebx,eax			# image should end within lower memory
	jae	jmperr
	sub	edx,[imgsize]
	jc	jmperr			# the memory size of the rom image
	cmp	edx,WINMAXSIZE		# has to be large enough to optionally
	jb	jmperr			# include a page switching rom window

	mov	bl,fs:boot_ld_length[di]
	test	bl,0xF0
	jz	jmperr
	and	bx,0x000F		# get offset of vendor infor-
	shl	bx,2			# mation within load record
	mov	edx,fs:[bx + di]	# get bootrom loader size
	test	edx,0xFFFF0000
	jnz	jmperr
	mov	[ldsize],edx

/*
 * Try to find a supported flash EPROM interface. Scan through the list
 * of possible interfaces to find an installed one.
 */

	mov	si,offset istruct_list
2:	cld
	lodsw				# get pointer to next list entry
	or	ax,ax
	jnz	3f			# check if at end of list
	mov	si,offset eprerr
	jmp	jmperr

3:	push	si
	push	ax
	mov	di,ax
	mov	si,offset chkmsg	# let the user know what we are
	call	prnstr			# currently checking
	mov	si,istruct_id[di]
	call	prnstr
	lfs	si,[imgptr]
	call	istruct_detect[di]	# call interface detection routine
	pushf
	jnc	1f			# check for error
	or	ax,ax
	jz	1f
	push	ax
	mov	al,' '
	call	prnchr
	mov	al,'('
	call	prnchr
	pop	ax
	call	prnwrd			# print error code
	mov	al,')'
	call	prnchr
1:	call	prcrlf			# terminate checking line
	popf				# restore error flag
	pop	bx
	pop	si
	jc	2b
	mov	[curif],bx		# save interface definition pointer
	mov	[curchip],dx		# save Flash EPROM definition pointer
	mov	[romaddr],eax		# save return values
	mov	[romsize],ecx
	mov	[romofs],edi

/*
 * Let the user know what we found
 */

	mov	si,offset fndmsg
	call	prnstr
	mov	si,[curchip]
	mov	si,fstruct_name[si]
	call	prnstr
	mov	edx,[romaddr]
	or	edx,edx
	jz	4f
	mov	si,offset adrmsg
	call	prnstr
	mov	eax,edx
	call	prndwd
4:	mov	si,offset ifmsg
	call	prnstr
	mov	bx,[curif]
	mov	si,istruct_id[bx]
	call	prnstr
	call	prcrlf

/*
 * If the Flash EPROM is not mapped into the address space entirely, we
 * have to install a special Flash EPROM loader into the bootrom binary.
 * For this, we copy the compressed bootrom image to the beginning of the
 * second Flash EPROM page, and then copy the Flash EPROM loader at the
 * end of the bootrom image loader.
 */

	mov	bx,istruct_loader[bx]
	or	bx,bx			# check if we need to install a Flash
	jz	5f			# EPROM loader at all
	mov	edx,lstruct_winsize[bx]
	cmp	edx,[imgsize]		# check if the full bootrom image can
	jae	5f			# be mapped into the address space

	mov	si,offset winerr
	test	edx,0x000001FF		# the window size has to be a multiple
	jnz	jmperr			# of 512 bytes, and a maximum of 64kB
	cmp	edx,WINMAXSIZE
	ja	jmperr
	mov	ax,lstruct_endofs[bx]
	sub	ax,lstruct_iniofs[bx]
	movzx32	eax,ax			# check if boot image loader fits into
	add	eax,[ldsize]		# Flash EPROM window
	add	eax,0x000F		# add some safety margin
	cmp	eax,edx
	ja	jmperr
	call	instload		# install Flash EPROM image loader
	mov	si,offset ilderr
	jc	jmperr

/*
 * Setup image for programming
 */

5:	call	imgprep			# prepare ROM image for programming
	mov	si,offset sizerr
	mov	eax,[imgsize]
	add	eax,[romofs]
	cmp	eax,[romsize]		# check if image fits into Flash EPROM
	ja	jmperr

/*
 * Give the user a final chance to abort all of this
 */

	mov	si,offset wrnmsg
	call	prnstr			# print a warning message
	mov	cx,ASK_TIMEOUT
6:	mov	si,offset k1msg
	call	prnstr
	movzx32	eax,cx			# print number of seconds left
	mov	dl,10
	call	prnnum
	mov	si,offset k2msg
	call	prnstr
	mov	ax,1
	call	keywait			# wait for one second
	jnc	7f
	mov	al,0x0D
	call	prnchr			# return cursor to beginning of line
	loop	6b			# continue next round
	jmp	8f

7:	cmp	al,ASK_KEY		# check if escape pressed
	jne	8f
	call	prcrlf
	mov	si,offset abmsg		# print abort message
	jmp	doerr

8:	call	prcrlf
	call	prcrlf
	mov	si,offset prog_snd
	call	play

/*
 * Initialize Flash EPROM access
 */

	mov	bx,[curif]
	call	istruct_init[bx]	# call interface initialization

/*
 * Erase the flash EPROM
 */

	mov	si,offset eramsg
	call	prnstr
	mov	edi,[romofs]
	mov	ecx,[imgsize]
	call	istruct_erase[bx]	# erase the chip
	mov	si,offset eraerr
	jc	9f

/*
 * Program the flash EPROM
 */

	mov	si,offset prgmsg
	call	prnstr			# print programming message
	lfs	si,[imgptr]
	mov	ecx,[imgsize]
	mov	edi,[romofs]
	call	istruct_prog[bx]	# program the Flash EPROM
	mov	dx,si
	mov	si,offset prgerr
	jc	9f

/*
 * Verify that the written image has really been programmed into the
 * Flash EPROM
 */

	mov	si,offset vfymsg
	call	prnstr			# print verifying message
	mov	si,dx
	call	istruct_verify[bx]	# verify the image
	mov	si,offset vfyerr	# in case of error fall through with
					# carry flag

/*
 * Terminate the programming and reboot the system
 */

9:	pushf
	call	istruct_clean[bx]	# call interface cleanup
	popf
	jc	jmperr
	mov	si,offset donmsg
	call	prnstr
	mov	si,offset end_snd
	call	play
	mov	si,offset endmsg
	call	prnstr			# print end message
	call	keyget			# wait for a key press
	xor	ax,ax
	mov	ds,ax
	mov	word ptr ds:[0x0472],0x1234
	ljmp	0xFFFF,0		# reboot the system



/*
 *====================================================================
 *
 * Return to the bootrom in case of error
 * Input:  SI  -  Pointer to error message
 * Output: routine does not return
 * Changed registers: all
 */
jmperr:	mov	word ptr [doexit],offset errsnd
	jmp	doerr



/*
 *====================================================================
 *
 * Play error sound. This gets called after the error message has
 * been printed.
 * Input:  none
 * Output: none
 * Changed registers: AX
 */
errsnd:	push	si
	mov	si,offset error_snd
	call	play
	pop	si
	ret



/*
 *====================================================================
 *
 * Install Flash EPROM loader into bootrom image
 * Input:  BX  -  Pointer to loader definition structure
 * Output: Carry flag set if error
 * Changed registers: EAX, EBX, ECX, EDX, ESI, EDI
 */
instload:

	push	es
	mov	di,bx
	les	si,[imgptr]		# get size of compressed image
	mov	ecx,es:[si + ROMCOMPSIZEOFS]
	mov	eax,[imgaddr]		# move the compressed bootrom image
	mov	ebx,eax			# to the beginning of the first Flash
	add	eax,[ldsize]		# EPROM page
	add	ebx,lstruct_winsize[di]
	add	ebx,WINID_LEN		# leave some bytes for the signature
	call	movmem
	jc	9f
	add	ecx,ebx
	sub	ecx,[imgaddr]		# compute new bootrom image size
	mov	[imgsize],ecx
	mov	bx,di

	cld
	mov	edi,[imgaddr]
	add	edi,lstruct_winsize[bx]
	mov	eax,edi
	and	di,0x000F		# move the window magic ID at the
	shr	eax,4			# beginning of the compressed bootrom
	mov	es,ax			# image
	mov	si,offset lmagic
	mov	cx,WINID_LEN
	rep	movsb

	mov	edi,[imgaddr]
	add	edi,[ldsize]
	mov	eax,edi
	and	di,0x000F		# move the Flash EPROM loader into
	shr	eax,4			# the bootrom image
	mov	es,ax
	mov	si,lstruct_iniofs[bx]
	mov	cx,lstruct_endofs[bx]
	sub	cx,si
	push	ds
	mov	ax,cs
	mov	ds,ax
	mov	ax,cx
	add	ax,0x000F		# adjust size to paragraph boundary
	and	ax,0xFFF0
	mov	[si + FLOADSIZEOFS],ax	# save size of Flash EPROM loader
	mov	ax,cx			# remember actual size for later
	rep	movsb
	pop	ds

	movzx32	eax,ax
	add	eax,[ldsize]
	mov	ecx,lstruct_winsize[bx]
	sub	ecx,eax			# fill the remaining window with 0xFF
	mov	dx,es
	mov	ax,di
	shr	ax,4
	add	dx,ax
	mov	es,dx
	and	di,0x000F
	mov	al,0xFF
	rep	stosb			# 0x8080 should always be at end of rom
	mov	word ptr es:[di - 2],0x8080
	mov	byte ptr es:[di - 3],0

	les	di,[imgptr]			# set Flash loader flag in rom
	or	byte ptr es:[di + ROMBOOTFLGOFS],ROMFLG_FLOADER
	mov	ecx,lstruct_winsize[bx]
	mov	eax,ecx				# compute new ROM size - even
	shr	eax,9				# though the size of the new
	and	ax,0x00FF			# ROM might be smaller than the
	mov	es:[di + ROMLENOFS],al		# window size we need the ROM
	mov	bx,es:[di + ROMPCIPTROFS]	# to be as large as the full
	or	bx,bx				# window so that it gets mapped
	jz	1f				# into the processor address
	mov	es:pcihdr_romlength[bx + di],ax	# space entirely for page switch

1:	mov	bx,[curif]
	or	word ptr istruct_flags[bx],IFLAGS_NEEDMEM
	clc
9:	pop	es
	ret



/*
 *====================================================================
 *
 * Prepare the ROM image for programming. This sets the DDIM flag in the
 * PnP header and re-computes all checksums.
 * Input:  none
 * Output: none
 * Changed registers: EAX, EBX, ECX, EDX, ESI, EDI
 */
imgprep:

	push	es
	mov	bx,[curif]

/*
 * Check if the Flash memory is not installed on the network interface
 * card. In that case, the IFLAGS_NOTONIF flag is set. It means that
 * we have to delete the pointer to the PCI structure in the bootrom
 * header.
 */

	test	word ptr istruct_flags[bx],IFLAGS_NOTONIF
	jz	6f
	les	di,[imgptr]
	mov	word ptr es:[di + ROMPCIPTROFS],0

/*
 * Needing direct memory access can be for two reasons:
 *
 *  1.)  The Flash EPROM programmer needs to directly write into the ROM
 *       for programming. In this case, the IFLAGS_NEEDMEM flag is set by
 *       default the interface definition structure.
 *  2.)  The bootrom needs a Flash loader with page switching. In this case
 *       the IFLAGS_NEEDMEM flag has been set by the Flash loader installer
 *       routine above.
 *
 * In both cases, we have to turn off DDIM support with the bootrom. DDIM
 * allows the BIOS to copy the bootrom into RAM, which gets switched to read-
 * only lateron. This prevents any direct write access to the physical Flash
 * EPROM chip. For the second case, DDIM is not necessary anyway since the
 * bootrom footprint is reduced by the Flash loader already. However, turning
 * off DDIM is unfortunate for case number 1. Direct memory access is only
 * required for Flash EPROM programming, but not during normal operation.
 * However, once DDIM is activated, everytime the system gets booted up,
 * direct memory access will be prohibited by the BIOS, and there is no way
 * to regain access to re-program the Flash EPROM. There are three solutions
 * to this problem:
 *
 *  1.) Use a network card which provides I/O access to the Flash EPROM (like
 *      the 3Com 3C90xB/C series or the AMD PCNet series). For those cards we
 *      dont need direct memory write access and can therefore activate DDIM.
 *      The IFLAGS_NEEDMEM flag is not set in the interface definition struc-
 *      ture.
 *  2.) Use some way of modifying the motherboard chipset so that it enables
 *      write access to the Flash EPROM temporarily. This way, we can use DDIM
 *      during normal boot, but can turn it off when required for programming
 *      the Flash EPROM. Also, it is necessary to turn off caching of the Flash
 *      EPROM region, which is much more complicated as it means to either to
 *      modify the chipset, or reprogram the MTRR registers of the CPU if they
 *      exist. Due to the variety of different chipsets and CPU configurations,
 *      this solution is not yet implemented. But it is the better way instead
 *      of turning off DDIM permanently, because DDIM is quite useful.
 *  3.) For network cards needing direct write access to the Flash EPROM to get
 *      re-programmed, turn off DDIM permanently. This is the worst of all solu-
 *      tions, but the easiest one to implement (besides using a different net-
 *      work card).
 *
 * In order to turn off DDIM support, we have to reset a bit in the PnP header of
 * the bootrom and recalculate the PnP header checksum.
 */

6:	test	word ptr istruct_flags[bx],IFLAGS_NEEDMEM
	jz	2f
	les	di,[imgptr]
	mov	bx,es:[di + ROMPNPPTROFS]
	or	bx,bx			# check if we do have a PnP header
	jz	2f			# no PnP header means no DDIM support
	add	di,bx
	mov	al,PNP_DEVIND_DDIM	# we do have to negate the flag like
	not	al			# this because GAS doesnt understand ~
	and	byte ptr es:pnphdr_devind[di],al
	movzx	cx,byte ptr es:pnphdr_size[di]
	shl	cx,4
	xor	bx,bx
	xor	al,al
1:	add	al,es:[bx + di]		# compute checksum of PnP header
	inc	bx
	loop	1b
	sub	es:pnphdr_chksum[di],al

/*
 * Up until now, the bootrom image might have been changed significantly. Before
 * we can program it into a Flash EPROM we have to set the checksum correctly.
 * If we have installed a Flash EPROM loader, only the first page of the bootrom
 * image is visible to the client at bootup, so we should only checksum that
 * first page.
 */

2:	les	di,[imgptr]
	movzx32	ecx,byte ptr es:[di + ROMLENOFS]
	shl	ecx,9			# compute size of physically visible
	xor	al,al			# rom image (can be max. 128kB)
3:	add	al,es:[di]		# compute bootrom checksum
	inc	di
	test	di,0x8000
	jz	4f			# avoid any DI overflow
	and	di,0x7FFF
	mov	bx,es
	add	bx,0x0800
	mov	es,bx
4:	cmp	ecx,4
	jne	5f			# the third byte from the end of the
	mov	dx,es			# bootrom image receives the checksum
	mov	si,di			# we just remember its pointer
5:
 data32	loop	3b
	or	al,al			# if checksum is zero already nothing
	jz	9f			# needs to be done
	mov	es,dx
	sub	es:[si],al		# make total checksum zero
9:	pop	es
	ret




/*
 *====================================================================
 *
 * String and constants definitions
 */
	_rodata


/* Startup signature */
sigmsg:	.byte	0x0D, 0x0A
	.ascii	"Netboot Flash EPROM programmer "
	.ascii	VERSION
	.byte	0x0D, 0x0A
	.ascii	COPYRIGHT
	.byte	0x0D, 0x0A
	.byte	0x0D, 0x0A
	.byte	0


/* Boot image header vendor magic ID */
vmagic:	.asciz	VENDOR_MAGIC


/* Magic ID at the beginning of the compressed bootrom image */
lmagic:	.ascii	WINID_MAGIC


/* Error messages */
recerr:	.asciz	"Error in load record data"
sizerr:	.asciz	"Bootrom image too large for Flash EPROM"
winerr:	.asciz	"Bootrom image loader too large"
ilderr:	.asciz	"Error while installing Flash EPROM loader"
eprerr:	.ascii	"Could not find a Flash EPROM."
	.byte	0x0D, 0x0A
	.ascii	"Make sure that you really have a supported network card installed,"
	.byte	0x0D, 0x0A
	.ascii	"and that it contains a supported Flash EPROM. Also make sure that"
	.byte	0x0D, 0x0A
	.ascii	"shadow RAM and caching are disabled for the Flash EPROM using the"
	.byte	0x0D, 0x0A
	.ascii	"BIOS setup, and that the Flash EPROM is activated using the network"
	.byte	0x0D, 0x0A
	.asciz	"card setup program."
eraerr:	.byte	0x0D, 0x0A
	.asciz	"Unable to erase Flash EPROM"
prgerr:	.byte	0x0D, 0x0A
	.asciz	"Timeout while programming Flash EPROM"
vfyerr:	.byte	0x0D, 0x0A
	.asciz	"Verify failed"


/* Messages to be printed for user information */
chkmsg:	.asciz	"Checking for "
fndmsg:	.asciz	"Found "
adrmsg:	.asciz	" at address "
ifmsg:	.asciz	" on "
wrnmsg:	.byte	0x0D, 0x0A
	.ascii	"We are now going to program the netboot bootrom image into the Flash"
	.byte	0x0D, 0x0A
	.ascii	"EPROM. This will erase all current contents. This is your last chance"
	.byte	0x0D, 0x0A
	.ascii	"to abort this process. Once the programming process has been started,"
	.byte	0x0D, 0x0A
	.ascii	"you should not reboot the system or turn off the power."
	.byte	0x0D, 0x0A, 0x00
k1msg:	.asciz	"You have "
k2msg:	.asciz	" seconds to press Escape to abort!  "
abmsg:	.byte	0x0D, 0x0A
	.asciz	"Flash EPROM programming aborted by user request"
eramsg:	.asciz	"Erasing... "
prgmsg:	.asciz	"Programming... "
vfymsg:	.asciz	"Verifying... "
donmsg:	.asciz	"done"
endmsg:	.byte	0x0D, 0x0A
	.asciz	"Press any key to reboot and activate the new rom image..."


/* The following sequence of sounds tells the user about what is going on */

start_snd:

	.word	5, 6541		# sound when program starts
	.word	3, 0
	.word	5, 6541
	.word	3, 0
	.word	10, 11000
	.word	0


error_snd:

	.word	15, 3270		# sound in case of error
	.word	6, 0
	.word	15, 3270
	.word	6, 0
	.word	15, 3270
	.word	0


prog_snd:

	.word	5, 11000		# start of programming
	.word	3, 0
	.word	5, 11000
	.word	3, 0
	.word	10, 6541
	.word	0


end_snd:

	.word	3, 6541		# finished programming
	.word	1, 0
	.word	3, 7342
	.word	1, 0
	.word	3, 8241
	.word	1, 0
	.word	3, 8731
	.word	1, 0
	.word	3, 9800
	.word	1, 0
	.word	3, 11000
	.word	1, 0
	.word	3, 12347
	.word	1, 0
	.word	3, 13081
	.word	0




/*
 *====================================================================
 *
 * Data definitions
 */
	.data


/*
 * List of pointers to flash EPROM interface structures. A flash EPROM
 * interface is the FlashCard for example, but can also be a NIC with
 * an integrated flash EPROM. The structures pointed to by the following
 * list point to various routines to access the flash EPROM built onto
 * any of these interfaces. Note that the FlashCard interface pointer has
 * to come last.
 */

	.extern istruct_3c905xbc
	.extern istruct_rtl8139
	.extern	istruct_i82595
	.extern	istruct_vt610x
	.extern	istruct_fc

istruct_list:

	.word	istruct_3c90xbc		# 3C905xB/C interface definition
	.word	istruct_rtl8139		# RTL8139 interface definition
	.word	istruct_i82595		# EtherExpress interface definition
	.word	istruct_vt610x		# VT610x interface definition
	.word	istruct_fc		# FlashCard interface definition
	.word	0			# end of list




/*
 *====================================================================
 *
 * Variable definitions
 */
	.bss

	.extern	bootpbuf		# space to hold BOOTP record
	.extern	header			# pointer to load record header
	.extern	doexit			# pointer to exit helper routine

	.lcomm	imgptr,4		# pointer to beginning of rom image
	.lcomm	imgaddr,4		# adress of rom image
	.lcomm	imgsize,4		# size of rom image
	.lcomm	ldsize,4		# size of rom image loader
	.lcomm	curif,2			# current interface definition
	.lcomm	curchip,2		# current Flash EPROM definition
	.lcomm	romaddr,4		# linear address of Flash EPROM
	.lcomm	romsize,4		# size of Flash EPROM
	.lcomm	romofs,4		# start offset into Flash EPROM




/*
 *====================================================================
 */
	.end

