Lo-tech XT-CF flash utility source
Jump to navigation
Jump to search
Source code for a simple lo-tech XT-CF flash utility, which can be compiled using Turbo Pascal 6. Compiled code can be downloaded here.
Code
Program Flash;
{
Simple utility to read or write ROM images to or from SST39SF or AMIC A29010
flash chips, as used by the lo-tech XT-CF and Universal ROM Boards.
See http://www.lo-tech.co.uk/wiki/XT-CF-Boards
Written by: James Pearce
Last Updated: 01-Aug-24
VERSION HISTORY:
0.1 - Initial beta
0.2 - Added custom delay routines, to make it work on Pentium and higher
0.3 - Corrected maximum delay loop setting
1.0 - Initial 'finished' release for Peacon XT-CF board:
- removed ROM size parameter (as ROM is hard-wired to be 32KB)
1.1 - Updated release to support Peacon 8-Bit ROM Board:
- Included prompt to confirm writing
- Added (back) ROMSIZE parameter, as it could be 32 or 64KB
- Added ability to merge code
1.2 - Rebranded to lo-tech
Added support for AMIC A29010A 1Mb flash chip
ROMSIZE parameter removed (again) as not needed; just use an image > 32K
1.3 - Added support for SST29EE010 and SST29LE010 flash chips
Corrected usDelayLoop (was calibrated at 500uS instead of 125uS)
1.4 - Corrected bug writing 64K ROMs; removed interactive mode placeholder
COMMAND LINE USAGE:
flash [image-file] [chip-base-address]
Examples:
1. To overwrite the entire chip whose base address is D000, with the
contents of an image file of between 2KB and 64KB:
flash xtide.bin D000
In all cases, the ROM is completely erased and reprogrammed, regardless
of the size of the image file.
}
uses dos, crt;
CONST
FLASH_OK : BOOLEAN = TRUE;
FLASH_ERROR : BOOLEAN = FALSE;
RES_OK : Byte = 0;
RES_UNDER : Byte = 1;
RES_OVER : Byte = 2;
VERSION : String = '1.4';
CHIP_UNKNOWN : String = 'Unknown';
SST29EE010 : String = 'SST 29EE010';
SST29LE010 : String = 'SST 29LE010';
SST512Kb : String = 'SST 512Kb';
SST1Mb : String = 'SST 1Mb';
SST2Mb : String = 'SST 2Mb';
SST4Mb : String = 'SST 4Mb';
AMIC1Mb : String = 'AMIC A29010';
TYPE
PByte = ^BYTE;
VAR
{global variables for the delay routines}
DelayCounter : LongInt;
Hr, Mn,
Sec, S100 : Word;
{global variables recording chip type}
ChipType : String;
procedure start_clock;
{stores the current system clock time in static globals, which
can then be used as a time base by function stop_clock at some
later point}
begin
GetTime(Hr,Mn,Sec,S100);
end;
function stop_clock : longint;
{returns number of miliseconds since start_clock was called,
assuming the day remains the same that is}
var h, m, s, cs : word;
begin
GetTime(h,m,s,cs);
stop_clock := ((h*3600000)+(m*60000)+(s*1000)+(cs*10)) -
((Hr*3600000)+(Mn*60000)+(Sec*1000)+(S100*10));
end;{function stop_clock}
procedure delayLoop( ms : word );
{creates a delay of ms miliseconds (once calibrated) by running
some DIV instructions}
var
a, b, c : word;
x : word;
y : LongInt;
begin
{first check if the in-built delay can be used}
if DelayCounter < 1 then Delay(ms)
else begin
c := WORD(DelayCounter AND $FFFF);
for x := 1 to ms do
begin
for y := 1 to DelayCounter do
begin
a := b div c;
inc(b);
end;{for}
end;{for x}
end;{if/else}
end;{procedure}
procedure usdelayLoop( count : word );
{creates a delay of count x 125 microseconds (once calibrated) by running
some DIV instructions}
var
a, b, c : word;
x : word;
y, ShortWait : LongInt;
begin
c := WORD(DelayCounter AND $FFFF);
ShortWait := DelayCounter SHR 3; {ms to 125us}
for x := 1 to count do
begin
for y := 1 to ShortWait do
begin
a := b div c;
inc(b);
end;{for}
end;{for x}
end;{procedure}
function test_delay : byte;
var
interval : longint;
begin
start_clock;
delayLoop(110); {testing for 110ms delay}
interval := stop_clock;
if interval < 110 then test_delay := RES_UNDER
else if interval > 110 then test_delay := RES_OVER
else test_delay := RES_OK;
end;{function test_delay}
procedure calibrate_delay;
var
res : byte;
interval : LongInt;
begin
{first check if the in-built delay procedure can be used. Otherwise
an XT will take a long time calibrating, for no purpose}
start_clock;
delayLoop(110); {testing for 110ms delay}
interval := stop_clock;
{ if interval = 110 then
DelayCounter := -1 {built-in procedure will be used}
{ else}
begin
{calibration required.}
{div is c.80 clocks on an 8088 => ~60 divs per ms}
DelayCounter := 30;
res := RES_UNDER;
while res <> RES_OK do
begin
res := test_delay;
if DelayCounter >= $40000000 then
begin
{we can't calibrate properly as we're about to overflow}
RES := RES_OK; {end the loop}
DelayCounter := $7FFFFFFF; {maximum value as it's signed}
end else
begin
if res = RES_UNDER then DelayCounter := DelayCounter * 2; {we need more delay}
if res = RES_OVER then DelayCounter := DelayCounter * 2 div 3; {we've overshot}
end;{if/else}
end;{while}
end;{if interval/else}
end;{procedure calibrate_delay}
function HexToWord( s : string ) : word;
{converts the hex represented by the string, to a WORD}
var
i : Byte;
Wd : Word;
Digit : Byte;
Error : Boolean;
begin
Wd := 0; Error := False;
if (length(s) <= 4) then begin
for i := 1 to length(s) do begin
case s[i] of
'0'..'9' : Digit := BYTE(s[i]) - 48;
'A'..'F' : Digit := 10 + (BYTE(s[i]) - 65);
'a'..'f' : Digit := 10 + (BYTE(s[i]) - 97);
else Error := True;
end;{case}
if Error then i := length(s)
else Wd := (Wd SHL 4) + Digit;
end;{for}
end;{if}
if Error then HexToWord := 0
else HexToWord := Wd;
end;{function HexToWord}
function WordToHex( Wd : word ) : string;
{converts a word to ASCII hex}
var
Digit, i : Byte;
AsciiDigit : Char;
s : string;
begin
BYTE(s[0]) := 4; {set string length}
for i := 4 downto 1 do
begin
{get the low 4 bits of Wd to Digit, and SHR Wd 4}
asm
mov ax, Wd
mov bx, ax
and bl, $0F
cmp bl, 10
jb @ZeroToNine
add bl, 17
@ZeroToNine:
add bl, 48
mov AsciiDigit, bl
shr ax, 1
shr ax, 1
shr ax, 1
shr ax, 1
mov Wd, ax
end;{asm}
s[i] := AsciiDigit;
end;{for i}
WordToHex := s;
end;{function WordToHex}
function Caps( s : string ) : string;
{returns string capitalised}
var
i : byte;
tempstr : string;
begin
BYTE(TempStr[0]) := BYTE(s[0]); {set length}
for i := 1 to 255 do
TempStr[i] := UpCase(s[i]); {copy all chars}
Caps := TempStr;
end;{function Caps}
function FlashType( baseaddr : word ) : Boolean;
{Attempts to determine flash chip type from supported types.
Returns FLASH_OK if determined properly, FLASH_ERROR otherwise.
Result itself is stored in global variable ChipType}
var
Location : ^byte;
begin
ChipType := CHIP_UNKNOWN;
{First check for SST39SF chips}
{ChipID routine entry...}
Location := ptr( baseaddr, $5555 );
Location^ := $AA;
Location := ptr( baseaddr, $2AAA );
Location^ := $55;
Location := ptr( baseaddr, $5555 );
Location^ := $90;
{check what's there}
{v1.3 - added 125uS delay, required by SST29EE010 before polling chip ID}
usdelayLoop(1);
Location := ptr( baseaddr, 0 );
If Location^ = $BF then
begin
Location := ptr( baseaddr, 1 );
case Location^ of
$07 : ChipType := SST29EE010;
$08 : ChipType := SST29LE010;
$B4 : ChipType := SST512Kb;
$B5 : ChipType := SST1Mb;
$B6 : ChipType := SST2Mb;
$B7 : ChipType := SST4Mb;
end; {case}
{Exit ChipID routine...}
Location := ptr( baseaddr, $5555 );
Location^ := $AA;
Location := ptr( baseaddr, $2AAA );
Location^ := $55;
Location := ptr( baseaddr, $5555 );
Location^ := $F0;
usdelayLoop(1); {125uS delay; required by SST29EE010}
end {if SST chip detected}
else
begin
{SST not detected; check now for AMIC A29010}
{Autoselect mode routine entry...}
Location := ptr( baseaddr, $555 );
Location^ := $AA;
Location := ptr( baseaddr, $2AA );
Location^ := $55;
Location := ptr( baseaddr, $555 );
Location^ := $90;
{check what's there}
Location := ptr( baseaddr, 0 );
If Location^ = $37 then
begin
Location := ptr( baseaddr, 1 );
case Location^ of
$A4 : ChipType := AMIC1Mb;
end;{case}
{Exit Autoselect mode by issuing reset...}
Location^ := $F0;
end;{if AMIC chip detected}
end;{if/else}
{return result}
If ChipType = CHIP_UNKNOWN then
FlashType := FLASH_ERROR
else
FlashType := FLASH_OK;
end;{function FlashType}
function eraseChip( baseaddr : word ) : boolean;
{erases the entire chip; returns FLASH_OK if successful}
var
Location : ^byte;
DQ7 : Byte;
Done : Boolean;
Error : Boolean;
LoopCount,
LoopMax : Word;
DivRes : Byte;
lastToggle,
thisToggle : Byte;
begin
{Route depends on the chip type}
If (ChipType = AMIC1Mb) or (ChipType = CHIP_UNKNOWN) then
begin
{AMIC A29010 erase command sequence}
Location := ptr( baseaddr, $555 );
Location^ := $AA;
Location := ptr( baseaddr, $2AA );
Location^ := $55;
Location := ptr( baseaddr, $555 );
Location^ := $80;
Location := ptr( baseaddr, $555 );
Location^ := $AA;
Location := ptr( baseaddr, $2AA );
Location^ := $55;
Location := ptr( baseaddr, $555 );
Location^ := $10;
LoopMax := 1875; {set timeout to 75s (1875 x 40ms)}
end else begin
{SST39SF and SST29xx erase command code...}
Location := ptr( baseaddr, $5555 );
Location^ := $AA;
Location := ptr( baseaddr, $2AAA );
Location^ := $55;
Location := ptr( baseaddr, $5555 );
Location^ := $80;
Location := ptr( baseaddr, $5555 );
Location^ := $AA;
Location := ptr( baseaddr, $2AAA );
Location^ := $55;
Location := ptr( baseaddr, $5555 );
Location^ := $10;
LoopMax := 25; {set timeout to 1s (25 x 40ms)}
end;{Chip specific erase command entry select}
{then wait}
Done := False;
Error := False;
LoopCount := 0;
Location := ptr( baseaddr, 0 ); {we'll poll address 0 to check when done}
if (ChipType = SST29EE010) or (ChipType = SST29LE010) then
begin
usDelayLoop(2); {250uS delay before we can poll}
{SST29xE010 provides a busy toggle bit (DQ6) that must be polled}
lastToggle := Location^ AND $80; {get DQ6 from device}
while (not done) and (not error) do
begin
inc(LoopCount);
delayLoop(40); {40ms delay}
thisToggle := Location^ AND $80; {get DQ6 from device}
if (thisToggle = lastToggle) then Done := true;
lastToggle := thisToggle;
if LoopCount mod 100 = 0 then write('.'); {provide an indication things are still running}
if (LoopCount = LoopMax) or KeyPressed then Error := true; {trap timeout based on chip type}
end;{while}
if (Done) then begin
{erase seemed to succeed, so enable SDP}
Location := ptr( baseaddr, $5555 );
Location^ := $AA;
Location := ptr( baseaddr, $2AAA );
Location^ := $55;
Location := ptr( baseaddr, $5555 );
Location^ := $A0;
end;{enable SDP}
end else begin
{other chips are handled with data polling}
while (not done) and (not error) do
begin
inc(LoopCount);
delayLoop(40); {40ms delay}
if Location^ = $FF then Done := true;
if LoopCount mod 100 = 0 then write('.'); {provide an indication things are still running}
if (LoopCount = LoopMax) or KeyPressed then Error := true; {trap timeout based on chip type}
end;{while}
end;{if/else}
If Error then
begin
If KeyPressed then write('Interrupted by keypress.')
else begin
WriteLn('** Timeout **');
Write('Expected FFh, but found ',WordToHex(Location^),'h.');
end;{else}
eraseChip := FLASH_ERROR;
end else eraseChip := FLASH_OK;
end;{function eraseChip}
function writeByte( baseaddr : word; offset : word; b : byte ) : BOOLEAN;
{writes byte b to baseaddr:offset, returns FLASH_OK if successful}
var
Location : ^byte;
DQ7 : Byte;
Done : Boolean;
Error : Boolean;
DivRes : byte;
usDelay,
LoopCount,
LoopMax : Word;
begin
{Routine depends on the chip type}
If (ChipType = AMIC1Mb) or (ChipType = CHIP_UNKNOWN) then
begin
{AMIC A29010 erase command sequence}
Location := ptr( baseaddr, $555 );
Location^ := $AA;
Location := ptr( baseaddr, $2AA );
Location^ := $55;
Location := ptr( baseaddr, $555 );
Location^ := $A0;
Location := ptr( baseaddr, offset );
Location^ := b;
usDelay := 1; {1 = 125us delay per byte}
LoopMax := 3; {timeout after 375us (3x125)}
end else begin
{SST39SF byte program command sequence}
Location := ptr( baseaddr, $5555 );
Location^ := $AA;
Location := ptr( baseaddr, $2AAA );
Location^ := $55;
Location := ptr( baseaddr, $5555 );
Location^ := $A0;
Location := ptr( baseaddr, offset );
Location^ := b;
usDelay := 1; {1 = 125us delay per byte}
LoopMax := 2; {timeout after 250us (2x125)}
end;{if/else}
{then wait}
Done := False;
Error := False;
LoopCount := 0;
while (not done) and (not error) do
begin
inc(LoopCount);
usdelayLoop(usDelay);
if Location^ = b then Done := true;
if (LoopCount = LoopMax) or KeyPressed then Error := true;
end;{while}
If Error then writeByte := FLASH_ERROR
else writeByte := FLASH_OK;
end;{function writeByte}
function writePage( baseaddr, pageaddr : word; var PageData : PByte ) : BOOLEAN;
{writes a 128 byte page to the device, returns FLASH_OK if successful}
{baseaddr = ROM chip base address
pageaddr = offset of page to be programmed (0, 128, 256...)
pagedata = pointer to 128 bytes of data to be programmed.}
var
Location : ^byte;
DQ7 : Byte;
Done : Boolean;
Error : Boolean;
DivRes : byte;
usDelay,
LoopCount,
LoopMax : Word;
i : byte;
begin
Error := True;
{Routine depends on the chip type}
if (ChipType = SST29EE010) or (ChipType = SST29LE010) then
begin
Location := ptr( baseaddr, 0 );
asm cli end; {clear interrupts - each bytes has to be sent within 100uS}
{SST29xE page SDP unlock sequence}
Location := ptr( baseaddr, $5555 );
Location^ := $AA;
Location := ptr( baseaddr, $2AAA );
Location^ := $55;
Location := ptr( baseaddr, $5555 );
Location^ := $A0;
{device should now (immediately) accept 128 bytes of data}
Location := ptr( baseaddr, pageaddr );
for i := 1 to 128 do
begin
Location^ := PageData^;
inc(Location);
inc(PageData);
end;{for}
{data sent to buffer - now wait for data to be ingested}
asm sti end; {re-enable interupts}
usdelayLoop(2); {250uS delay before we can check}
{then wait}
Done := False;
Error := False;
LoopCount := 0;
usDelay := 4; {4 = 500us delay per byte}
LoopMax := 20; {timeout after 10ms (20 x 500us)}
while (not done) and (not error) do
begin
inc(LoopCount);
usdelayLoop(usDelay);
if Location^ = PageData^ then Done := true;
if (LoopCount = LoopMax) or KeyPressed then Error := true;
end;{while}
end;{if/else}
If Error then writePage := FLASH_ERROR
else writePage := FLASH_OK;
end;{function writePage}
procedure FlashChip( imagefile, baseaddr : string );
{called once parameters are validated; this does the work!}
{$R- disable range checking, as the ROM is directly accessed and could be
64K exactly}
type
TRomImage = array[0..31767] of byte;
var
RomImage1,
RomImage2 : ^TRomImage;
Rom : ^TRomImage; {used to compare}
CurrentPage : PByte;
RomFile : File;
BytesRead : Longint;
BytesRead1,
BytesRead2,
CurrentPos : word;
Res : Integer;
RomBaseAddr : word;
Error : Boolean;
X, Y : Byte;
UpdateInt : word;
UpdateCount : word;
Percent : Byte;
Ch : Char;
CurrentByte : Byte;
begin
{load the ROM image file}
write('Opening ROM image... ');
new(RomImage1); new(RomImage2);
assign(RomFile,imagefile);
{$I- disable IO error checking}
reset(RomFile,1);
{$I+ }
if IOResult <> 0 then
begin
WriteLn('Unable to open file.');
Exit;
end;{if}
if (FileSize(RomFile) < 2048) or (FileSize(RomFile) > 65536) then
begin
{ROM file too small or too large}
WriteLn('ROM file must be between 2 and 64K.');
Close(RomFile);
Exit;
end else WriteLn('OK');
{get chip base address}
RomBaseAddr := HexToWord( baseaddr ); {starting location of ROM}
{Display detected flash type}
If FlashType(RomBaseAddr) = FLASH_OK then
WriteLn( 'Detected ', ChipType, ' Flash Chip at ', baseaddr, 'h' )
else
WriteLn( 'Unable to determine flash chip type. Attempting JDEC programming.');
{read the file}
Write('Reading... ');
BytesRead2 := 0;
BlockRead(RomFile,RomImage1^,32768,BytesRead1);
if not EoF(RomFile) then
BlockRead(RomFile,RomImage2^,32768,BytesRead2);
BytesRead := Longint(BytesRead1) + Longint(BytesRead2);
WriteLn( BytesRead, ' bytes read OK.');
Close(RomFile);
{erase the chip}
Write('Erasing ' );
if (eraseChip(RomBaseAddr) = FLASH_OK) then
begin
{erase was successful; write out new code}
writeLn('OK');
Error := False;
If BytesRead > 0 then
begin
WriteLn('Programming... ');
UpdateCount := 0;
Percent := 0;
X := WhereX;
Y := WhereY;
write( '0%' );
if (ChipType = SST29EE010) or (ChipType = SST29LE010) then
begin
{page-mode programming}
while CurrentPos < Pred(BytesRead) do
begin
if CurrentPos < 32768 then
CurrentPage := Addr(RomImage1^[CurrentPos])
else
CurrentPage := Addr(RomImage2^[CurrentPos-32768]);
if (writePage(RomBaseAddr,CurrentPos,CurrentPage) = FLASH_ERROR) then
begin
GotoXY(X,Y);
Writeln('Failed writing at page ',(CurrentPos div 128));
CurrentPos := Pred(BytesRead); {terminate loop}
Error := TRUE; {record error state}
end else begin
{programming that page succeeded - update screen and counters}
CurrentPos := CurrentPos + 128; {advance to the next page}
Percent := (CurrentPos*100) div BytesRead;
GotoXY(X,Y);
write(Percent,'%');
end;{if (writePage...}
end;{while}
end else begin
{byte-mode programming}
UpdateInt := BytesRead div 100;
for CurrentPos := 0 to Pred(BytesRead) do
begin
inc(UpdateCount);
if (UpdateCount = UpdateInt) then
begin
{update percentage displayed on-screen}
GotoXY(X,Y);
inc(Percent);
write(Percent,'%');
UpdateCount := 0;
end;{screen update}
if CurrentPos < 32768 then
CurrentByte := RomImage1^[CurrentPos]
else
CurrentByte := RomImage2^[CurrentPos-32768];
if (writeByte(RomBaseAddr,CurrentPos,CurrentByte) = FLASH_ERROR) then
begin
GotoXY(X,Y);
Writeln('Failed writing at byte ',CurrentPos);
CurrentPos := Pred(BytesRead); {terminate loop}
Error := TRUE; {record error state}
end;{if}
end;{for}
end;{if ChipType.../else}
if Not Error then begin
{write was OK; now check what's there}
GotoXY(X,Y);
WriteLn('OK ');
Write('Comparing... ');
Rom := Ptr( RomBaseAddr, 0 ); {access the ROM directly}
for CurrentPos := 0 to Pred(BytesRead) do
begin
if CurrentPos < 32768 then
CurrentByte := RomImage1^[CurrentPos]
else
CurrentByte := RomImage2^[CurrentPos-32768];
if Rom^[CurrentPos] <> CurrentByte then
begin
Error := True;
WriteLn('Error at offset ', WordToHex(CurrentPos), 'h');
CurrentPos := Pred(BytesRead);
end;{if}
end;{for}
If Not Error then WriteLn('OK!');
end;{if not error / else}
end;{if BytesRead > 0}
end {if eraseChip}
else
{chip erase failed}
WriteLn('Erase operation FAILED.');
Dispose(RomImage1); Dispose(RomImage2);
end;{procedure FlashChip}
function BaseOK( s : string ) : Boolean;
{checks that the base address i:
- in the correct range, A000 - F000
- 4K aligned
}
var
BaseAddr : Word;
begin
BaseAddr := HexToWord( s );
if ((BaseAddr AND $FF) = 0) and (BaseAddr >= $A000) and
(BaseAddr <= $F000) then
BaseOK := True
else
BaseOK := False;
end;{function BaseOK}
function ParamSpecified(s : string) : boolean;
{checks all command line arguments for s, returning true if found}
var i : word; found : boolean;
begin
found := false;
for i := 1 to ParamCount do
if Copy(ParamStr(i),1,Length(s)) = s then found := true;
ParamSpecified := found;
end;{function ParamSpecified}
Function ParamsOK( P1, P2 : string ) : Boolean;
{checks parameters, returns true if look OK}
var
f : file;
OK : Boolean;
begin
OK := True;
assign(f,P1);
{$i- } reset(f); {$i+ }
if IOResult <> 0 then begin
OK := False;
WriteLn('Couldn''t open file ', P1, '.');
end else begin
{found file OK}
close(f);
if not BaseOK(P2) then OK := False;
end;
if OK then
begin
{parameters suggest we're good to go - calibrate the timing loops}
write('Calibrating delay loops...');
calibrate_delay;
writeLn(' calibration factor is ', DelayCounter, '.');
end;{if OK}
ParamsOK := OK;
end;{function ParamsOK}
Procedure DisplayHelp;
begin
WriteLn('Supports SST 29xE010/39SF0x0, and AMIC A29010 flash chips.');
WriteLn;
WriteLn('Usage:');
WriteLn;
WriteLn('Write an image file to the ROM. The ROM is completely');
WriteLn('erased, then the file copied. Can be used with ROMs');
WriteLn('between 2KB and 64KB. Note: Checksum is not calculated.');
WriteLn;
WriteLn('eg: flash [image-file] [base-address]');
WriteLn;
WriteLn(' [image-file] - ROM image, i.e. xtide.bin');
WriteLn(' [base-address] - hex ROM window base address, i.e. D000');
WriteLn(' must be A000-F800');
WriteLn;
end;{procedure DisplayHelp}
var Ch : Char;
BEGIN
WriteLn('Lo-tech XT-CF and 8-bit ROM Board Flash Programmer, version ',VERSION);
If ParamsOK( ParamStr(1), ParamStr(2) ) then
begin
FlashChip( ParamStr(1), ParamStr(2) );
WriteLn;
WriteLn('If BIOS images currently in use have been updated, you MUST');
WriteLn('now reboot.');
WriteLn;
Write('Press any key to return to DOS, or CTRL-ALT-DEL to restart.');
{clear keyboard buffer, in case user interrupted the process}
while keypressed do Ch := ReadKey;
{now pause}
repeat until keypressed;
if keypressed then Ch := Readkey;
end
else DisplayHelp;
END.{program}