Hogwarts Wand Docs: ../server/ww.c

Wand Sourcecode: ../server/ww.c

//
// ww.c - program & UI for talking to the wand using the serial port
// 
// Copyright (C) 2006  Nathan (Acorn) Pooley
// 
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// version 2 as published by the Free Software Foundation.
// 
// 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 (gpl.txt) 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
// 
// You can contact the author, Nathan (Acorn) Pooley, by writing
// to Nathan (Acorn) Pooley, 949 Buckeye Drive, Sunnyvale, CA  94086, USA
// or through the form at http://www.rawbw.com/~acorn/wand/feedback.html
//

//#@DOC@ program & UI for talking to the wand using the serial port


//###########################################################################
//############################### INCLUDES ##################################
//###########################################################################

#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include <sys/time.h>
#include <time.h>
#include "ac_serialio.h"
#include "ac_fdio.h"
#include "wandio.h"
#include "ac_hexfile.h"
#include "ac_log.h"

#include "kernel.h"

//###########################################################################
//############################### DEFINES ###################################
//###########################################################################

#define PROG_MAX            65536
#define DEFAULT_HEX_FILE "../sw/wand.hex"
#define TMP_FILENAME    ".ww_tmp_data_file.txt"



//
// Each bit represents 64 bytes (0x20 bytes)
//
#define UI_PROG_IS_DIRTY_100(ui,addr) \
        ((ui)->wandProgDirty[addr>>(6+5)] & (7<<((addr>>6)&0x1c)))

#define UI_PROG_IS_DIRTY(ui,addr) \
        ((ui)->wandProgDirty[addr>>(6+5)] & (1<<((addr>>6)&0x1f)))

#define UI_PROG_SET_DIRTY(ui,addr) \
        ((ui)->wandProgDirty[addr>>(6+5)] |= (1<<((addr>>6)&0x1f)))

#define UI_PROG_CLR_DIRTY(ui,addr) \
        ((ui)->wandProgDirty[addr>>(6+5)] &= ~(1<<((addr>>6)&0x1f)))

//###########################################################################
//############################### TYPEDEFS ##################################
//###########################################################################

struct WandUiRec;
typedef int (*UiCmdFunc)(struct WandUiRec *ui, char *arg);

typedef enum UiCmdFlagsEnum {
    UI_CFLG_NOARGS      = 0x00000001,
    UI_CFLG_NEEDARGS    = 0x00000002,
    UI_CFLG_EXACTMATCH  = 0x00000004,

    UI_CFLG_END = 0x7fffffff
} UiCmdFlagsEnum;

typedef struct UiCmdRec {
    UiCmdFunc    func;
    const char  *name;
    int          flags;     // from UiCmdFlagsEnum
    const char  *helpArgs;
    const char  *help1;
    const char  *help2;
    int          namelen;
} UiCmd;

typedef struct WandUiRec {
    WandInfo        *wand;
    AcHexByte       *wandProg;
    unsigned int    *wandProgDirty; // 1 bit for each 64 byte block
    int              wandProgAllClean;

    const UiCmd     *cmdList;

    AcHexFile       *hexFile;

    char            *saveName;      // name to insert in save filenames
    char            *lastOut;       // last wand output
    char            *lastSaveName;

    //
    // header from file & wand
    //
    kernelHeader    headerFile;
    kernelHeader    headerWand;

    //
    // addrMax is the max prog addr we care about
    //
    AcHexAddress     addrMax;       
    AcHexAddress     addrMaxUser;
    AcHexAddress     addrMaxFile;
    AcHexAddress     addrMaxWand;

    //
    // addrMin is the min prog addr we care about
    //
    AcHexAddress     addrMin;
    AcHexAddress     addrMinUser;

    AcFdGroup       *selectGroup;

    AcLog           *log;
    int              logToWand;
    int              logFromWand;
    int              logCmd;
    int              logMsg;

    int              debugLevel;
    int              quit;
} WandUi;

//###########################################################################
//############################### GLOBALS ###################################
//###########################################################################

AcHexByte gbuf[0x1000];

//###########################################################################
//############################### PROTOTYPES ################################
//###########################################################################

static int UiCmdGraph(WandUi *ui, char *args);

//###########################################################################
//############################### CODE ######################################
//###########################################################################

//===========================================================================
// GetReply() - ask for an answer
//===========================================================================
static const char *GetReply(void)
{
    static char buf[100];
    char *s = buf;
    while(1) {
        int c = getc(stdin);
        if (c == '\n') {
            *(s++) = 0;
            return buf;
        }
        *(s++) = c;
        if (s-buf >= sizeof(buf)-3) s--;
    }
}

//===========================================================================
// UiGetArgs() - get numeric args.
//===========================================================================
//
// argsp  - pointer to args - pointer is updated to point to next arg
// values - array of longs where results will go
// max    - number of longs lin values array
//
// RETURN: number of args found
//
static int UiGetArgs(
                WandUi *ui,
                char **argsp,
                unsigned long *values,
                int max)
{
    int i;
    char *s = *argsp;
    while(isspace(*s)) s++;
    *argsp = s;

    for (i=0; i<max; i++) {
        unsigned long val;
        char *e = 0;

//printf("a1='%s'\n",s);
        if (!*s) {
            return i;
        }

        val = strtoul(s,&e,0);

        if (!e || e == s) {
            break;
        }

        s = e;
//printf("a2='%s'\n",s);
        values[i] = val;
        while(isspace(*s)) s++;
//printf("a3='%s'\n",s);
        *argsp = s;
    }
//printf("a4='%s'\n",*argsp);
    return i;
}

//===========================================================================
// UiHeaderValid() - true if header is valid
//===========================================================================
static int UiHeaderValid(WandUi *ui, kernelHeader *hdr)
{
    if (
        !hdr ||
        hdr->xa5    != 0xa5 ||
        hdr->x5a    != 0x5a ||
        hdr->pad0   != 0x00 ||
        hdr->pad1   != 0x00 ||
        hdr->pad2   != 0x00 ||
        hdr->version          <  10 ||
        hdr->kk_app_main_high <  0x01 ||
        hdr->kk_app_main_low  != 0x00) {
        return 0;
    }
    return 1;
}

//===========================================================================
// UiHeaderCompare() - 0 if headers are the same, nonzero otherwise
//===========================================================================
static int UiHeaderCompare(
                    WandUi *ui,
                    kernelHeader *hdr1,
                    kernelHeader *hdr2)
{
    if (!UiHeaderValid(ui,hdr1) ||
        !UiHeaderValid(ui,hdr2) ||
        hdr1->version          != hdr2->version ||
        hdr1->kk_app_main_high != hdr2->kk_app_main_high ||
        hdr1->kk_app_main_low  != hdr2->kk_app_main_low) {
        return 1;
    }
    return 0;
}

//===========================================================================
// UiCheckAddrMaxMin() - calc max/min address
//===========================================================================
static void UiCheckAddrMaxMin(WandUi *ui)
{
    ui->addrMin = 0;
    if (!UiHeaderCompare(ui, &ui->headerWand, &ui->headerFile)) {
        ui->addrMin =   (ui->headerWand.kk_app_main_high << 8) +
                        (ui->headerWand.kk_app_main_low);
    }
    if (ui->addrMinUser != AC_HEX_INVALID_ADDRESS) {
        ui->addrMin = ui->addrMinUser;
    }

    ui->addrMax = 0;
    if (ui->addrMaxFile != AC_HEX_INVALID_ADDRESS &&
        ui->addrMax < ui->addrMaxFile) {
        ui->addrMax = ui->addrMaxFile;
    }
    if (ui->addrMaxWand != AC_HEX_INVALID_ADDRESS &&
        ui->addrMax < ui->addrMaxWand) {
        ui->addrMax = ui->addrMaxWand;
    }
    if (ui->addrMaxUser != AC_HEX_INVALID_ADDRESS) {
        ui->addrMax = ui->addrMaxUser;
    }
    
    if (ui->addrMax <= ui->addrMin) {
        ui->addrMax  = ui->addrMin + 64;
    }

    ui->addrMin &= ~63UL;
    ui->addrMax = (ui->addrMax + 63) & ~63UL;
}

//===========================================================================
// UiSetProgDirty() - mark range that is unknown (dirty)
//===========================================================================
static void UiSetProgDirty(WandUi *ui, unsigned long start, unsigned long end)
{
    start &= ~63UL;
    end   = (end + 63)&~63;

    if (start == 0 && end == 0) {
        end = PROG_MAX;
    }
    if (end > PROG_MAX) {
        end = PROG_MAX;
    }

    for (; start<=end; start+=64) {
        UI_PROG_SET_DIRTY(ui,start);
    }
    ui->wandProgAllClean = 0;
}

//===========================================================================
// UiSetProgClean() - mark range that was read from wand (clean)
//===========================================================================
static void UiSetProgClean(WandUi *ui, AcHexAddress start, AcHexAddress end)
{
    AcHexAddress addr;

    if (ui->wandProgAllClean) return;

    start = (start + 63)&~63UL;
    end  &= ~63UL;

    for (addr=start; addr<=end; addr+=64) {
        UI_PROG_CLR_DIRTY(ui,addr);
    }


    //
    // Check if whole program is clean
    //
    start =  ui->addrMin & ~63UL; 
    end   = (ui->addrMax + 63) & ~63UL; 
    for (addr=start; ; addr+=64) {
        if (addr > end) {
            ui->wandProgAllClean = 1;
            break;
        }
        if (UI_PROG_IS_DIRTY(ui,addr)) {
            break;
        }
    }
}

//===========================================================================
// UiReadProgOld() - get program from wand
//===========================================================================
static int UiReadProgOld(
                WandUi *ui,
                AcHexAddress start, 
                AcHexOffset  size,
                int force)
{
    AcHexByte *buf;
    AcHexAddress addr = start;
    AcHexAddress end  = start + size;
    int errCnt = 0;
    int lastGood=0;

    start = start      & ~63UL;
    end   = (end + 63) & ~63UL;

    if (end > PROG_MAX) {
        end = PROG_MAX;
    }

    buf = ui->wandProg;

    addr = start;
    while(addr < end) {
        int err = 0;
        AcHexAddress addr2;
        char *wand_output;
        if (!force && !UI_PROG_IS_DIRTY(ui,addr)) {
            addr = (addr + 64) & ~63UL;
            continue;
        }
        if (!lastGood) {
            wand_output = WandCmdfStr(ui->wand,"P%04x",
                                            (addr-1)&0xffff);
            if (wand_output) {
                free(wand_output);
            } else {
                err=1;
            }
        }
        if (!err) {
            wand_output = WandCmdfStr(ui->wand,"");
            if (!wand_output) {
                err = 1;
            }
        }
        
        if (!err) do {
            char *s = wand_output;
            char *e;
            err = 1;
            if (strncmp(s,"flash 0x",7)) break;
            s += 8;
            addr2 = strtol(s,&e,16);
            if (e!=s+4 || *e!=':' || addr2 != addr) break;

            s = e+1;
            while(1) {
                unsigned int val;
                while(isspace(*s)) s++;
                if (addr2 >= end) {
                    err = 0;
                    break;
                }
                if (!*s && addr != addr2) {
                    err = 0;
                    break;
                }
                if (!isxdigit(s[0]) || !isxdigit(s[1])) break;

                if (isdigit(s[0])) {
                    val = 16 * (s[0] - '0');
                } else {
                    val = 16 * (toupper(s[0]) - 'A' + 10);
                }
                if (isdigit(s[1])) {
                    val += (s[1] - '0');
                } else {
                    val += (toupper(s[1]) - 'A' + 10);
                }
                buf[addr2] = val;
                addr2 += 1;
                s += 2;
            }

        } while(0);

        lastGood = !err;
        if (err) {
            errCnt++;
            AcLogPrintf(0,4,"6:Error (%d) in wand output (b):\n",err);
            AcLogPrintf(0,4,"6:Got from wand: '%s'\n",wand_output);
            if (errCnt > 5) {
                AcLogPrintf(0,4,"3:Too many errors - aborting\n");
                if (wand_output) free(wand_output);
                if (addr >= start + 64 + 64) {
                    UiSetProgClean(ui, start, addr-64-64);
                }
                return -1;
            }
            WandRecoverWait(ui->wand);
        } else {
            AcBytePrint(&buf[addr],addr2-addr,addr,0);
            if (errCnt) errCnt--;
            addr = addr2;
        }
        if (wand_output) free(wand_output);
    }

    UiSetProgClean(ui, start, end-1);

    return 0;
}

//===========================================================================
// UiReadProg() - get program from wand
//===========================================================================
static int UiReadProg(
                WandUi *ui,
                AcHexAddress start0, 
                AcHexOffset  size0,
                int force)
{
    AcHexByte *buf;
    AcHexAddress start = start0;
    AcHexAddress addr = start;
    AcHexAddress end  = start + size0;
    int errCnt = 0;

    start = start        & ~0xffUL;
    end   = (end + 0xff) & ~0xffUL;

    if (end > PROG_MAX) {
        end = PROG_MAX;
    }

    buf = ui->wandProg;

    addr = start;
    while(addr < end) {
        char *s;
        int err = 0;
        AcHexAddress end2 = end;
        AcHexAddress addr2;
        char *wand_output;
        addr = addr & ~0xffUL;
        if (!force) {
            if (!UI_PROG_IS_DIRTY_100(ui,addr)) {
                addr = (addr + 0x100) & ~0xffUL;
                continue;
            }
            end2 = addr + 0x100;
            while(1) {
                if (end2 >= addr + 0x2000) break;
                if (end2 >= end) break;
                if (!UI_PROG_IS_DIRTY_100(ui,end2)) break;
                end2 += 0x100;
            }
        }
        if (end2 - addr > 0x0800) {
            end2 = addr + 0x0800;
        }
        wand_output = WandCmdfStr(ui->wand,"F%02x%02x",
                        (int)(addr>>8),
                        (int)(end2>>8));
        if (!wand_output || !wand_output[0]) err = 2;

        s = wand_output;
        if (!err) while(1) {
            int state;

            while(isspace(*s)) s++;
            if (*s == 0) break;

            addr2 = 0;
            for (state=0; state<4; state++, s++) {
                if (!isxdigit(*s)) {
                    err = 3+state;
                    break;
                }
                addr2 *= 16;
                if (isdigit(*s)) {
                    addr2 += *s - '0';
                } else {
                    addr2 += tolower(*s) - 'a' + 10;
                }
            }
            if (err) break;
            if (*s != ':') {
                err = 8;
                break;
            }
            if (addr != addr2) {
                err=9;
                break;
            }
            s++;

            while(1) {
                int val = 0;
                if (*s == 0x0a || *s == 0x0d || *s == 0x00) break;
                if (isdigit(*s)) {
                    val = *s - '0';
                } else if (isxdigit(*s)) {
                    val = tolower(*s) - 'a' + 10;
                } else {
                    err = 10;
                    break;
                }
                s++;
                val *= 16;
                if (isdigit(*s)) {
                    val += *s - '0';
                } else if (isxdigit(*s)) {
                    val += tolower(*s) - 'a' + 10;
                } else {
                    err = 11;
                    break;
                }
                s++;
                buf[addr2] = val;
                addr2 += 1;
            }
            if ((addr2 & 0xff) == 0) {
                AcLogPrintf(0,4,"0:read 0x%04x - 0x%04x\n",
                    (int)addr,
                    (int)addr2);
                addr = addr2;
            }
        }

        if (err) {
            errCnt++;
            AcLogPrintf(0,4,"6:Error (%d) in wand output (a):\n",err);
            AcLogPrintf(0,4,"6:Got from wand: '%s'\n",wand_output);
            if (errCnt > 5) {
                AcLogPrintf(0,4,"3:Too many errors - use old method\n");
                if (wand_output) free(wand_output);
                WandRecoverWait(ui->wand);
                return UiReadProgOld(ui,start0,size0,force);
            }
            WandRecoverWait(ui->wand);
        } else {
            //printf("Read 0x%04x\n",(int)addr);
            //AcBytePrint(&buf[addr],addr2-addr,addr,0);
            if (errCnt) errCnt--;
            addr = addr2;
        }
        if (wand_output) free(wand_output);
    }

    UiSetProgClean(ui, start, end-1);

    return 0;
}

//===========================================================================
// UiReadMinProgAddr() - calculate addrMin from wand and file
//===========================================================================
static void UiReadMinProgAddr(WandUi *ui)
{
    if (!UiReadProg(
                ui, 
                KERNEL_HEADER_ADDR, 
                sizeof(ui->headerWand), 
                0)) {
        memcpy(&ui->headerWand, ui->wandProg + 0x10, sizeof(ui->headerWand));
    } else {
        memset(&ui->headerWand, 0, sizeof(ui->headerWand));
    }

    UiCheckAddrMaxMin(ui);
}

//===========================================================================
// UiCmdDumpEEProm() show eeprom contents
//===========================================================================
static int UiCmdDumpEEProm(WandUi *ui, char *args)
{
    AcHexAddress addr;
    AcHexAddress start;
    AcHexAddress end;
    unsigned long vals[2];
    int cnt;
    int rv = 0;

    cnt = UiGetArgs(ui, &args, vals, 2);

    if (*args) {
        AcLogPrintf(0,4,"0:Error: need 0-2 address arguments\n");
        return -1;
    }

    if (cnt == 1) {
        start = vals[0];
        end   = vals[0];
    } else if (cnt == 2) {
        start = vals[0];
        end   = vals[1];
    } else {
        start = 0;
        end   = 0xff;
    }

    for (addr=start; addr<=end; addr++) {
        if (WandCmdf(ui->wand, "E%04x",(int)(addr&0xffffUL))) {
            rv = -1;
            break;
        }
    }

    return rv;
}

//===========================================================================
// UiCmdInvalProg() - invalidate prog mem
//===========================================================================
static int UiCmdInvalProg(WandUi *ui, char *args)
{
    const char *reply;

    //
    // Confirm inval
    //
    AcLogPrintf(0,4,"0:\nSure you want to invalidata prog mem cp? [y/n] ");
    fflush(stdout);
    reply = GetReply();
    if (toupper(reply[0]) != 'Y') {
        AcLogPrintf(0,4,"0:Aborting kload command\n");
        return -1;
    }

    UiSetProgDirty(ui, 0,0);

    return 0;
}

//===========================================================================
// UiCmdDumpProg() - show program in wand
//===========================================================================
static int UiCmdDumpProg(WandUi *ui, char *args)
{
    AcHexAddress start;
    AcHexAddress end;
    unsigned long vals[2];
    int cnt;
    int force_read = 0;


    cnt = UiGetArgs(ui, &args, vals, 2);
    if (*args == 'f') {
        force_read = 1;
        args++;
    }
    while(isspace(*args)) args++;

    if ((cnt && cnt != 2) || *args) {
        AcLogPrintf(0,4,"0:Error: need 2 address arguments\n");
        return -1;
    }

    if (cnt == 2) {
        start = vals[0];
        end   = vals[1];
    } else {
        UiReadMinProgAddr(ui);
        start = ui->addrMin;
        end   = ui->addrMax;
    }

    if (!ui->wandProgAllClean || force_read) {
        AcLogPrintf(0,4,"6:Reading wand...\n");
        if (UiReadProg(ui,start,end-start,force_read)) return -1;
    }

    AcLogPrintf(0,4,"4:Wand Contents:\n");

    AcBytePrint((char*)ui->wandProg + start,end-start,start,1);
    printf("\n");
    return 0;
}

//===========================================================================
// UiCmdDumpVars() - dump variable memory
//===========================================================================
static int UiCmdDumpVars(WandUi *ui, char *args)
{
    AcHexByte buf[10000];
    unsigned long start, end, p;
    int lastGood = 0;
    int end_at_zeros=0;
    int zcnt=0;
    int dots=0;
    int rv = 0;


    unsigned long vals[2];
    int cnt;

    cnt = UiGetArgs(ui, &args, vals, 2);
    if (*args == 'z') {
        end_at_zeros = 1;
        args++;
    }
    while(isspace(*args)) args++;

    if (cnt != 2 || *args) {
        AcLogPrintf(0,4,"0:Error: need 2 address arguments\n");
        return -1;
    }


    start = vals[0];
    end   = vals[1];

    if (end < start) {
        AcLogPrintf(0,4,"0:Error: start follows end\n");
        return -1;
    }
    end++;
    if (end - start > sizeof(buf)) {
        AcLogPrintf(0,4,"0:Error: too long a range requested\n");
        return -1;
    }

    AcLogPrintf(0,4,"6:Reading wand...");
    fflush(stdout);

    for (p=start; p<end; ) {
        int retry = 3;

        for (; retry; retry--) {
            char *res;
            char *s;
            if (lastGood) {
                res = WandCmdfStr(ui->wand,"");
            } else {
                res = WandCmdfStr(ui->wand,"V%04x",p);
            }
            lastGood = 0;
            if (!res) continue;
            s = res;
            while(*s && *s != ':') s++;
            if (*s == ':') {
                unsigned long oldp = p;
                
                while(p<end) {
                    char *e;
                    int val;
                    s++;
                    val = strtoul(s,&e,16);
                    if (e != s+2) break;
                    buf[p-start] = val;
                    if (val ==0) {
                        zcnt++;
                    } else {
                        zcnt = 0;
                    }
                    s = e;
                    p++;
                    lastGood = 1;
                }
                if (p == oldp) continue;
                break;
            }
        }
        if (zcnt >= 32 && end_at_zeros) {
            AcLogPrintf(0,4,"9:\nFound 32 zeros - ending\n");
            end = p;
            break;
        }
        if (!retry) {
            AcLogPrintf(0,4,"0:\nFailed to read register %04lx\n",p);
            end = p;
            rv = -1;
            break;
        }
        if (end-start > 0x200) {
            if ((p&3)==0) {
                AcLogPrintf(0,4,"3:.");
                fflush(stdout);
                dots=1;
            }
        }
    }
    if (dots) {
        AcLogPrintf(0,4,"3:\n");
    }

    memset(gbuf,0,sizeof(gbuf));
    memcpy(gbuf,buf,end-start);

    if (end > start) {
        AcBytePrint(buf,end-start,start,1);
    }
    return rv;
}

//===========================================================================
// UiDoWandOut() - show motions
//===========================================================================
static void UiDoWandOut(void *data, const char *str)
{
    WandUi *ui = data;

#if 0
    static char *dirStr[] = {
        "up","right","down","left"
    };
    int i=0;
#endif


    if (!str) str = ui->lastOut;
    if (!str) return;

    if (!strncmp(str,"Motion=",7)) {
        
        if (ui->lastSaveName) {
            free(ui->lastSaveName);
            ui->lastSaveName = 0;
        }
        if (!UiCmdGraph(ui, "auto")) {
            if (ui->lastSaveName) {
                char cmdBuf[100];
                snprintf(cmdBuf,sizeof(cmdBuf),
                    "fmtb_parse -s -o < %s | grep RESULT | tee mresult.txt",
                    ui->lastSaveName);
                system(cmdBuf);
                system("grep '[*+]' mresult.txt > mresult2.txt");
                //system("post -H < mresult.txt");
                sprintf(cmdBuf,"post -t%s -H < mresult2.txt",ui->lastSaveName);
                system(cmdBuf);
                AcLogPrintf(0,4,"4:Ran command: %s\n",cmdBuf);
                
            }
        }
    }

#if 0
    if (strncmp(str,"Motion=",7)) return;

    if (!ui->lastOut || strcmp(ui->lastOut,str)) {
        if (ui->lastOut) {
            free(ui->lastOut);
        }
        ui->lastOut = strdup(str);
    }

    str +=7;

    while(*str) {
        unsigned int v;
        char *e;
        while(isspace(*str)) str++;
        if (!isdigit(*str)) break;

        v = strtol(str,&e,16);
        if (e==str) break;
        str = e;

        if (0 && i==0) {
            AcLogPrintf(0,4,"4:  %d motions:\n",v);
        } else if (v & 0xf) {
            AcLogPrintf(0,4,
                "4:  [%2d]=0x%02x  circle cnt=%d.%d  rot=%s  start=%d %s\n",
                i,
                v,
#if 0
                (v>>1)&0x7,
                (v&1)?5:0,
#else
                (v & 0xf),
                0,
#endif
                (v&0x40)?"ccw":"cw",
                (v>>4)&3,
                dirStr[(v>>4)&3]);
        } else {
            AcLogPrintf(0,4,
                "4:  [%2d]=0x%02x  end dir=%d %s\n",
                i,
                v,
                (v>>4)&3,
                dirStr[(v>>4)&3]);
        }

        i++;
    }
#endif
    (void)ui;
}

//===========================================================================
// UiCmdMotion() - show motions
//===========================================================================
static int UiCmdMotion(WandUi *ui, char *args)
{
    int i;

    if (UiCmdDumpVars(ui,"0x800 0x900 z")) {
        AcLogPrintf(0,4,"0:Unable to read memory\n");
        return -1;
    }

    for (i=0; i<sizeof(gbuf); i++) {
        unsigned int v = gbuf[i];
        if (v == 0xff) break;

        if (i==0) {
            AcLogPrintf(0,4,"4:  %d motions:\n",v);
        } else if (v & 0xf) {
            AcLogPrintf(0,4,
                "4:  [%2d]=0x%02x  circle cnt=%d.%d  rot=%s  start=%d\n",
                i,
                v,
                (v>>1)&0x7,
                (v&1)?5:0,
                (v&0x40)?"ccw":"cw",
                (v>>4)&3);
        } else {
            AcLogPrintf(0,4,
                "4:  [%2d]=0x%02x  end dir=%d\n",
                i,
                v,
                (v>>4)&3);
        }

    }

    return 0;
}

//===========================================================================
// UiCmdSaveName() - set saveName
//===========================================================================
static int UiCmdSaveName(WandUi *ui, char *args)
{
    while(isspace(*args)) args++;
    if (*args == 0) {
        if (ui->saveName) {
            free(ui->saveName);
        }
        ui->saveName = strdup("");
    } else if (isupper(*args) || islower(*args) || *args == '_') {
        if (ui->saveName) {
            free(ui->saveName);
        }
        ui->saveName = strdup(args);
        args = ui->saveName;
        while(*args) {
            if (*args == 0) break;
            if (*args == 0 || isspace(*args)) {
                *args = 0;
                break;
            }
            if (isdigit(*args) ||
                isupper(*args) ||
                islower(*args) ||
                *args == '_') {
                args++;
            } else {
                *(args++) = '_';
            }
        }

    } else {
        AcLogPrintf(0,4,"0:Bad savename: '%s'\n",args);
        return -1;
    }
    
    AcLogPrintf(0,4,"3:Setting saveName='%s'\n",ui->saveName);
    return 0;
}

//===========================================================================
// UiCmdShowbuf() - show wand output buffer
//===========================================================================
static int UiCmdShowbuf(WandUi *ui, char *args)
{
    double ct_tap = 0;
    double ct = 0;
    int was_running = 0;
    int i;

    was_running = WandIsRunning(ui->wand);

    if (WandHalt(ui->wand)) {
        AcLogPrintf(0,4,"3:Unable to halt wand\n");
        return -1;
    }

    if (UiCmdDumpVars(ui,"0x400 0x800 z")) {
        AcLogPrintf(0,4,"0:Unable to read memory\n");
        return -1;
    }

    if (gbuf[0] == 0 &&
        gbuf[1] == 0 &&
        gbuf[2] == 0 &&
        gbuf[3] == 0 &&
        gbuf[4] == 0 &&
        gbuf[5] == 0 &&
        gbuf[6] == 0 &&
        gbuf[7] == 0) {
        AcLogPrintf(0,4,"3:\nNo data found - not saving file\n\n");
        return -1;
    }

    for (i=0; i<sizeof(gbuf); i+=4) {
        int t,imp,a, u;
        double tsec;
        if (gbuf[i+0] == 0 &&
            gbuf[i+1] == 0 &&
            gbuf[i+2] == 0 &&
            gbuf[i+3] == 0) break;

        printf("  %02x %02x %02x %02x  ",
            gbuf[i+0],
            gbuf[i+1],
            gbuf[i+2],
            gbuf[i+3]);

        t    = (gbuf[i+0] <<8 ) + gbuf[i+1];
        u    = gbuf[i+2];
        a    = gbuf[i+3];
        tsec = (double)t * 640.0/(1000*1000);

        if (a < 0x20) {
            int imp, magn, magx;
            char *dirStr[] = {
                "Down ",
                "Right",
                "Up   ",
                "Left ",
            };
            i+=4;
            imp   = (gbuf[i+0] <<8 ) + gbuf[i+1];
            magn = gbuf[i+2];
            magx = gbuf[i+3];

            printf("Angle t=%04x = %12.6f %12.6f sec  "
                "Angle=0x%02x %d=%s imp=%04x "
                "mag=%02x %02x %02x",
                t,
                tsec,
                ct,
                a,
                ((a+4)&0x1f)>>3,
                dirStr[((a+4)&0x1f)>>3],
                imp,
                magn,
                imp/t,
                magx);

            if (magx >= 0x20) {
                printf("  taptime=%04x %12.6f",
                    (int)((ct - ct_tap)*1000*1000/640),
                    ct - ct_tap);
                ct_tap = ct;
            }
            printf("\n");

            ct += tsec;

        } else if (a == 0xff) {
            printf("Pause t=%04x = %12.6f %12.6f sec\n",
                t,
                tsec,
                ct);
            ct += tsec;
        } else if (a == 0x81) {
            printf("nz    t=%04x = %12.6f sec\n",
                imp,
                (double)imp * 640.0/(1000*1000));
        } else if (a == 0x82) {
            printf("n     t=%04x = %12.6f sec\n",
                imp,
                (double)imp * 640.0/(1000*1000));
        } else if (a == 0x83) {
            printf("n   imp=%04x             \n",
                imp);
        } else {
            printf("0x%02x  ?=%04x = %12.6f sec\n",
                a,
                imp,
                (double)imp * 640.0/(1000*1000));
        }
    }

#if 0
    if (ui->lastOut) {
        UiDoWandOut(ui,0);
    }
#endif

    if (was_running) {
        AcLogPrintf(0,4,"3:Running wand (debug mode)\n");
        return WandCmdf(ui->wand, "D");
    }
    return 0;
}

//===========================================================================
// UiCmdGraph() - get acc data from wand & draph it
//===========================================================================
static int UiCmdGraph(WandUi *ui, char *args)
{
    int was_running = 0;
    char *saveName;
    char name[1000];
    FILE *fp;
    int i;
    int cnt;

    while(isspace(*args)) args++;
    if (*args != 0) {
        if (UiCmdSaveName(ui,args)) {
            return -1;
        }
    }

    was_running = WandIsRunning(ui->wand);

    if (WandHalt(ui->wand)) {
        AcLogPrintf(0,4,"3:Unable to halt wand\n");
        return -1;
    }

    saveName = ui->saveName ? ui->saveName : "";

    for (i=0; i<100000; i++) {
        if (i>=99999) {
            AcLogPrintf(0,4,"0:Could not find free filename\n");
            return -1;
        }
        snprintf(name,sizeof(name)-1,"wpic%s%05d.txt",saveName,i);
        fp = fopen(name,"r");
        if (!fp) break;
        fclose(fp);
    }

    AcLogPrintf(0,4,"3:\nGetting data for graph '%s'\n\n",name);

    cnt=0;
    while (1) {
        if (!UiCmdDumpVars(ui,"0x700 0xe00 z")) break;
        AcLogPrintf(0,4,"0:Unable to read memory\n");
        if (++cnt > 4) {
            AcLogPrintf(0,4,"0:FAILED - GET ACORN\n");
            return -1;
        }
        AcLogPrintf(0,4,"0:Retrying\n");
    }

    if (gbuf[0] == 0 &&
        gbuf[1] == 0 &&
        gbuf[2] == 0 &&
        gbuf[3] == 0 &&
        gbuf[4] == 0 &&
        gbuf[5] == 0 &&
        gbuf[6] == 0 &&
        gbuf[7] == 0) {
        AcLogPrintf(0,4,"3:\nNo data found - not saving file\n\n");
        return -1;
    }

    fp = fopen(name,"w");


#define USE_WG_FMTB 1
#if USE_WG_FMTB
    //
    // new format:
    //
    // pause record
    //    1  relatve time (h) (sample count)
    //    0  relatve time (l)
    //    2  unused (0xff)
    //    3  angle (0xff indicates pause)
    //
    // non-pause record
    //    0  relatve time (h) (sample count)
    //    1  relatve time (l)
    //    2  unused (0xff)
    //    3  angle
    //    4  impulse (h)
    //    5  impulse (l)
    //    6  min mag
    //    7  max mag
    //
    // 
    //
    if (fp) {
        fprintf(fp,"# formatB\n");
    }
    {
        int tm = 0;
        int gotangle = 0;
        for (i=0; i<sizeof(gbuf); ) {
            int dt,a;
            if (gbuf[i+0] == 0 &&
                gbuf[i+1] == 0 &&
                gbuf[i+2] == 0 &&
                gbuf[i+3] == 0 &&
                gbuf[i+4] == 0 &&
                gbuf[i+5] == 0 &&
                gbuf[i+6] == 0 &&
                gbuf[i+7] == 0) break;

            dt = (gbuf[i+0] <<8 ) + gbuf[i+1];
            a    = gbuf[i+3];

            if (a==0xfe) {
                printf("Found end @ offset 0x%04x\n", i);
                break;
            }

            if (a==0xff) {
                printf("  ACC: 0x%04x: %08x pause %04x\n",
                    i,
                    tm,dt);
                if (gotangle) {
                    if (fp) {
                        fprintf(fp,"%8.1f %8.1f %8.1f %8.1f %8.1f %8.1f\n", 
                            (double)tm,
                            (double)a,
                            (double)0.0,
                            (double)0.0,
                            (double)0.0,
                            (double)0.0);
                    }
                    tm += dt;
                }
                i += 4;
            } else {
                int imp = (gbuf[i+4] <<8 ) + gbuf[i+5];
                int minmag = gbuf[i+6];
                int maxmag = gbuf[i+2];
                int flags  = gbuf[i+7];

                printf("  ACC: 0x%04x: %08x move  %04x a=%02x "
                    "imp=%04x min=%02x max=%02x flg=0x%02x\n",
                    i,
                    tm,dt,
                    a,imp,minmag,maxmag,flags);

                if (fp) {
                    fprintf(fp,
                        "%8.1f %8.1f %8.1f %8.1f %8.1f %8.1f\n", 
                        (double)tm,
                        (double)a,
                        (double)imp,
                        (double)minmag,
                        (double)maxmag,
                        (double)flags);
                }
                gotangle = 1;

                tm += dt;
                i+= 8;
            }

        }
    }
#else
    //
    // old format:
    // byte
    // 0   abs time h  (TMR1 time - 244usec period)
    // 1   abs time l
    // 2   angle
    // 3   mag
    //
    for (i=0; i<sizeof(gbuf); i+=4) {
        float time,x,y;
        if (gbuf[i+0] == 0 &&
            gbuf[i+1] == 0 &&
            gbuf[i+2] == 0 &&
            gbuf[i+3] == 0) break;

        time = (gbuf[i+0] <<8 ) + gbuf[i+1];
        x    = gbuf[i+2];
        y    = gbuf[i+3];

        if (fp) {
            fprintf(fp,"%8.1f %8.1f %8.1f\n", time,x,y);
        }
    }
#endif

    if (fp) {
        static char cmd[1000];
        fclose(fp);
#if 0
#if USE_WG_FMTB
        snprintf(cmd,sizeof(cmd)-1,"wgg -b %s",name);
#else
        snprintf(cmd,sizeof(cmd)-1,"wgg -a %s",name);
#endif
#endif
        AcLogPrintf(0,4,"3:Wrote %d points to %s\n",i/4,name);
        system(cmd);
    }

    sleep(1);

    AcLogPrintf(0,4,"3:\n\nSaved graph as '%s'\n\n\n",name);

    if (ui->lastSaveName) {
        free(ui->lastSaveName);
    }
    ui->lastSaveName = strdup(name);

#if 0
    if (ui->lastOut) {
        UiDoWandOut(ui,0);
    }
#endif

    if (was_running) {
        AcLogPrintf(0,4,"3:Running wand (debug mode)\n");
        return WandCmdf(ui->wand, "D");
    }
    return 0;
}

//===========================================================================
// UiGetFileBuffer() - return what the file wants (merged with what wand has)
//===========================================================================
//
// Where file is known - set to file byte
// else where wand is known - set to wand byte
// else set to 0xff
//
// Returned buffer is a malloced buffer ui->addrMax bytes long
//
static void *UiGetFileBuffer(WandUi *ui, AcHexAddress *endp)
{
    AcHexAddress start;
    AcHexAddress end;
    AcHexAddress size;
    AcHexByte *buf;

    if (!ui->hexFile) {
        AcLogPrintf(0,4,"0:No HEX file loaded\n");
        return 0;
    }

    ui->addrMaxWand = AC_HEX_INVALID_ADDRESS;
    UiReadMinProgAddr(ui);

    start = ui->addrMin;
    end = (ui->addrMax + 64 + 63) & ~63UL;

    if (UiReadProg(ui,start,end-start,0)) return 0;
    
    size = end;
    if (size < ui->addrMaxFile) {
        size = ui->addrMaxFile;
    }
    buf = malloc(size);
    if (buf) {

        //
        // start all 0xff
        //
        memset(buf, 0xff, size);

        //
        // overwrite from wand
        //
        memcpy(buf+start, ui->wandProg+start, end-start);

        //
        // overwrite from file
        //
        AcHexGetBytes(ui->hexFile,0,buf,ui->addrMaxFile);
    }

    if (endp) *endp = end - 64;
    return buf;
}

//===========================================================================
// UiCompareProg() - compare wand program to buffer
//===========================================================================
static int UiCompareProg(
                    WandUi *ui,
                    AcHexAddress addr,
                    AcHexByte *data,
                    AcHexOffset size)
{
    unsigned long cnt = 0;
    AcHexAddress a;
    AcHexAddress end = addr + size;
    AcHexAddress errStart = 0;
    int isErr = 0;

    if (UiReadProg(ui,addr, end-addr, 0)) {
        AcLogPrintf(0,4,"0:\nError reading wand in UiCompareProg()\n");
        return -1;
    }

    if (!data) return -1;

    for (a=addr; ; a++) {
        if (a>=end || data[a-addr] == ui->wandProg[a]) {
            if (isErr) {
                if (errStart == a-1) {
                    AcLogPrintf(0,4,"3:Different at   0x%04lx\n",
                        errStart);
                } else {
                    AcLogPrintf(0,4,"3:Different from 0x%04lx - 0x%04lx\n",
                        errStart,
                        a-1);
                }
                isErr = 0;
            }
            if (a>=end) break;
        } else {
            if (!isErr) {
                isErr = 1;
                errStart = a;
            }
            AcLogPrintf(0,4,"6:Different at %08lx: file=%02x  wand=%02x\n",
                (unsigned long)a,
                data[a-addr],
                ui->wandProg[a]);
            cnt++;
        }
    }   

    AcLogPrintf(0,4,"0:Checked 0x%08lx - 0x%08lx\n",
        (unsigned long)ui->addrMin,
        (unsigned long)end);
    AcLogPrintf(0,4,"0:%lu bytes differ.\n",cnt);
    
    return cnt ? -1 : 0;
}

//===========================================================================
// UiCmdCmpProg() - compare wand program to hex file
//===========================================================================
static int UiCmdCmpProg(WandUi *ui, char *args)
{
    AcHexAddress end;
    AcHexByte *buf = UiGetFileBuffer(ui, &end);
    int rv = 0;

    if (end > ui->addrMax) {
        end = ui->addrMax;
    }
    if (ui->addrMin < end) {
        rv = UiCompareProg(ui, ui->addrMin, buf+ui->addrMin, end-ui->addrMin);
    }
    free(buf);
    return rv;
}

//===========================================================================
// UiDoProgram64() - program a 64 byte section of the wand
//===========================================================================
static int UiDoProgram64(
                    WandUi *ui, 
                    AcHexAddress addr, 
                    AcHexByte *buf,
                    int force)
{
    char cmdBuf[64*3+10];
    char *s = cmdBuf;
    int i;
    int change = 0;
    char *reply;

    if (force) {
        change = 1;
    } else {
        for (i=0; i<64; i++) {
            if (ui->wandProg[addr+i] != buf[i]) change = 1;
            s += sprintf(s,"%02x ",buf[i]);
        }
    }

    if (!change) {
        AcLogPrintf(0,4,"4:  %08lx - %08lx  unchanged - skipping\n",
            (unsigned long)addr,
            (unsigned long)addr+63);
        return 0;
    }
    s[-1] = 0;

    UiSetProgDirty(ui, addr-64, addr+64);

    //
    // program command
    //
    reply = WandCmdfStr(ui->wand, "P%04x:%s",addr,cmdBuf);

    if (reply) {
        int err = 0;
        AcLogPrintf(0,4,"3:Wand Reply = '%s'\n",reply);
        if (strlen(reply) < 13) {
            err = -1;
        } else {
            reply[8]  = 'X';
            reply[9]  = 'X';
            reply[10] = 'X';
            reply[11] = 'X';
            if (strcmp(reply,"flash 0xXXXX: -> Written")) {
                err = -1;
            }
        }
        free(reply);
        return err;
    } else {
        AcLogPrintf(0,4,"0:Wand programming error\n");
        return -1;
    }
}

//===========================================================================
// UiDoProgram() - program the wand
//===========================================================================
//
// confirm - ask for confirmation if this is set
// addr    - destination address in flash (must be multiple of 64)
// size    - number of bytes (must be multiple of 64)
// data    - bytes to write
//
// returns
//     0 success
//    -1 failure
//
static int UiDoProgram(
                    WandUi *ui,
                    int confirm,
                    AcHexAddress addr,
                    AcHexByte *data,
                    AcHexOffset size,
                    int force)
{
    AcHexAddress end = addr + size;
    AcHexAddress a;
    const char *reply;
    int errCnt = 2;

    if (!data) {
        return -1;
    }

    if (addr & 63) {
        AcLogPrintf(0,4,"0:\nBad addr in UiDoProgram: %04lx\n",(long)addr);
        return -1;
    }
    if (end & 63) {
        AcLogPrintf(0,4,"0:\nBad size in UiDoProgram: %04lx\n",(long)size);
        return -1;
    }

    //
    // read current vals if not known
    //
    if (!force) {
        if (UiReadProg(ui,addr, end-addr, 0)) {
            AcLogPrintf(0,4,"0:\nError reading wand in UiDoProgram()\n");
            return -1;
        }
    }

    //
    // Confirm program request
    //
    if (confirm) {
        AcLogPrintf(0,4,"0:\nSure you want to program the wand? [y/n] ");
        fflush(stdout);
        reply = GetReply();
        if (toupper(reply[0]) != 'Y') {
            AcLogPrintf(0,4,"0:Aborting program command\n");
            return -1;
        }
    }

    //
    // program each section
    //
    for (a=addr; a<end; a+=64) {
        if (UiDoProgram64(ui,a,data + (a - addr),force)) {
            errCnt++;
            if (errCnt > 9) {
                AcLogPrintf(0,4,"7:Error while programming addr=%08lx!\n",
                    (unsigned long)a);
                WandRecoverWait(ui->wand);
                return -1;
            }
            WandRecoverWait(ui->wand);
            a -= 64;
        } else if (errCnt) {
            errCnt--;
        }
    }

    AcLogPrintf(0,4,"3:Programmed 0x08lx - 0x%08lx\n",
            (long)addr,
            (long)end);
    AcLogPrintf(0,4,"3:Verifying...\n");
    
    return UiCompareProg(ui, addr, data, size);
}


//===========================================================================
// UiCmdProgram() - program the wand
//===========================================================================
static int UiCmdProgram(WandUi *ui, char *args)
{
    AcHexAddress end;
    AcHexByte *buf = UiGetFileBuffer(ui, &end);
    AcHexAddress size = end - ui->addrMin;
    int rv;

    rv = UiDoProgram(ui, 1, ui->addrMin, buf + ui->addrMin, size, 0);

    free(buf);
    return rv;
}

//===========================================================================
// UiDoLoadKernel() - load new kernel
//===========================================================================
static int UiDoLoadKernel(WandUi *ui, const char *basename, int confirm, 
                int force)
{
    char fn2[1000];
    const char *filename;
    AcHexFile *hx = 0;
    AcHexByte *buf = 0;
    AcHexOffset loaderSize;
    AcHexOffset bufSize;;
    const char *reply;
    FILE *fp;
    
    if (!basename) basename="";
    while(isspace(*basename)) basename++;
    if (!basename[0]) {
        basename = "kload.hex";
    }
    filename = basename;

    if (WandHalt(ui->wand)) {
        AcLogPrintf(0,4,"3:Unable to halt wand\n");
        return -1;
    }

    fp = fopen(filename,"r");
    if (!fp) {
        snprintf(fn2,sizeof(fn2),"../sw/%s",basename);
        filename = fn2;
        fp = fopen(filename,"r");
    }
    if (!fp) {
        snprintf(fn2,sizeof(fn2),"../sw/Archive/%s",basename);
        filename = fn2;
        fp = fopen(filename,"r");
    }
    if (!fp) {
        snprintf(fn2,sizeof(fn2),"../sw/Archive/kload.v%s.hex",basename);
        filename = fn2;
        fp = fopen(filename,"r");
    }
    if (!fp) {
        AcLogPrintf(0,4,"0:Could not find file %s\n",basename);
        return -1;
    }
    fclose(fp);

    hx = AcHexLoadFile(filename,0);
    if (!hx) {
        AcLogPrintf(0,4,"0:Error loading hex file '%s'\n",filename);
        return -1;
    }

    AcLogPrintf(0,4,"3:Loaded hex file '%s'\n",filename);

    do {
        
        kernelHeader hdr;
        AcHexOffset len;
        AcHexAddress oldAppMain;
        AcHexAddress newAppMain;
        AcHexAddress oldEnd;
        AcHexAddress newEnd;
        AcHexAddress dst;
        AcHexByte *bp;

        UiReadMinProgAddr(ui);
        if (!UiHeaderValid(ui, &ui->headerWand)) {
            AcLogPrintf(0,4,"3:Current Kernel header is invalid\n");
            break;
        }
        if (ui->headerWand.version < KERNEL_MIN_VERSION) {
            AcLogPrintf(0,4,"3:Bad Current Kernel version (%d)\n",
                ui->headerWand.version);
            AcLogPrintf(0,4,"3:(Need version %d or higher\n",
                KERNEL_MIN_VERSION);
            break;
        }
        oldAppMain = (ui->headerWand.kk_app_main_high << 8) |
                      ui->headerWand.kk_app_main_low;
        oldEnd = oldAppMain + 64;
        if (oldAppMain & 0x3f) {
            AcLogPrintf(0,4,"3:Bad old kk_app_main alignment: 0x%04x\n",
                oldAppMain);
            break;
        }
        if (oldEnd > KERNEL_LOADER_BEGIN) {
            AcLogPrintf(0,4,"3:old kk_app_main is too big: 0x%04x\n",
                oldAppMain);
            break;
        }


        len = AcHexGetBytes(hx, KERNEL_HEADER_ADDR, &hdr, sizeof(hdr));
        if (len != sizeof(hdr)) {
            AcLogPrintf(0,4,"3:Could not get new kernel header\n");
            break;
        }
        if (!UiHeaderValid(ui, &hdr)) {
            AcLogPrintf(0,4,"3:new Kernel header is invalid\n");
            break;
        }
        if (hdr.version < KERNEL_MIN_VERSION) {
            AcLogPrintf(0,4,"3:Bad new Kernel version (%d)\n",hdr.version);
            AcLogPrintf(0,4,"3:(Need version %d or higher\n",
                KERNEL_MIN_VERSION);
            break;
        }
        newAppMain = (hdr.kk_app_main_high << 8) |
                      hdr.kk_app_main_low;
        newEnd = newAppMain + 64;
        if (newAppMain & 0x3f) {
            AcLogPrintf(0,4,"3:Bad new kk_app_main alignment: 0x%04x\n",
                oldAppMain);
            break;
        }
        if (newEnd > KERNEL_LOADER_BEGIN) {
            AcLogPrintf(0,4,"3:new kk_app_main is too big: 0x%04x\n",
                oldAppMain);
            break;
        }
        
        loaderSize = AcHexGetRegion(hx, KERNEL_LOADER_BEGIN, 0, 0);
        if (loaderSize < 10) {
            AcLogPrintf(0,4,"3:Bad loader size (too small): 0x%lx\n",
                (long)loaderSize);
            break;
        }
        if (loaderSize > KERNEL_LOADER_NEW_KERNEL-KERNEL_LOADER_BEGIN) {
            AcLogPrintf(0,4,"3:Bad loader size (too big): 0x%lx\n",
                (long)loaderSize);
            break;
        }

        //
        // get buffer
        //
        bufSize = loaderSize;
        if (bufSize < newEnd) {
            bufSize = newEnd;
        }
        if (bufSize < 64) {
            bufSize = 64;
        }
        buf = malloc(bufSize + 64);
        if (!buf) break;

        //
        // Program the new kernel into flash 
        //
        if (UiReadProg(ui, KERNEL_LOADER_NEW_KERNEL, newEnd, 0)) {
            break;
        }
        memcpy(buf, ui->wandProg+KERNEL_LOADER_NEW_KERNEL, newEnd + 64);
        AcHexGetBytes(hx, 0, buf, newAppMain);

        //
        // fill in app with return statements
        //
        bp = buf + newAppMain + KERNEL_APP_MAIN_OFFSET;
        bp[0] = 0x12;   // return opcode
        bp[1] = 0x00;
        bp = buf + newAppMain + KERNEL_APP_ISR_LOW_OFFSET;
        bp[0] = 0x12;   // return opcode
        bp[1] = 0x00;
        bp = buf + newAppMain + KERNEL_APP_ISR_HIGH_OFFSET;
        bp[0] = 0x11;   // retfie 1 opcode
        bp[1] = 0x00;

        if (UiDoProgram(ui, confirm, KERNEL_LOADER_NEW_KERNEL, buf, newEnd, force)) {
            break;
        }

        //
        // Program the loader into flash
        //
        if (UiReadProg(ui, KERNEL_LOADER_BEGIN, loaderSize, 0)) {
            break;
        }
        memcpy(buf, ui->wandProg+KERNEL_LOADER_BEGIN, loaderSize + 64);
        AcHexGetBytes(hx, KERNEL_LOADER_BEGIN, buf, loaderSize);
        if (UiDoProgram(
                    ui, 
                    confirm, 
                    KERNEL_LOADER_BEGIN, 
                    buf, 
                    (loaderSize + 63) & ~63UL,
                    force)) {
            break;
        }

        //
        // Make kk_app_main     jump to KERNEL_LOADER_BEGIN
        // Make kk_app_isr_low  jump to KERNEL_LOADER_BEGIN
        // Make kk_app_isr_high jump to KERNEL_LOADER_BEGIN
        //
        if (UiReadProg(ui, oldAppMain, 64, 0)) {
            break;
        }
        memcpy(buf, ui->wandProg + oldAppMain, 64);
        dst = KERNEL_LOADER_BEGIN;
        bp = buf + KERNEL_APP_MAIN_OFFSET;
        bp[0] = ((dst >>  1) & 0xff);
        bp[1] = 0xEF;                       // goto opcode
        bp[2] = ((dst >>  9) & 0xff);
        bp[3] = ((dst >> 17) & 0xff) | 0xf0;
        bp = buf + KERNEL_APP_ISR_LOW_OFFSET;
        bp[0] = ((dst >>  1) & 0xff);
        bp[1] = 0xEF;                       // goto opcode
        bp[2] = ((dst >>  9) & 0xff);
        bp[3] = ((dst >> 17) & 0xff) | 0xf0;
        bp = buf + KERNEL_APP_ISR_HIGH_OFFSET;
        bp[0] = ((dst >>  1) & 0xff);
        bp[1] = 0xEF;                       // goto opcode
        bp[2] = ((dst >>  9) & 0xff);
        bp[3] = ((dst >> 17) & 0xff) | 0xf0;

        //
        // Confirm running
        //
        if (confirm) {
            AcLogPrintf(0,4,"0:\nSure you want to load kernel %d? [y/n] ",
                hdr.version);
            fflush(stdout);
            reply = GetReply();
            if (toupper(reply[0]) != 'Y') {
                AcLogPrintf(0,4,"0:Aborting kload command\n");
                break;
            }
        }

        if (UiDoProgram(ui, 0, oldAppMain, buf, 64, force)) {
            break;
        }

        //
        // invalidate stuff
        //
        UiSetProgDirty(ui, 0, newEnd+64);

        //
        // Run the loader
        //
        AcLogPrintf(0,4,"0:\nRunning the kernel loader...\n");
        if (WandCmdf(ui->wand, "D")) {
            break;
        }

    
        if (buf) {
            free(buf);
        }
        AcHexDelete(hx);

        return 0;

    } while(0);

    AcLogPrintf(0,4,"0:\n\n");
    AcLogPrintf(0,4,"0:ERROR: Failed to load new kernel\n");
    AcLogPrintf(0,4,"0:\n\n");

    if (buf) {
        free(buf);
    }
    AcHexDelete(hx);

    return -1;
}


//===========================================================================
// UiCmdLoadKernel() - load new kernel
//===========================================================================
static int UiCmdLoadKernel(WandUi *ui, char *args)
{
    return UiDoLoadKernel(ui,args ? args : "",1,0);
}

//===========================================================================
// UiCmdShowFile() - show hex file
//===========================================================================
static int UiCmdShowFile(WandUi *ui, char *args)
{
    if (!ui->hexFile) {
        AcLogPrintf(0,4,"0:No HEX file loaded\n");
        return -1;
    }
    AcHexPrint(ui->hexFile,1);
    return 0;
}

//===========================================================================
// AcSystemCmdf() - run a system command and get output
//===========================================================================
// rvp     - if not NULL return value oes here
// lenp    - length of stdio (chars) returned here if not NULL
// fmt,... - command to run
//
// returns stdout of command (free it when done)
//
char *AcSystemCmdf(int *rvp, long *lenp, const char *fmt,...)
{
    static const char *tmpfile = TMP_FILENAME;
    va_list va;
    char buf[1000];
    int rv = -1;
    int cnt;
    char *out = 0;
    long len;

    va_start(va, fmt);
    cnt = vsnprintf(buf,sizeof(buf)-2,fmt,va);
    va_end(va);

    if (cnt) {
        cnt = snprintf(buf+cnt,sizeof(buf)-cnt-1," > %s",tmpfile);
    }
    if (cnt && cnt < sizeof(buf)-3) {
        rv = system(buf);
    }
    if (rv == 0) {
        FILE *fp = fopen(tmpfile,"r");
        if (fp) {
            fseek(fp,0,SEEK_END);
            len = ftell(fp);
            fseek(fp,0,SEEK_SET);
            out = malloc(len+1);
            if (len) {
                len = fread(out,1,len,fp);
            }
            fclose(fp);
        }
    }

    if (rvp) *rvp = rv;
    if (lenp) *lenp = len;
    return out;
}

//===========================================================================
// UiGetSym() - lookup a symbol to get addr and val (both optional)
//===========================================================================
// ui     - 
// sym    - name of symbol (use addr=0x00 if NULL)
// offset - add this to symbol to get address
// addrp  - if not null, return address here
// valp   - if not null, return value here
//
// return 0 on success, -1 on fail
static int UiGetSym(
                    WandUi *ui,
                    char *sym,
                    long offset,
                    unsigned long *addrp,
                    unsigned long *valp)
{
    FILE *fp;
    char buf[1000];
    int rv = 0;
    int err = 0;
    int gotfirst = 0;

    if (sym) {
        snprintf(buf,sizeof(buf),"./ws '\\<%s\\>' > %s",sym,TMP_FILENAME);
        buf[sizeof(buf)-1]=0;
        rv = system(buf);
    } else {
        fp = fopen(TMP_FILENAME, "w");
        if (!fp) return -1;
        fprintf(fp,"v_null = 0, type = constant\n");
        fclose(fp);
    }

    if (rv) {
        return -1;
    }

    fp = fopen(TMP_FILENAME, "r");
    if (!fp) {
        return -1;
    }

    while(1) {
        unsigned long addr = 0;
        unsigned long val = 0;
        int gotaddr = 0;
        int gotval = 0;
        char *s = buf;
        char *t = "";
        int c;
        while(1) {
            c = getc(fp);
            if (c == EOF || c == '\n' || isspace(c) || c=='=') break;
            if (s-buf > sizeof(buf)-2) break;
            *(s++) = c;
        }
        *(s++) = 0;
        while(c==' ' || c=='=') {
            c = getc(fp);
        }
        while(isxdigit(c)) {
            gotaddr = 1;
            addr *= 16;
            addr += isdigit(c) ?
                        (c - '0') :
                        (toupper(c) - 'A' + 10);
            c = getc(fp);
        }
        if (c==',') {
            char *tp = s;
            while(c!='t' && c!='\n' && c!=EOF) c = getc(fp);
            if (c!='t') break;
            c = getc(fp);
            if (c!='y') break;
            c = getc(fp);
            if (c!='p') break;
            c = getc(fp);
            if (c!='e') break;
            c = getc(fp);
            while(c==' ' || c=='=') c = getc(fp);
            while(isalpha(c)) {
                if (s-buf > sizeof(buf)-2) break;
                *(s++) = c;
                c = getc(fp);
            }
            *(s++) = 0;
            t = tp;
        }
        while(c!=EOF && c!='\n') {
            c = getc(fp);
        }
        if (gotaddr && (t[0] || !gotfirst)) {
            addr += offset;
            if (!strcmp(t,"constant") || buf[0]=='v') {
                int try=5;
                while(try--) {
                    char *wand_output;
                    if (!valp) break;
                    wand_output = WandCmdfStr(ui->wand,"V%04x",addr);
                    val = 0;
                    if (wand_output) {
                        char *e;
                        AcLogPrintf(0,4,"9:wand_output='%s'\n",wand_output);
                        s = wand_output;
                        while(*s && *s!=':') s++;
                        if (*s) s++;
                        val = strtoul(s,&e,16);
                        if (e && e != s) {
                            gotval = 1;
                            try = 0;
                        } else {
                            val = 0;
                        }
                        free(wand_output);
                    }
                }
            } else if (valp) {
                err = -1;
            }
            if (gotval || buf[0] || addr) {
                if (gotfirst &&
                    ((addrp && *addrp != addr) ||
                     (valp  && *valp  != val))) {
                    err = -1;   // more than 1 matching symbol
                }
                if (addrp) {
                    *addrp = addr;
                }
                if (valp) {
                    *valp = val;
                }
                gotfirst = 1;
            }
        }

        if (c==EOF) break;
    }

    fclose(fp);
    if (!gotfirst) err = -1;
    return err;
}

//===========================================================================
// UiCmdShowSyms() - load a hex file
//===========================================================================
static int UiCmdShowSyms(WandUi *ui, char *args)
{
    FILE *fp;
    char buf[1000];
    int rv;
    const char *pattern = args;

    snprintf(buf,sizeof(buf),"./ws '%s' > %s",pattern,TMP_FILENAME);
    buf[sizeof(buf)-1]=0;
    rv = system(buf);

    if (rv) {
        AcLogPrintf(0,4,"3:No symbols match pattern '%s'\n",pattern);
        return -1;
    }

    fp = fopen(TMP_FILENAME, "r");
    if (!fp) {
        AcLogPrintf(0,4,"0:Could not read tmp file '%s'\n",TMP_FILENAME);
        return -1;
    }

    while(1) {
        unsigned long addr = 0;
        int val = 0;
        int gotval = 0;
        char *s = buf;
        char *t = "";
        int c;
        while(1) {
            c = getc(fp);
            if (c == EOF || c == '\n' || isspace(c) || c=='=') break;
            if (s-buf > sizeof(buf)-2) break;
            *(s++) = c;
        }
        *(s++) = 0;
        while(c==' ' || c=='=') {
            c = getc(fp);
        }
        while(isxdigit(c)) {
            addr *= 16;
            addr += isdigit(c) ?
                        (c - '0') :
                        (toupper(c) - 'A' + 10);
            c = getc(fp);
        }
        if (c==',') {
            char *tp = s;
            while(c!='t' && c!='\n' && c!=EOF) c = getc(fp);
            if (c!='t') break;
            c = getc(fp);
            if (c!='y') break;
            c = getc(fp);
            if (c!='p') break;
            c = getc(fp);
            if (c!='e') break;
            c = getc(fp);
            while(c==' ' || c=='=') c = getc(fp);
            while(isalpha(c)) {
                if (s-buf > sizeof(buf)-2) break;
                *(s++) = c;
                c = getc(fp);
            }
            *(s++) = 0;
            t = tp;
        }
        while(c!=EOF && c!='\n') {
            c = getc(fp);
        }
        if (!strcmp(t,"constant") || buf[0]=='v') {
            int try=5;
            while(try--) {
                char *wand_output = WandCmdfStr(ui->wand,"V%04x",addr);
                if (wand_output) {
                    char *e;
                    AcLogPrintf(0,4,"9:wand_output='%s'\n",wand_output);
                    s = wand_output;
                    while(*s && *s!=':') s++;
                    if (*s) s++;
                    val = strtoul(s,&e,16);
                    if (e && e != s) {
                        gotval = 1;
                        try=0;
                    }
                    free(wand_output);
                }
            }
        }
        if (gotval || buf[0] || addr) {
            if (gotval) {
                AcLogPrintf(0,4,"3:  0x%02x = %3d = ",val,val);
            } else {
                AcLogPrintf(0,4,"3:               ");
            }
            AcLogPrintf(0,4,"3:*0x%04lx = *%s\n",addr,buf);
        }

        if (c==EOF) break;
    }

    fclose(fp);
    return 0;
}

//===========================================================================
// UiCmdParseError()
//===========================================================================
static int UiCmdParseError(WandUi *ui, char *args)
{
    unsigned long rcon;
    unsigned long stkptr;
    unsigned long error;
    unsigned long val;
    char *prompt = WandGetPrompt(ui->wand);
    int err = 0;

    WandQuiet(ui->wand);

    AcLogPrintf(0,4,"0:\n");

    if (UiGetSym(ui,"vk_boot0_rcon",0,0,&rcon)) {
        rcon = 0xff;
    }
    if (UiGetSym(ui,"vk_boot0_stkptr",0,0,&stkptr)) {
        stkptr = 0;
    }
    if (!strncmp(prompt+3,"Error>",6)) {
        char *e;
        err = strtoul(prompt,&e,16);
        if (!e || e == prompt) {
            err = 0;
        }
    }
    if (UiGetSym(ui,"vk_boot0_error",0,0,&error)) {
        error = err;
    } else {
        if (!err) err = error;
    }

    if (       (rcon & 0x02) == 0) {
        AcLogPrintf(0,4,"0:  POWER UP RESET\n");
    } else if ((rcon & 0x10) == 0) {
        if (err) {
            AcLogPrintf(0,4,"0:  RESET INSTR FOR Error 0x%02x\n",err);
        } else {
            AcLogPrintf(0,4,"0:  RESET INSTRUCTION\n");
        }
    } else if ((rcon & 0x01) == 0) {
        AcLogPrintf(0,4,"0:  BROWN-OUT RESET\n");
    } else if ((stkptr & 0x80)) {
        AcLogPrintf(0,4,"0:  STACK OVERFLOW RESET\n");
    } else if ((stkptr & 0x40)) {
        AcLogPrintf(0,4,"0:  STACK UNDEFLOW RESET\n");
    } else if ((rcon & 0x08) == 0) {
        AcLogPrintf(0,4,"0:  WATCHDOG TIMEOUT RESET\n");
    } else if ((rcon & 0x04) == 0) {
        AcLogPrintf(0,4,"0:  INTERRUPTED FROM SLEEP\n");
    } else {
        AcLogPrintf(0,4,"0:  RESET BUTTON WAS PRESSED\n");
    }
    AcLogPrintf(0,4,"0:\n");

    AcLogPrintf(0,4,"0:  rcon   = 0x%02x\n",rcon);
    AcLogPrintf(0,4,"0:  stkptr = 0x%02x\n",stkptr);
    AcLogPrintf(0,4,"0:  error  = 0x%02x\n",err);
    AcLogPrintf(0,4,"0:\n");


    AcLogPrintf(0,4,"0:Wand Error 0x%02x\n",err);
    if (error && (error != err)) {
        AcLogPrintf(0,4,"0:vk_boot0_error = 0x%02x\n",error);
    }

    system("grep -h '^ERROR_[^;]*[Ee][Qq][Uu]' "
            "../sw/kernel.inc ../sw/wand.asm | "
            "detab");

    UiCmdShowSyms(ui,"boot._error");
    AcLogPrintf(0,4,
        "0:RCON:   IPEN   SBOREN <rsvd> /RI | /TO  /PD  /POR /BOR\n");
    UiCmdShowSyms(ui,"\\<RCON\\>");
    UiCmdShowSyms(ui,"boot._rcon");
    AcLogPrintf(0,4,
        "0:STKPTR: STKFUL STKUNF <rsvd> SP4 | SP3  SP2  SP1  SP0\n");
    UiCmdShowSyms(ui,"STKPTR");
    UiCmdShowSyms(ui,"boot._stkptr");

    AcLogPrintf(0,4,
        "0:bbits: 0 0 0 0 | 0 <cold> <first> <serial>\n");
    UiCmdShowSyms(ui,"vk_boot0_bbits");
    
    AcLogPrintf(0,4,
        "0:boot0_runstate: 00=run 04=debug 08=halt 0c=error 10=breakpt\n");
    UiCmdShowSyms(ui,"vk_boot0_runstate");
    AcLogPrintf(0,4,
        "0:runstate_bits: 00=run 01=debug 03=halt 0b=error 07=breakpt\n");
    UiCmdShowSyms(ui,"vki_runstate_bits");

    if (UiGetSym(ui,"vk_where",0x500,0,&val)) {
        AcLogPrintf(0,4,"3:Could not lookup symbol 'vk_where'\n");
    } else {
        AcLogPrintf(0,4,
            "0:vk_where  = %3d = 0x%02x   (last DB_WHERE  macro call)\n",
            (int)val,
            (int)val);
    }

    if (UiGetSym(ui,"vk_where2",0x500,0,&val)) {
        AcLogPrintf(0,4,"3:Could not lookup symbol 'vk_where2'\n");
    } else {
        AcLogPrintf(0,4,
            "0:vk_where2 = %3d = 0x%02x   (last DB_WHERE2 macro call)\n",
            (int)val,
            (int)val);
    }

    if (UiGetSym(ui,"vk_iwhere",0x500,0,&val)) {
        AcLogPrintf(0,4,"3:Could not lookup symbol 'vk_iwhere'\n");
    } else {
        AcLogPrintf(0,4,
            "0:vk_iwhere = %3d = 0x%02x   (last DB_IWHERE macro call)\n",
            (int)val,
            (int)val);
    }

    AcLogPrintf(0,4,"0:Stack:\n");
    UiCmdDumpVars(ui,"0x400 0x41f");

    AcLogPrintf(0,4,"0:\nvalues of 0x000-0x0ff at boot:\n");
    UiCmdDumpVars(ui,"0x500 0x5ff");
    AcLogPrintf(0,4,"0:\n");

    WandUnQuiet(ui->wand);

    free(prompt);
    return 0;
}

//===========================================================================
// UiCmdLoadHex() - load a hex file
//===========================================================================
static int UiCmdLoadHex(WandUi *ui, char *args)
{
    const char *filename = args;
    AcHexFile *hx;

    if (!filename || !filename[0]) {
        filename = ui->hexFile ? AcHexGetFilename(ui->hexFile) : 0;
        if (!filename || !filename[0]) {
            filename = DEFAULT_HEX_FILE;
        }
    }

    hx = AcHexLoadFile(filename,0);
    if (!hx) {
        AcLogPrintf(0,4,"0:Error in AcHexLoadFile(\"%s\")\n",filename);
        return -1;
    }

    AcLogPrintf(0,4,"3:Loaded hex file \"%s\"\n",filename);
    if (ui->hexFile) {
        AcHexDelete(ui->hexFile);
    }
    ui->hexFile = hx;

    //
    // get top address we care about
    //
    {
        AcHexAddress addr = 0;
        AcHexAddress addrMax = 0;

        while (1) {
            AcHexOffset len;

            len = AcHexGetRegion(hx,addr,&addr,0);

            if (!len) break;

            if (addr > addrMax && addr+len <= PROG_MAX) {
                addrMax = addr+len;
            }

            addr += len;
        }

        AcHexGetBytes(hx,0x10,&ui->headerFile,sizeof(ui->headerFile));
        ui->addrMaxUser = AC_HEX_INVALID_ADDRESS;
        ui->addrMinUser = AC_HEX_INVALID_ADDRESS;

        ui->addrMaxFile = (addrMax + 63) & ~63UL;
        UiCheckAddrMaxMin(ui);
    }

    return 0;
}

//===========================================================================
// UiCmdMake()
//===========================================================================
static int UiCmdMake(WandUi *ui, char *args)
{
    int rv = system("cd .. && make");

    if (rv) {
        AcLogPrintf(0,4,"0:\n\nBUILD FAILED - not loading hex file\n");
        return -1;
    }

    AcLogPrintf(0,4,"3:\n*****Build success! - Reloading file*****\n\n");
    return UiCmdLoadHex(ui,"");
}

//===========================================================================
// UiInitLog()
//===========================================================================
static void UiInitLog(WandUi *ui, int append)
{
    if (ui->log) {
        AcLogDestroy(ui->log);
    }
    ui->log = AcLogCreate("wwlog.txt",append,1);
    ui->logToWand   = AcLogCreateSource(ui->log,"+  ToWand");
    ui->logFromWand = AcLogCreateSource(ui->log,"+FromWand");
    ui->logCmd      = AcLogCreateSource(ui->log,"      Cmd");
    ui->logMsg      = AcLogCreateSource(ui->log," Messages");

    //
    // Program assumes static log source assignment as follows
    //
    if (ui->logToWand != 1) {
        printf("Error: ui->logToWand = %d != 1\n",ui->logToWand);
        exit(1);
    }
    if (ui->logFromWand != 2) {
        printf("Error: ui->logToWand = %d != 2\n",ui->logFromWand);
        exit(1);
    }
    if (ui->logCmd != 3) {
        printf("Error: ui->logToWand = %d != 3\n",ui->logMsg);
        exit(1);
    }
    if (ui->logMsg != 4) {
        printf("Error: ui->logToWand = %d != 4\n",ui->logMsg);
        exit(1);
    }

}



//===========================================================================
// UiCmdHelp() 
//===========================================================================
static int UiCmdHelp(WandUi *ui, char *args)
{
    const UiCmd *c;
    int maxlen = 0;

    AcLogPrintf(0,4,"0:"
    "LEDS  (X=on B=blink -=off ?=dont-care) (from base to tip)\n"
    "---------------------------------------------------------\n"
    "(base)   (tip)\n"
    "    ------  = Debug/Run\n"
    "    X--B-?  = Halt\n"
    "    XXXB-?  = Error\n"
    "    XX-B-?  = Breakpoint\n"
    "    X-B---  = Sync\n"
    "\n"
    "Commands\n"
    "--------\n");

    for(c=ui->cmdList; c->name; c++) {
        int l = c->namelen;
        if (c->helpArgs) {
            l += strlen(c->helpArgs) + 2;
        }
        if (c->help1 && l > maxlen) {
            maxlen = l;
        }
    }

    for(c=ui->cmdList; c->name; c++) {
        if (c->help1) {
            int l = 0;
            l += AcLogPrintf(0,4,"0:%s ",c->name);
            if (c->helpArgs) {
                l += AcLogPrintf(0,4,"0:%s ",c->helpArgs);
            }
            if (l < maxlen) {
                AcLogPrintf(0,4,"0:%*s",maxlen-l,"");
            }
            AcLogPrintf(0,4,"0:- %s\n", c->help1);
        }
    }

    AcLogPrintf(0,4,"0:"
    "\n"
    "Wand Commands (preceded by ':')\n"
    "-------------------------------\n");

    WandCmdf(ui->wand, "I");

    return 0;
}


//===========================================================================
// UiCmdQuit()
//===========================================================================
static int UiCmdQuit(WandUi *ui, char *args)
{
    ui->quit = 1;
    return 0;
}

//===========================================================================
// UiCmdHalt()
//===========================================================================
static int UiCmdHalt(WandUi *ui, char *args)
{
    return WandHalt(ui->wand);
}

//===========================================================================
// UiCmdDebug()
//===========================================================================
static int UiCmdDebug(WandUi *ui, char *args)
{
    return WandCmdf(ui->wand, "D");
}

//===========================================================================
// UiCmdInitLog()
//===========================================================================
static int UiCmdInitLog(WandUi *ui, char *args)
{
    UiInitLog(ui,0);
    return 0;
}

//===========================================================================
// UiCmdRun()
//===========================================================================
static int UiCmdRun(WandUi *ui, char *args)
{
    const char *reply;
    AcLogPrintf(0,4,"0:Are you sure you want to RUN? (Y/N) ");
    fflush(stdout);
    reply = GetReply();
    if (toupper(reply[0]) == 'Y') {
        return WandCmdf(ui->wand, "R");
    }

    return -1;
}

//===========================================================================
// UiCmdTime()
//===========================================================================
static int UiCmdTime(WandUi *ui, char *args)
{
    int rv;
    int doSet = 0;
    unsigned long hourOld, hourOld2, hourAddr, hourNew, hourNow;
    unsigned long minOld,  minOld2,  minAddr,  minNew,  minNow;
    unsigned long secOld,                      secNew,  secNow;
    unsigned long qsecOld,           qsecAddr, qsecNew;
    signed long hourDelta, minDelta, secDelta;
    struct timeval timeNow;
    struct tm tmNow;

    while(1) {
        if (UiGetSym(ui,"vk_hour",0,&hourAddr,&hourOld)) {
            AcLogPrintf(0,4,"0:Error finding smbol vk_hour\n");
            return -1;
        }
        if (UiGetSym(ui,"vk_minute",0,&minAddr,&minOld)) {
            AcLogPrintf(0,4,"0:Error finding smbol vk_min\n");
            return -1;
        }
        if (UiGetSym(ui,"vk_qsec",0,&qsecAddr,&qsecOld)) {
            AcLogPrintf(0,4,"0:Error finding smbol vk_qsec\n");
            return -1;
        }
        if (gettimeofday(&timeNow,0)) {
            AcLogPrintf(0,4,
                "0:Error in gettimeofday\n");
            return -1;
        }
        if (UiGetSym(ui,"vk_minute",0,&minAddr,&minOld2)) {
            AcLogPrintf(0,4,"0:Error finding smbol vk_min\n");
            return -1;
        }
        if (UiGetSym(ui,"vk_hour",0,&hourAddr,&hourOld2)) {
            AcLogPrintf(0,4,"0:Error finding smbol vk_hour\n");
            return -1;
        }

        if (hourOld2 == hourOld && minOld2 == minOld) break;
    }

    secOld = qsecOld/4;

    //
    //  Get current local time
    //  
    localtime_r(&timeNow.tv_sec, &tmNow);
    hourNow = tmNow.tm_hour;
    minNow  = tmNow.tm_min;
    secNow  = tmNow.tm_sec;
    switch(tmNow.tm_wday) {
        case 0: 
            hourNow += 24 * 2;
            break;
        case 6:
            hourNow += 24;
            break;
        default: break;
    }

    if (args && args[0] ) {
        while(isspace(*args)) args++;
        if (!strncmp(args,"now",3)) {
            args += 3;
            while(isspace(*args)) args++;
            if (*args) {
                AcLogPrintf(0,4,
                    "0:Error - trailing characters\n");
                return -1;
            }

            hourNew = hourNow;
            minNew  = minNow;
            secNew  = secNow;
            qsecNew = secNew * 4;

            doSet = 1;
            args = 0;
        }
    }
    if (args && args[0]) {
        char *e;
        while(isspace(*args)) args++;
        hourNew = strtol(args,&e,0);
        if (!e || e == args || *e != ':') {
            AcLogPrintf(0,4,
                "0:Error in format(h).  Use hh:mm:ss (decimal or 0xhex)\n");
            return -1;
        }
        args = e+1;

        while(isspace(*args)) args++;
        minNew = strtol(args,&e,0);
        if (!e || e == args || *e != ':') {
            AcLogPrintf(0,4,
                "0:Error in format(m).  Use hh:mm:ss (decimal or 0xhex)\n");
            return -1;
        }
        args = e+1;

        while(isspace(*args)) args++;
        secNew = strtol(args,&e,0);
        if (!e || e == args) {
            AcLogPrintf(0,4,
                "0:Error in format(s).  Use hh:mm:ss (decimal or 0xhex)\n");
            return -1;
        }
        args = e;

        while(isspace(*args)) args++;
        if (*args) {
            AcLogPrintf(0,4,
                "0:Error in format.  Trailing characters\n");
            return -1;
        }
        if (hourNew<0 || hourNew>255) {
            AcLogPrintf(0,4,
                "0:Error in format.  Bad hour (0-255)\n");
            return -1;
        }
        if (minNew<0 || minNew>255) {
            AcLogPrintf(0,4,
                "0:Error in format.  Bad minute (0-60)\n");
            return -1;
        }
        if (secNew<0 || secNew>255) {
            AcLogPrintf(0,4,
                "0:Error in format.  Bad second (0-59)\n");
            return -1;
        }
        qsecNew = secNew * 4;
        doSet = 1;

    }

    if (doSet) {
        //
        // setting takes about 3 sec - add 3 sec
        //
        qsecNew += 3*4;
        while (qsecNew >= 60*4) {
            qsecNew -= 60*4;
            minNew += 1;
        }
        while (minNew >= 60) {
            minNew -= 60;
            hourNew += 1;
        }

        rv = WandRecoverWait(ui->wand);
        if (rv) {
            AcLogPrintf(0,4, "0:Unable to recover wand prompt (s0)\n");
            return -1;
        }
        rv = WandCmdf(ui->wand, "V%04x:%02x",
                (int)qsecAddr,1);
        if (rv) {
            AcLogPrintf(0,4, "0:Error writing qsec(0)\n");
            return -1;
        }

        rv = WandRecoverWait(ui->wand);
        if (rv) {
            AcLogPrintf(0,4, "0:Unable to recover wand prompt (m0)\n");
            return -1;
        }
        rv = WandCmdf(ui->wand, "V%04x:%02x",
                (int)minAddr,1);
        if (rv) {
            AcLogPrintf(0,4, "0:Error writing minute(0)\n");
            return -1;
        }

        rv = WandRecoverWait(ui->wand);
        if (rv) {
            AcLogPrintf(0,4, "0:Unable to recover wand prompt (h)\n");
            return -1;
        }
        rv = WandCmdf(ui->wand, "V%04x:%02x",
                (int)hourAddr,
                (int)hourNew);
        if (rv) {
            AcLogPrintf(0,4, "0:Error writing hour\n");
            return -1;
        }

        rv = WandRecoverWait(ui->wand);
        if (rv) {
            AcLogPrintf(0,4, "0:Unable to recover wand prompt (m)\n");
            return -1;
        }
        rv = WandCmdf(ui->wand, "V%04x:%02x",
                (int)minAddr,
                (int)minNew);
        if (rv) {
            AcLogPrintf(0,4, "0:Error writing minute\n");
            return -1;
        }

        rv = WandRecoverWait(ui->wand);
        if (rv) {
            AcLogPrintf(0,4, "0:Unable to recover wand prompt (s)\n");
            return -1;
        }
        rv = WandCmdf(ui->wand, "V%04x:%02x",
                (int)qsecAddr,
                (int)qsecNew);
        if (rv) {
            AcLogPrintf(0,4, "0:Error writing qsec\n");
            return -1;
        }

        rv = WandRecoverWait(ui->wand);
        if (rv) {
            AcLogPrintf(0,4, "0:Unable to recover wand prompt (e)\n");
            return -1;
        }
    }

    AcLogPrintf(0,4,
        "0:%s wand time is %d:%02d:%02d  (0x%02x:0x%02x:0x%02x)\n",
        doSet?"Old    ":"Current",
        (int)hourOld,(int)minOld,(int)qsecOld/4,
        (int)hourOld,(int)minOld,(int)qsecOld/4);

    secDelta  = secOld  - secNow;
    minDelta  = minOld  - minNow;
    hourDelta = hourOld - hourNow;
    while(secDelta < 0) {
        secDelta += 60;
        minDelta -= 1;
    }
    while(minDelta < 0) {
        minDelta += 60;
        hourDelta -= 1;
    }
    if (hourDelta == 0 && minDelta==0 && secDelta==0) {
        AcLogPrintf(0,4,
            "0:Wand clock %s ON TIME +/- 1 sec\n",
            doSet?"was":"is");
    } else if (hourDelta >= 0) {
        AcLogPrintf(0,4,
            "0:Wand clock %s AHEAD %2d:%02d:%02d\n",
            doSet?"was":"is",
            hourDelta,
            minDelta,
            secDelta);
    } else {
        hourDelta = -hourDelta;
        minDelta  = -minDelta;
        secDelta  = -secDelta;
        while(secDelta < 0) {
            secDelta += 60;
            minDelta -= 1;
        }
        while(minDelta < 0) {
            minDelta += 60;
            hourDelta -= 1;
        }
        AcLogPrintf(0,4,
            "0:Wand clock %s BEHIND %2d:%02d:%02d\n",
            doSet?"was":"is",
            hourDelta,
            minDelta,
            secDelta);
    }
    
    if (doSet) {
        AcLogPrintf(0,4,
            "0:\nNew     wand time is %d:%02d:%02d  (0x%02x:0x%02x:0x%02x)\n",
            (int)hourNew,(int)minNew,(int)qsecNew/4,
            (int)hourNew,(int)minNew,(int)qsecNew/4);

        UiCmdTime(ui, 0);
    }

    return 0;
}

//===========================================================================
// UiCmdSetTop()
//===========================================================================
static int UiCmdSetTop(WandUi *ui, char *args)
{
    int err = -1;
    char *e=0;
    unsigned long n;
    n = strtol(args,&e,16);
    if (e && e!=args && n > 100) {
        ui->addrMaxUser = (n + 63) & ~63UL;
        UiCheckAddrMaxMin(ui);
        err = 0;
    }
    ui->wandProgAllClean = 0;
    AcLogPrintf(0,4,"0:Ignoring addresses above %08lx\n",
        (unsigned long)ui->addrMax);
    return err;
}

//===========================================================================
// UiCmdSetBottom()
//===========================================================================
static int UiCmdSetBottom(WandUi *ui, char *args)
{
    int err = -1;
    char *e=0;
    unsigned long n;
    n = strtol(args,&e,16);
    if (e && e!=args) {
        ui->addrMinUser = n & ~63UL;
        UiCheckAddrMaxMin(ui);
        err = 0;
    }
    ui->wandProgAllClean = 0;
    AcLogPrintf(0,4,"0:Ignoring addresses below %08lx\n",
        (unsigned long)ui->addrMin);
    return err;
}

//===========================================================================
// UiCmdDebugLevel()
//===========================================================================
static int UiCmdDebugLevel(WandUi *ui, char *args)
{
    static const int DEFAULT_DEBUG_LEVEL = 4;
    int val = ui->debugLevel;

    if (args) {
        while(isspace(args[0])) args++;
        if (args[0]) {
            char *e;
            val = strtol(args,&e,0);
            if (!e || e==args) {
                val = DEFAULT_DEBUG_LEVEL;
            }
        }
    }
    if (val < 0) {
        val = DEFAULT_DEBUG_LEVEL;
    } else {
        AcLogPrintf(0,4,"0:Wand debugLevel is now %d\n",val);
        AcLogPrintf(0,4,"0:"
            "  0 = minimal\n"
            "  3 = sparse\n"
            "  4 = normal (default)\n"
            "  5 = show wand errors\n"
            "  8 = verbose\n"
            "  9 = debug messges - all\n"
            );
    }
    ui->debugLevel = val;
    WandDebugLevel(ui->wand,val);
    AcLogSetDebugLevel(ui->log,val);
    return 0;
}

//===========================================================================
// UiCmdNewInit()
//===========================================================================
static int UiCmdNewInit(WandUi *ui, char *cmdline)
{
    AcHexAddress end;
    AcHexByte *buf;
    AcHexAddress size;
    int rv;
    const char *reply;


    AcLogPrintf(0,4,
        "0:\nSure you want to program entire wand from scratch? [y/n] ");
    fflush(stdout);
    reply = GetReply();
    if (toupper(reply[0]) != 'Y') {
        AcLogPrintf(0,4,"0:Aborting NEW command\n");
        return -1;
    }

    //
    // invalidate local copy of wand mem
    //
    UiSetProgDirty(ui, 0,0);

    //
    // load latest kernel
    //
    rv = UiDoLoadKernel(ui,0,0,0);

    if (rv) {
        AcLogPrintf(0,4,"0:Error loading kernel\n");
        return rv;
    }

    //
    // wait for kernel load to complete
    //
    while(1) {
        rv = WandRecoverWait(ui->wand);
        //
        // -2 means timeout.  Loop until non-timeout.
        //
        if (rv != -2) break;
    }

    if (rv!=0) {
        AcLogPrintf(0,4,"0:Aborting NEW command while loading kernel!!\n");
        return -1;
    }

    //
    // set time
    //
    UiCmdTime(ui,"now");


    //
    // load latest program
    //
    buf = UiGetFileBuffer(ui, &end);
    size = end - ui->addrMin;

    rv = UiDoProgram(ui, 0, ui->addrMin, buf + ui->addrMin, size, 0);

    free(buf);

    //
    // check time and set it again
    //
    UiCmdTime(ui,"now");

    return rv;
}

//===========================================================================
// UiWandCommand()
//===========================================================================
static int UiWandCommand(WandUi *ui, char *cmdline)
{
    int rv;
    if (cmdline[0] == 'R') {
        return UiCmdRun(ui,cmdline);
    }
    rv = WandCmdf(ui->wand, "%s",cmdline);
    if (!rv) {
        // allow wand command to take up to a minute
        rv = WandPromptWaitUsec(ui->wand,60 * 1000000);
    }
    return rv;
}


//===========================================================================
// UiInitGetCmdList()
//===========================================================================
static void UiInitGetCmdList(WandUi *ui)
{
    static UiCmd g_cmd_list[] = {
    { UiCmdQuit,        "quit",     UI_CFLG_NOARGS,0,
                                    "quit",  },
    { UiCmdHalt,        "halt",     UI_CFLG_NOARGS,0,
                                    "halt the wand", },
    { UiCmdDebug,       "debug",    UI_CFLG_NOARGS,0,
                                    "debug (run) the program", },
    { UiCmdRun,         "RUN",      UI_CFLG_NOARGS,0,
                                    "run the program in RUN mode", },
    { UiCmdNewInit,     "NEW",      UI_CFLG_NOARGS,0,
                                    "initialize/verify a new wand", },
    { UiCmdHelp,        "?",        0,0,
                                    "print this help (/ works too)", },
    { UiCmdHelp,        "/",        0,},
    { UiCmdShowSyms,    "symbol",   UI_CFLG_NEEDARGS,0,
                                    "lookup a symbol (show its value too)", },
    { UiCmdLoadHex,     "load",     0,"<file>",
                                    "load a hex file (default = reload same)",},
    { UiCmdSetTop,      "top",      UI_CFLG_NEEDARGS,"<addr>",
                                    "set max address we care about in "
                                    "program memory", },
    { UiCmdSetBottom,   "bottom",   UI_CFLG_NEEDARGS,"<addr>",
                                    "set min address we care about in "
                                    "program memory", },
    { UiCmdInvalProg,   "inval",    UI_CFLG_NOARGS|UI_CFLG_EXACTMATCH,0,
                                    "invalidate local copy of wand prog data",},
    { UiCmdShowFile,    "file",     UI_CFLG_NOARGS,0,
                                    "show contents of file", },
    { UiCmdDebugLevel,  "DEBUG",    0,0,
                                    "set toggle wand debugging level", },
    { UiCmdProgram,     "prog",     UI_CFLG_NOARGS|UI_CFLG_EXACTMATCH,0,
                                    "program the wand", },
    { UiCmdDumpProg,    "pdump",    0,0,
                                    "dump *cached* program (from read cmd)", },
    { UiCmdDumpVars,    "vdump",    UI_CFLG_NEEDARGS,"<s> <e>",
                                    "dump variable memory from <s> to <e>", },
    { UiCmdDumpEEProm,  "eeprom",   0,0,
                                    "dump the wand eeprom", },
    { UiCmdCmpProg,     "compare",  UI_CFLG_NOARGS,0,
                                    "compare file vs *cached* wand program", },
    { UiCmdSaveName,    "Savename", 0,0,
                                    "set the base name for saving files",},
    { UiCmdGraph,       "graph",    0,0,
                                    "graph data from 0x400 - 0x800",},
    { UiCmdShowbuf,     "Bufshow",  0,0,
                                    "show buffer 0x400 - 0x800",},
    { UiCmdMotion,      "motion",   UI_CFLG_NOARGS,0,
                                    "show motions at 0x800-0x900", },
    { UiCmdInitLog,     "killlog",  UI_CFLG_NOARGS,0,
                                    "empty the log file", },
    { UiCmdLoadKernel,  "kload",    0,"<kload.hex>",
                                    "Load a new kernel", },
    { UiCmdParseError,  "error",    UI_CFLG_NOARGS,0,
                                    "show error info", },
    { UiCmdMake,        "make",     UI_CFLG_NOARGS,0,
                                    "run make in the sw dir and reload prog", },
    { UiCmdTime,        "time",     0,"<time>",
                                    "print or set time hh:mm:ss (decimal)", },
    { 0,0,0,0, },
    };

    UiCmd *c = g_cmd_list;
    while (c->name) {
        c->namelen = strlen(c->name);
        if (!c->help2 || !c->help2[0]) {
            c->help2 = c->help1;
        }
        c++;
    }

    ui->cmdList = g_cmd_list;
}

//===========================================================================
// UiFindCommand() - parse cmdline into info
//===========================================================================
static const UiCmd *UiFindCommand(
                        WandUi *ui, 
                        char *cmdline,
                        char **args)
{
    static UiCmd wand_command = {
        UiWandCommand, ":", 0, "Send a command to the wand",
    };
    const UiCmd *best = 0;
    const UiCmd *c;
    char *s;
    int len;

    while(isspace(*cmdline)) cmdline++;
    for (s=cmdline; *s && !isspace(*s); s++);
    len = s-cmdline;

    if (*cmdline == ':') {
        if (args) *args = cmdline+1;
        return &wand_command;
    }

    for(c=ui->cmdList; c->name; c++) {
        if (len > c->namelen) continue;
        if (!strncmp(cmdline,c->name,len)) {
            if (best) {
                AcLogPrintf(0,4,"0:Error: Ambiguous command\n");
                best = 0;
                break;
            }
            best = c;
        }
    }

    if (best) {
        while(isspace(*s)) s++;
        if (args) *args = s;
        if ((best->flags & UI_CFLG_NOARGS) && *s) {
            AcLogPrintf(0,4,"0:Error: No args allowed with command '%s'\n",
                best->name);
            return 0;
        }
        if ((best->flags & UI_CFLG_NEEDARGS) && !*s) {
            AcLogPrintf(0,4,"0:Error: Argument(s) required with command '%s'\n",
                best->name);
            return 0;
        }
        if ((best->flags & UI_CFLG_EXACTMATCH) && len != best->namelen) {
            return 0;
        }
    }

    return best;
}

//===========================================================================
// UiHandleStdin()
//===========================================================================
static int UiHandleStdin(int fd, void *data)
{
    WandUi *ui = data;
    char buf[1000];
    int cnt = read(fd, buf, sizeof(buf)-1);
    char *s = buf;
    int rv = 0;

    //
    // strip off CR/LF
    //
    buf[cnt] = 0;
    while(1) {
        if (!*s || *s == '\n' || *s == '\r') {
            *s = 0;
            break;
        }
        s++;
    }
    for (s=buf; isspace(*s); s++);

    AcLogPrintf(ui->log,ui->logCmd,"%s\n",buf);
    AcLogFlush(ui->log);

    AcLogPrompt(0,0,-2);

    if (s[0] != 0) {
        char *args = 0;
        const UiCmd *cmd = UiFindCommand(ui, buf, &args);

        if (cmd) {
            rv = cmd->func(ui,args);
        } else {
            AcLogPrintf(0,4,"0:Error: command line syntax\n");
            rv = -1;
        }
    }

    WandPrompt(ui->wand);
    
    return ui->quit;
}

//===========================================================================
// UiCreate()
//===========================================================================
static WandUi *UiCreate(void)
{
    unsigned int size;
    WandUi *ui = calloc(1,sizeof(WandUi));
    if (!ui) return 0;

    ui->saveName = strdup("");

    UiInitLog(ui,1);

    UiInitGetCmdList(ui);

    ui->addrMaxUser = AC_HEX_INVALID_ADDRESS;
    ui->addrMaxFile = AC_HEX_INVALID_ADDRESS;
    ui->addrMaxWand = AC_HEX_INVALID_ADDRESS;
    ui->addrMinUser = AC_HEX_INVALID_ADDRESS;
    UiCheckAddrMaxMin(ui);

    ui->wandProg = malloc(PROG_MAX);
    if (!ui->wandProg) return 0;
    memset(ui->wandProg,0xff,PROG_MAX);

    size = sizeof(unsigned int) * ((PROG_MAX>>(6+5))+1);
    ui->wandProgDirty = malloc(size);
    if (!ui->wandProgDirty) return 0;
    memset(ui->wandProgDirty,0xff,size);

    //
    // initialize wand
    //
    ui->wand = WandInit();
    if (!ui->wand) {
        AcLogPrintf(0,4,"0:Error in WandInit()\n");
        return 0;
    }
    WandOutCallback(ui->wand, UiDoWandOut, ui);

    UiCmdDebugLevel(ui, "-1");  // set default debugLevel

    //
    // create select group
    //
    ui->selectGroup = AcFdGroupCreate();
    if (!ui->selectGroup) {
        AcLogPrintf(0,4,"0:Error in AcFdGroupCreate\n");
        free(ui->wand);
        return 0;
    }

    //
    // add wand serial port handler
    //
    AcFdGroupAdd(
                ui->selectGroup,
                WandGetFd(ui->wand),
                WandHandleSerial,
                ui->wand);

    //
    // add stdin handler (fd=0)
    //
    AcFdGroupAdd(ui->selectGroup, 0, UiHandleStdin, ui);

    //
    // return pointers
    //
    return ui;
}

//===========================================================================
// UiDestroy()
//===========================================================================
static void UiDestroy(WandUi *ui)
{
    AcFdGroupDestroy(ui->selectGroup);

    WandShutdown(ui->wand);

    if (ui->saveName) {
        free(ui->saveName);
    }

    AcLogDestroy(ui->log);
}


//===========================================================================
// main()
//===========================================================================
int main(int argc, char *argv[])
{
    WandUi *ui = UiCreate();

    if (!ui) {
        printf("Error in UiCreate()\n");
        return 1;
    }

    UiCmdLoadHex(ui,0);

    WandPromptWaitUsec(ui->wand,1000000);


    //
    // This loops waiting for wand or stdin input until "q" command
    //   input from wand calls WandHandleSerial()
    //   input from stdin calls UiHandleStdin()
    //
    WandPrompt(ui->wand);
    AcFdGroupLoop(ui->selectGroup);

    UiDestroy(ui);
    printf("\n");

    return 0;
}

This file Copyright (C) 2006 by Nathan (Acorn) Pooley
Go to TOP Wand page
Go to Acorn's personal webpage
Go to Hogwarts website: www.gotohogwarts.com
Snout: www.snout.org/game
Gadgets of Justice Unlimited
Snout GC (Wiki)
Snout Wiki
File created by do_doc at Wed May 30 03:30:03 PDT 2007