微處理器 - NXP MC68HC908JB8 - bl08.c



參考資訊:
https://www.sparetimelabs.com/bl08/bl08.zip
https://www.nxp.com/docs/en/data-sheet/MC68HC908JB8.pdf

bl08.c

// TODO POSIX COMPATIBLE IOCTL
// Note page size for JB8 has been temporarily changed to 32
/*
File: bl08.c

Copyright (C) 2004,2008  Kustaa Nyholm

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public
License as published by the Free Software Foundation; either
version 2.0 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

*/
// This code compiles cleanly with i686-apple-darwin8-gcc-4.0.1 with command:
// gcc -Wall -std=c99 -pedantic -o bl08 bl08.c
// and uses only POSIX standard headers so this should be pretty
// easy to port anywhere: Mac OS X,  Linux , MinGW, Cygwin
//
// Having said that I'm pretty sure there is implicit assumptions that
// int is 32 bits, char is signed 8 bits
// I also expect that extending this code to handle S-records in the +2G range
// will uncover more implicit assumptions.
//
// cheers Kusti
// additions ... override value for non supported CPUs
// reset control with DTR
#include <asm/ioctls.h>
#include <ctype.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <time.h>
#include <unistd.h>
//#include <asm/termios.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>

// Terrible hack here because POSIX says 'ioctl()' is in <stropts.h> but
// for example Mac OS X Tiger does not have this header, OTOH I think
// that <sys/ictl.h> is not POSIX either so how do you write
// actual POSIX compliant code that compiles cleanly on POSIX...
extern int ioctl(int filedes, int command, ...);
// End of hack

#define PIDFILENAME "bl08PIDfile.temp"

int com = 0;
char* COM = "/dev/ttyUSBS0";
char version[] = "1.0.0.0";
unsigned char image[0x10000];  // HC908 memory image

// give some initial meaningfull values (based on MC68HC908GZ16)
int FLASH = 0xE000;    // Flash start address
int PUTBYTE = 0xFEAF;  // Receive byte routine address
int GETBYTE = 0x1C00;  // Receive byte routine address
int RDVRRNG = 0x1C03;  // Read/verify flash routine address
int ERARRNG = 0x1C06;  // Erase flash routine address
int PRGRNGE = 0x1C09;  // Flash programming routine address
int FLBPR = 0xFF7E;    // Flash block proctection register address
int CPUSPEED =
    8;  // 2 x Fbus freq, e.g.  ext osc 16 MHz -> Fbus == 4 Mh => CPUSPEED==2
int MONDATA = 0x48;   // Flashing routines parameter block address
int MONRTN = 0xFE20;  // Monitor mode return jump address
int EADDR = 0xFF7E;   // For FLBPR in Flash the mass erase must use FLBPR as the
                      // erase address
// these are calculated
int DATABUF;   // Flashing routines data buffer address (==MONDATA+4)
int PAGESIZE;  // Databuffer size
int WORKRAM;   // Work storage needed for calling flashing routines
int WORKTOP;   // Topmost work storage address
int CTRLBYT;   // Address of flashing routine control variable (==MONDATA+0)
int CPUSPD;    // Address of flashing routine cpu speed variable (==MONDATA+1)
int LADDR;  // Address of flashing routine last address variable (==MONDATA+2)

// HC908GZ16 Memory usage (note for some HC908 variants the ROM routines use
// memory starting from 0x80): 0x40 - 0x47 reserved for future ROM routine
// expansion needs 0x48 - 0x4B ROM routine parameters 0x4C - 0x6C ROM routine
// data buffer (64 bytes as used in this code) 0xAC - 0xFF Working storage for
// calling the ROM routines (about 17 bytes used)

// Cavets
// DATABUF size
// security erase at FLBPR, power cycle

int tickP1 = 15;
int tickP2 = 1023;
int verbose = 1;
int size = sizeof(image);
int useStdin = 0;
int dumpStart = 0;
int dumpSize = 0;
char* dumpFormat = "hex";
int eraseFlash = 0;
int verify = 0;
int baudRate = B9600;
char* executeCode = NULL;
int pageErase = 0;
int uploadOnly = 0;
int terminalMode = 0;
int connected = 0;
int useFastProg = 0;
int resetPulse = 0;
int killPrevious = 0;
int loadOnly = 0;

void comErr(char* fmt, ...) {
    char buf[500];
    va_list va;
    va_start(va, fmt);
    vsnprintf(buf, sizeof(buf), fmt, va);
    fprintf(stderr, "%s", buf);
    perror(COM);
    va_end(va);
    abort();
}

void flsprintf(FILE* f, char* fmt, ...) {
    char buf[500];
    va_list va;
    va_start(va, fmt);
    vsnprintf(buf, sizeof(buf), fmt, va);
    fprintf(f, "%s", buf);
    fflush(f);
    va_end(va);
}

void initSerialPort() {
    com = open(COM, O_RDWR | O_NOCTTY | O_NDELAY);
    if (com < 0) comErr("Failed to open seria port");

    fcntl(com, F_SETFL, 0);

    struct termios opts;

    tcgetattr(com, &opts);

    opts.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);

    opts.c_cflag |= (CLOCAL | CREAD);
    opts.c_cflag &= ~PARENB;
    opts.c_cflag |= CSTOPB;  // two stop bits
    opts.c_cflag &= ~CSIZE;
    opts.c_cflag |= CS8;

    opts.c_oflag &= ~OPOST;

    opts.c_iflag &= ~INPCK;
    opts.c_iflag &= ~(IXON | IXOFF | IXANY);
    opts.c_cc[VMIN] = 0;
    opts.c_cc[VTIME] = 10;  // 0.1 sec

    cfsetispeed(&opts, baudRate);
    cfsetospeed(&opts, baudRate);

    if (tcsetattr(com, TCSANOW, &opts) != 0) {
        perror(COM);
        abort();
    }

    tcflush(com, TCIOFLUSH);  // just in case some crap is the buffers

    char buf = -2;
    while (read(com, &buf, 1) > 0) {
        if (verbose)
            printf("Unexpected data from serial port: %02X\n", buf & 0xFF);
    }
}

void putByte(int byte) {
    char buf = byte;
    if (verbose > 3) flsprintf(stdout, "TX: 0x%02X\n", byte);
    int n = write(com, &buf, 1);
    if (n != 1)
        comErr("Serial port failed to send a byte, write returned %d\n", n);
}

int getByte() {
    char buf;
    int n = read(com, &buf, 1);
    if (verbose > 3)
        flsprintf(stdout, n < 1 ? "RX: fail\n" : "RX:  0x%02X\n", buf & 0xFF);
    if (n == 1) return buf & 0xFF;

    comErr("Serial port failed to receive a byte, read returned %d\n", n);
    return -1;  // never reached
}

// This reads away break 'character' from the serial line
void flushBreak() {
    int i;
    for (i = 0; i < 2; ++i) {
        char buf;
        int n = read(com, &buf, 1);
        if (verbose > 3)
            flsprintf(stdout, n < 1 ? "FL: nothing\n" : "FL:  0x%02X\n",
                      buf & 0xFF);
    }
}

void sendByte(int byte) {
    byte &= 0xFF;
    putByte(byte);
    char buf;
    if (read(com, &buf, 1) != 1)
        comErr("Loopback failed, nothing was received\n");
    int rx = buf & 0xFF;
    // if (byte !=  rx) {

    // read(com, &buf, 1);
    // comErr("Loopback failed, sent 0x%02X, got 0x%02X\n", byte,  rx);
    //}
    rx = getByte();
    // if (byte !=  rx)
    // comErr("Target echo failed, sent 0x%02X, got 0x%02X\n", byte, rx);
}

void readMemory(int addr, int n, int tick) {
    if (verbose > 2)
        flsprintf(stdout, "Read memory address %04X size %04X\n", addr, n);
    unsigned char* p = &image[addr];
    sendByte(0x4A);  // Monitor mode READ command
    sendByte(addr >> 8);
    sendByte(addr & 0xFF);
    *(p++) = getByte();
    n--;
    int tc = 0;
    while (n > 0) {
        sendByte(0x1A);  // Monitor mode IREAD command
        int b1 = getByte();
        int b2 = getByte();
        *(p++) = b1;
        n--;
        if (n > 0) *(p++) = b2;
        n--;
        if (tick) {
            tc++;
            if ((tc & tickP1) == 0) flsprintf(stdout, ".");
            // if ((tc & tickP2)==0)
            //    flsprintf(stdout,"\n");
        }
    }
}

void writeMemory(int addr, int n, int tick) {
    if (verbose > 2)
        flsprintf(stdout, "Write memory address %04X size %04X\n", addr, n);
    unsigned char* p = &image[addr];
    sendByte(0x49);  // Monitor mode WRITE command
    sendByte(addr >> 8);
    sendByte(addr & 0xFF);
    sendByte(*(p++));
    int tc = 1;
    while (n > 1) {
        sendByte(0x19);  // Monitor mode IWRITE command
        sendByte(*(p++));
        n -= 1;
        if (tick) {
            tc++;
            if ((tc & tickP1) == 0) flsprintf(stdout, ".");
            // if ((tc & tickP2)==0)
            //    flsprintf(stdout,"\n");
        }
    }
}

void connectTarget() {
    int j;
    if (connected) return;
    // Hmmm, following does not work, how should we do this
    // For blank device we need to send FF, but for non blank something else,
    // oth reprogramming a non blank device, does it make sense
    for (j = 0; j < 8; ++j) sendByte(0xFF);
    flushBreak();
    readMemory(0x40, 1, 0);
    connected = 1;
    if ((image[0x40] & 0x40) == 0)
        flsprintf(stdout, "Failed to unlock the security");

    // in case FLBPR is RAM based we clear it first by just writing it
    image[FLBPR] = 0xFF;
    writeMemory(FLBPR, 1, 0);
}

void dumpMemory(int addr, int n) {
    unsigned char* p = &image[addr];
    int i;
    for (i = 0; i < n; ++i) {
        if ((i & 0xF) == 0) flsprintf(stdout, "%04X  ", addr + i);
        flsprintf(stdout, "%02X ", *(p++) & 0xFF);
        if ((i & 0xF) == 7) flsprintf(stdout, " ");
        if ((i & 0xF) == 15) flsprintf(stdout, "\n");
    }
    if ((i & 0xF) != 0) flsprintf(stdout, "\n");
}

void dumpMemorySrec(int addr, int size) {
    unsigned char* p = &image[addr];
    while (size > 0) {
        int n = size > 16 ? 16 : size;
        int bc = 2 + n + 1;
        flsprintf(stdout, "S1%02X%04X", bc, addr);
        int s = (addr >> 8) + (addr & 0xFF) + bc;
        int i;
        for (i = 0; i < n; ++i) {
            int bty = *p & 0xFF;
            s += bty;
            flsprintf(stdout, "%02X", bty);
            ++p;
            ++addr;
        }
        size -= n;
        flsprintf(stdout, "%02X\n", ~s & 0xFF);
    }
}

int readSP() {
    if (verbose > 2) flsprintf(stdout, "Read Stak Pointer\n");
    sendByte(0x0C);  // Monitor mode READSP command
    return (((getByte() << 8) | (getByte() & 0xFF)) - 1) & 0xFFFF;
}

int runFrom(int PC, int A, int CC, int HX) {
    int SP = readSP();
    if (verbose > 2)
        flsprintf(stdout,
                  "Execute code PC=%04X A=%02X CC=%02X H:X=%04X SP=%04X\n", PC,
                  A, CC, HX, SP);
    image[SP + 1] = HX >> 8;
    image[SP + 2] = CC;
    image[SP + 3] = A;
    image[SP + 4] = HX & 0xFF;
    image[SP + 5] = PC >> 8;
    image[SP + 6] = PC & 0xFF;
    writeMemory(SP + 1, 6, 0);
    sendByte(0x28);  // Monitor mode RUN command
    return SP;
}

int lastMon = -1;

int callMonitor(int mon, int ctrlbyt, int accu, int faddr, int laddr) {
    int SP = readSP();
    image[CTRLBYT] = ctrlbyt;  // CTRLBYT BIT 6  =  1  = > mass erase
    image[CPUSPD] =
        CPUSPEED;  // CPUSPD  =  16 MHz ext clock  = > 4 MHz Fbus speed  = > 8
    image[LADDR] = laddr >> 8;
    image[LADDR + 1] = laddr & 0xFF;
    writeMemory(MONDATA, 4, 0);

    if (WORKRAM > 0xFF) {
        flsprintf(stderr, "Work RAM must be on zero page");
        abort();
    }

    if (lastMon != mon) {
        // construct small HC908 code fragment to call the monitor function and
        // return results
        int i = WORKRAM + 2;

        image[i++] = 0xCD;  // JSR mon ; calls the monitor routine
        image[i++] = mon >> 8;
        image[i++] = mon & 0xFF;

        image[i++] = 0x87;  // PSHA ; save condition A
        image[i++] = 0x85;  // TPA  ; condition codes to A
        image[i++] = 0xB7;  // STA  ; store to WORKRAM
        image[i++] = WORKRAM;
        image[i++] = 0x86;  // PULA ; restore A
        image[i++] = 0xB7;  // STA  ; store to WORKRAM+1
        image[i++] = WORKRAM + 1;

        image[i++] = 0x45;  // LDHX #SP+1 ; restore stack pointer
        image[i++] = (SP + 1) >> 8;
        image[i++] = (SP + 1) & 0xFF;
        image[i++] = 0x94;  // TXS

        image[i++] = 0xCC;  // JMP back to MON (this is the only way)
        image[i++] = MONRTN >> 8;
        image[i++] = MONRTN & 0xFF;

        if (WORKRAM >=
            WORKTOP) {  // leave some stack space for monitor routines
            flsprintf(stderr, "Not enough WORKRAM on target");
            abort();
        }

        writeMemory(WORKRAM, i - WORKRAM, 0);
        lastMon = mon;
    }

    // now execute the fragment
    runFrom(WORKRAM + 2, accu, 0x00, faddr);

    // fetch the return values
    readMemory(WORKRAM, 2, 0);
    return ((image[WORKRAM] & 0xFF) << 8) |
           (image[WORKRAM + 1] & 0xFF);  // return condition codes and accu
}

int fastProg(int faddr, int n) {
    static int n_addr;
    static int last_n;
    if (WORKRAM > 0xFF) {
        flsprintf(stderr, "Work RAM must be on zero page");
        abort();
    }

    if (lastMon != -1) {
        int SP = readSP();
        image[CTRLBYT] = 0;        // CTRLBYT =0
        image[CPUSPD] = CPUSPEED;  // CPUSPD  =  16 MHz ext clock  = > 4 MHz
                                   // Fbus speed  = > 8

        // construct small HC908 code fragment to call the monitor function and
        // return results
        int i = WORKRAM;
        image[i++] = 0x9D;  // NOP / reserve space
        image[i++] = 0x9D;  // NOP / reserve space

        image[i++] = 0x35;  // STHX WORKRAM (dir)
        image[i++] = WORKRAM;

        image[i++] = 0x45;  // LDHX #DATABUF
        image[i++] = DATABUF >> 8;
        image[i++] = DATABUF & 0xFF;

        image[i++] = 0xCD;  // JSR GETBYTE ; calls the monitor routine
        image[i++] = GETBYTE >> 8;
        image[i++] = GETBYTE & 0xFF;
        //        image[ i++ ] = 0xFE9C>>8;  ; // 0xFE9C = GET WITH echo
        //        image[ i++ ] = 0xFE9C&0xFF;

        image[i++] = 0xF7;  // STA ,X

        image[i++] = 0x5C;  // INCX

        image[i++] = 0xA3;  // cpx #DATABUF+n
        n_addr = i;
        image[i++] = 0;  // place holder

        image[i++] = 0x25;  // BLO *-10
        image[i++] = (-9) & 0xFF;

        image[i++] = 0x55;
        image[i++] = WORKRAM;

        image[i++] = 0xCD;  // JSR PRGRNGE ; calls the monitor routine
        image[i++] = PRGRNGE >> 8;
        image[i++] = PRGRNGE & 0xFF;

        image[i++] = 0x45;  // LDHX #SP+1 ; restore stack pointer
        image[i++] = (SP + 1) >> 8;
        image[i++] = (SP + 1) & 0xFF;

        image[i++] = 0x94;  // TXS

        image[i++] = 0xCC;  // JMP back to MON (this is the only way)
        image[i++] = MONRTN >> 8;
        image[i++] = MONRTN & 0xFF;

        if (WORKRAM >=
            WORKTOP) {  // leave some stack space for monitor routines
            flsprintf(stderr, "Not enough WORKRAM on target");
            abort();
        }

        writeMemory(WORKRAM, i - WORKRAM, 0);
        // dumpMemorySrec(WORKRAM , i-WORKRAM);
        lastMon = -1;
    }

    if (last_n != n) {
        image[n_addr] = DATABUF + n;
        writeMemory(n_addr, 1, 0);
        last_n = n;
    }

    int laddr = faddr + n - 1;
    image[LADDR] = laddr >> 8;
    image[LADDR + 1] = laddr & 0xFF;
    writeMemory(LADDR, 2, 0);

    // now execute the fragment
    runFrom(WORKRAM + 2, 0x00, 0x00,
            faddr);  // NOTE! to override flash security and erase FLBPR at be
                     // used as the erase address for mass erase

    int i;
    for (i = 0; i < n; ++i) putByte(image[faddr + i]);
    for (i = 0; i < n; ++i) getByte();

    readSP();
    return 0;
}

int fastProg2(int faddr, int progn) {
    if (WORKRAM > 0xFF) {
        flsprintf(stderr, "Work RAM must be on zero page");
        abort();
    }

    int SP = readSP();
    image[CTRLBYT] = 0;        // CTRLBYT =0
    image[CPUSPD] = CPUSPEED;  // CPUSPD=  16 MHz ext  => 4 MHz Fbus  = > 8

    // construct small HC908 code fragment to call the monitor function
    int i = WORKRAM;
    int j;

    int FADDR = i;
    image[i++] = faddr >> 8;  // FADDR initial contents
    image[i++] = faddr & 0xFF;

    int PROGN = i;
    image[i++] = progn >> 8;  // PROGN initial contents
    image[i++] = progn & 0xFF;

    int start = i;

    if (lastMon != -1) {
        image[i++] = 0x55;  //    LDHX PROGN  get bytes left to program
        image[i++] = PROGN;

        image[i++] = 0x27;  //    BEQ    DONE branch if all done
        int patchDone = i;
        image[i++] = 0;

        image[i++] = 0x65;  //    CPHX #PAGESIZE  only page full at a time
        image[i++] = 0;
        image[i++] = PAGESIZE;

        image[i++] = 0x25;  //    BLO DOIT
        image[i++] = 2;

        image[i++] = 0xAE;  //    LDX #PAGESIZE
        image[i++] = PAGESIZE;

        image[i++] = 0x9F;  //    TXA

        image[i++] = 0x87;  //    PSHA

        image[i++] = 0xB6;  //    LDA  PROGN+1
        image[i++] = PROGN + 1;

        image[i++] = 0x9E;  //        SUB 1,SP
        image[i++] = 0xE0;
        image[i++] = 1;

        image[i++] = 0xB7;  //    STA PROGN+1
        image[i++] = PROGN + 1;

        image[i++] = 0xB6;  //    LDA  PROGN
        image[i++] = PROGN;

        image[i++] = 0xA2;  //    SBC #0
        image[i++] = 0;

        image[i++] = 0xB7;  //    STA PROGN
        image[i++] = PROGN;

        image[i++] = 0x9F;  //    TXA

        image[i++] = 0x4A;  //    DECA

        image[i++] = 0xBB;  //    ADD FADDR+1
        image[i++] = FADDR + 1;

        image[i++] = 0xB7;  //    STA LADDR+1
        image[i++] = LADDR + 1;

        image[i++] = 0xB6;  //    LDA  FADDR
        image[i++] = FADDR;

        image[i++] = 0xA9;  //    ADC #0
        image[i++] = 0;

        image[i++] = 0xB7;  //    STA  LADDR
        image[i++] = LADDR;

        image[i++] = 0x45;  //        LDHX #DATABUF
        image[i++] = DATABUF >> 8;
        image[i++] = DATABUF & 0xFF;

        image[i++] = 0x86;  //        PULA

        int getMore = i;

        image[i++] = 0x87;  //    PSHA

        image[i++] = 0xCD;  //        JSR GETBYTE
        image[i++] = GETBYTE >> 8;
        image[i++] = GETBYTE & 0xFF;

        image[i++] = 0xF7;  //        STA ,X

        image[i++] = 0x5C;  //        INCX

        image[i++] = 0x86;  //    PULA

        image[i++] = 0x4A;  //    DECA

        image[i++] = 0x26;  //    BNE GETMORE
        j = i;
        image[i++] = (getMore - (j + 1)) & 0xFF;

        image[i++] = 0x55;  //    LDHX FADDR
        image[i++] = FADDR;

        image[i++] = 0xCD;  //        JSR PRGRNGE
        image[i++] = PRGRNGE >> 8;
        image[i++] = PRGRNGE & 0xFF;

        image[i++] = 0x8B;  // PSHH Annoyingly JB8 RDVRNG leaves H:X off by one
        image[i++] =
            0x89;  // PSHX compared to GZ16 necessiating this pus/pul sequence

        image[i++] = 0x55;  //        LDHX FADDR
        image[i++] = FADDR;

        image[i++] = 0xA6;  //    LDAA #1
        image[i++] = 1;

        image[i++] = 0xCD;  //        JSR RDVRRNG
        image[i++] = RDVRRNG >> 8;
        image[i++] = RDVRRNG & 0xFF;

        image[i++] = 0x88;  // PULX
        image[i++] = 0x8A;  // PULH

        image[i++] = 0x35;  //    STHX FADDR
        image[i++] = FADDR;

        image[i++] = 0xCD;  //        JSR PUTBYTE
        image[i++] = PUTBYTE >> 8;
        image[i++] = PUTBYTE & 0xFF;

        image[i++] = 0x20;  //    BRA start
        j = i;
        image[i++] = (start - (j + 1)) & 0xFF;

        image[patchDone] = (i - (patchDone + 1)) & 0xFF;

        image[i++] = 0x45;  // LDHX #SP+1 ; restore stack pointer
        image[i++] = (SP + 1) >> 8;
        image[i++] = (SP + 1) & 0xFF;

        image[i++] = 0x94;  // TXS

        image[i++] = 0xCC;  // JMP back to MON (this is the only way)
        image[i++] = MONRTN >> 8;
        image[i++] = MONRTN & 0xFF;

        if (i >= WORKTOP) {  // leave some stack space for monitor routines
            flsprintf(stderr, "Not enough WORKRAM on target");
            abort();
        }

        writeMemory(WORKRAM, i - WORKRAM, 0);
        lastMon = -1;
    } else
        writeMemory(WORKRAM, 4, 0);

    runFrom(start, 0x00, 0x00, 0);

    while (progn) {
        int n = progn < PAGESIZE ? progn : PAGESIZE;
        int sum = 0;
        for (i = 0; i < n; ++i) {
            putByte(image[faddr + i]);
        }
        for (i = 0; i < n; ++i) {
            int b = getByte();
            // flsprintf(stderr,"%d %02X\n",i,b);
            sum = (sum + b) & 0xFF;
            if (b != image[faddr + i])
                flsprintf(
                    stderr,
                    "Program data transfer error, at %04X sent %02X got %02\n",
                    faddr + i, image[faddr + i], b);
        }
        int back = getByte();
        // flsprintf(stderr,"%02X\n",back);
        if (back != sum)
            flsprintf(stderr,
                      "Program checksum failure, at %04X size %04X checksum "
                      "calculated %02X received %02X\n",
                      faddr, n, sum, back);

        progn -= n;
        faddr += n;
        if (verbose) flsprintf(stdout, ".");
    }
    return 0;
}

void massErase() {
    // NOTE! to override flash security and erase FLBPR must be used as the
    // erase address for mass erase
    if (verbose) flsprintf(stdout, "Mass erase\n");
    callMonitor(ERARRNG, 0x40, 0, EADDR, 0);
}

void flashProgram(int addr, int size, int verify) {
    if (addr < FLASH) {
        flsprintf(stdout,
                  "Programming address %04X below flash start address %04X\n",
                  addr, FLASH);
        exit(0);
    }

    if (useFastProg) {
        if (verbose)
            flsprintf(stdout, "Program %04X - %04X ", addr, addr + size - 1);

        fastProg2(addr, size);
        if (verbose) flsprintf(stdout, "\n");
    } else {
        while (size > 0) {
            int n = size <= PAGESIZE ? size : PAGESIZE;
            if (verbose)
                flsprintf(stdout, "Program %04X - %04X ", addr, addr + n - 1);

            memcpy(&image[DATABUF], &image[addr], n);
            writeMemory(DATABUF, n, verbose);
            callMonitor(PRGRNGE, 0, 0, addr, addr + n - 1);

            if (verify) {
                int cca = callMonitor(RDVRRNG, 0, 1, addr, addr + n - 1);
                int sum = 0;
                int i;
                for (i = 0; i < n; ++i) sum = (sum + image[addr + i]) & 0xFF;
                int back = cca & 0xFF;  // get Accu from target
                if (sum != back) {
                    flsprintf(stderr,
                              "Program checksum failure, at %04X size %04X "
                              "checksum calculated %02X received %02\n",
                              addr, n, sum, back);
                    abort();
                }

                if (!(cca & 0x0100)) {  // check Carry bit from target
                    flsprintf(stderr, "Verify failed\n");
                    abort();
                }
                if (verbose) flsprintf(stdout, "OK");
            }
            if (verbose) flsprintf(stdout, "\n");
            addr += n;
            size -= n;
        }
    }
}

int readSrec(int verbose, FILE* sf, unsigned char* image, int size, int base,
             int* ranges, int rn) {
    if (verbose) flsprintf(stdout, "Reading S-records\n");
    memset(image, 0xff, size);
    char line[2 + 2 + 255 * 2 + 2 +
              1];  // Sx + count + 255 bytes for data address & checksum + CR/LF
                   // +nul (in windows)
    int amax = 0;
    int rc = 0;
    while (fgets(line, sizeof(line), sf) != NULL) {
        int o = 0;
        if (line[0] == 'S') {
            unsigned int n, a;
            sscanf(line + 2, "%2x", &n);
            n--;
            if (line[1] == '1') {
                sscanf(line + 4, "%4x", &a);
                n = n - 2;
                o = 8;
            }
            if (line[1] == '2') {
                sscanf(line + 4, "%6x", &a);
                n = n - 4;
                o = 10;
            }
            if (line[1] == '3') {
                sscanf(line + 4, "%8x", &a);
                n = n - 6;
                o = 12;
            }
            if (o != 0) {
                int i, j;
                if (ranges) {
                    for (i = 0; i < rc; i += 2) {
                        int rlo = ranges[i];
                        int rhi = rlo + ranges[i + 1];
                        if (!((a + n <= rlo) || (rhi <= a))) {
                            flsprintf(stderr,
                                      "Overlapping S-record ranges %04X,%04X "
                                      "and %0x4 %04X\n",
                                      rlo, rhi - rlo, a, n);
                            abort();
                        }
                    }
                    if (rc + 2 >= rn) return -1;
                    ranges[rc] = a;
                    ranges[rc + 1] = n;
                    rc += 2;

                    int cf = 0;
                    do
                    compact : {
                        for (i = 0; i < rc; i += 2) {
                            for (j = i + 2; j < rc; j += 2) {
                                cf = 1;
                                if (ranges[i] + ranges[i + 1] == ranges[j])
                                    ranges[i + 1] += ranges[j + 1];
                                else if (ranges[i] == ranges[j] + ranges[j + 1])
                                    ranges[i] -= ranges[j + 1];
                                else
                                    cf = 0;
                                if (cf) {
                                    for (i = j + 2; i < rc; i++)
                                        ranges[i] = ranges[i + 2];
                                    rc -= 2;
                                    cf = 0;
                                    goto compact;
                                }
                            }
                        }
                    }
                        while (cf)
                            ;
                }
                for (i = 0; i < n; ++i) {
                    unsigned int d;
                    sscanf(line + o + i * 2, "%2x", &d);
                    if ((a >= base) && (a < base + size)) {
                        image[a - base] = d;
                        a++;
                        amax = a > amax ? a : amax;
                    }
                }
            }
        }
        if (verbose > 1) flsprintf(stdout, ">>> %s", line);
        if (verbose && o == 0) flsprintf(stdout, "Line ignored: %s\n", line);
    }
    if (verbose) {
        if (ranges) {
            int i;
            for (i = 0; i < rc; i += 2)
                flsprintf(stdout, "S-record data address %06X size %06X\n",
                          ranges[i], ranges[i + 1]);
        }
        flsprintf(stdout, "\n");
    }
    return rc;
}
void printHelp() {
    flsprintf(stdout,
              "bl08 burns MC68HC908 Flash memory from S-record file(s) using "
              "Monitor mode\n");
    flsprintf(stdout, "Usage: \n");
    flsprintf(stdout, " bl08 [-abcdefhiklmnpqrstuvx] [filename...]\n");
    flsprintf(
        stdout,
        "  -a address     Set dump memory address (needs -s option too)\n");
    flsprintf(stdout,
              "  -b baudrate    Set baudrate for target communication\n");
    flsprintf(stdout,
              "  -c device      Set serial com device used to communicate with "
              "target\n");
    flsprintf(stdout, "                 typically /dev/ttyS0 \n");
    flsprintf(
        stdout,
        "  -d dumpformat  Set dump format, supported formats are: 'srec'\n");
    flsprintf(stdout,
              "  -e             Erase target using mass erase mode, clearing "
              "security bytes\n");
    flsprintf(stdout, "  -f             Use fast programming method");
    flsprintf(stdout,
              "  -g address     Go execute target code from address or use '-g "
              "reset'\n");
    flsprintf(stdout, "  -h             Print out this help text\n");
    flsprintf(stdout,
              "  -i             Read input (S-records) from standard inputi\n");
    flsprintf(stdout, "  -k             Kill previous instance of bl08\n");
    flsprintf(stdout,
              "  -l verbosity   Set verbosity level, valid values are 0-4, "
              "default 1\n");
    flsprintf(stdout, "  -m             Terminal emulator mode\n");
    flsprintf(stdout, "  -n             Print bl08 version number\n");
    flsprintf(stdout, "  -o param=value Override target parameter value\n");
    flsprintf(stdout,
              "                 param = "
              "ROMBASE,FLASH,PUTBYTE,GETBYTE,RDVRRNG,MONRTN\n");
    flsprintf(stdout,
              "                         "
              "ERARRNG,PRGRNGE,FLBPR,MONDATA,PAGESIZE,EADDR\n");
    flsprintf(stdout,
              "  -p             Use page erase when programming flash\n");
    flsprintf(stdout, "  -q             Run quietly, same as -l 0\n");
    flsprintf(stdout, "  -s size        Set dump memory size\n");
    flsprintf(stdout, "  -r pulse       Pulse DTR for pulse milliseconds\n");
    flsprintf(stdout,
              "  -t cputype     Set CPU type, valid values are: 'gz16'\n");
    flsprintf(stdout, "  -u             Upload only (do not program flash)\n");
    flsprintf(stdout, "  -v             Verify when programming \n");
    flsprintf(stdout,
              "  -x cpuspeed    Set CPU speed, typically set for Fbus (in MHz) "
              "x 4 \n");
    flsprintf(stdout,
              "  -z             Do not program, do no upload, just read in the "
              "S-rec file \n");
    flsprintf(stdout,
              " addresses and sizes in decimal, for hex prefix with '0x'\n");
    // Example
    exit(0);
}

void termEmu() {
    int STDIN = 0;
    int STDOUT = 1;
    // get rid of stuff that has been echoed to us
    tcflush(com, TCIFLUSH);  // .. then get rid of the echoes and what ever...

    struct termios newtio, oldtio;
    tcgetattr(STDIN, &oldtio);  // we do not want to change the console setting
                                // forever, so keep the old
    tcgetattr(STDIN, &newtio);  // configure the console to return as soon as it
                                // a key is pressed
    newtio.c_lflag = 0;
    newtio.c_cc[VTIME] = 0;
    newtio.c_cc[VMIN] = 1;
    tcsetattr(STDIN, TCSANOW, &newtio);

    flsprintf(stdout, "\nTerminal mode, press CTRL-C to exit.\n");

    fd_set readfs;
    int maxfd;
    int res;
    char buf[2];
    maxfd = com + 1;

    while (1) {
        // wait for something from console or serial port
        FD_SET(STDIN, &readfs);
        FD_SET(com, &readfs);
        struct timeval tout;
        tout.tv_usec = 10;
        tout.tv_sec = 0;
        select(maxfd, &readfs, NULL, NULL, &tout);

        // copy console stuff to the serial port
        if (FD_ISSET(STDIN, &readfs)) {
            res = read(STDIN, buf, 1);
            if (buf[0] == 3) break;  // CTRL-C terminates terminal mode
            write(com, buf, res);
        }
        // copy serial port stuff to console
        if (FD_ISSET(com, &readfs)) {
            res = read(com, buf, 1);
            write(STDOUT, buf, res);
            fflush(stdout);
        }
    }

    tcsetattr(0, TCSANOW, &oldtio);  // restore console settings

    ///    close(con);
}

void setCPUtype(char* cpu) {
    if (strcmp("gz16", cpu) == 0) {
        // These settings depend on the CPU version
        FLASH = 0xC000;
        PUTBYTE = 0xFEAF;
        GETBYTE = 0x1C00;
        RDVRRNG = 0x1C03;
        ERARRNG = 0x1C06;
        PRGRNGE = 0x1C09;
        MONRTN = 0xFE20;
        FLBPR = 0xFF7E;
        EADDR = FLBPR;
        MONDATA = 0x48;
        PAGESIZE = 64;
    } else if (strcmp("jb8", cpu) == 0) {
        // These settings depend on the CPU version
        FLASH = 0xDC00;
        PUTBYTE = 0xFED6;
        GETBYTE = 0xFC00;
        RDVRRNG = 0xFC03;
        ERARRNG = 0xFC06;
        PRGRNGE = 0xFC09;
        MONRTN = 0xFE55;
        FLBPR = 0xFE09;
        EADDR = FLASH;
        MONDATA = 0x48;
        PAGESIZE = 64;
    } else {
        flsprintf(stderr, "Unsupported CPU type '%s'\n", cpu);
        abort();
    }
    // these are independent of CPU type
    DATABUF = MONDATA + 4;
    WORKRAM = DATABUF + PAGESIZE;
    WORKTOP = 0xF0;  // this leaves 16 bytes for stack, the deepest ROM routines
                     // use 11 so there some for myself too
    CTRLBYT = MONDATA + 0;
    CPUSPD = MONDATA + 1;
    LADDR = MONDATA + 2;
    if (DATABUF + PAGESIZE > 0xFF) {
        flsprintf(stderr,
                  "bl08 limitation, DATABUF+PAGESIZE>0xFF, DATABUF=%04X, "
                  "PAGESIZE=%04X\n",
                  DATABUF, PAGESIZE);
        abort();
    }
}

int getIntArg(char* arg) {
    if (strlen(arg) >= 2 && memcmp(arg, "0x", 2) == 0) {
        unsigned int u;
        sscanf(arg + 2, "%X", &u);
        return u;
    } else {
        int d;
        sscanf(arg, "%d", &d);
        return d;
    }
}

void parseOverride(char* str) {
    char* vp = strstr(str, "=");
    if (!vp) {
        flsprintf(stderr, "Bad override syntax, no '=' found\n");
        abort();
    }
    *vp = 0;
    vp++;
    int val = getIntArg(vp);
    if (strcmp("ROMBASE", str) == 0) {
        GETBYTE = val + 0;
        RDVRRNG = val + 3;
        ERARRNG = val + 6;
        PRGRNGE = val + 9;
    } else if (strcmp("FLASH", str) == 0)
        FLASH = val;
    else if (strcmp("PUTBYTE", str) == 0)
        PUTBYTE = val;
    else if (strcmp("GETBYTE", str) == 0)
        GETBYTE = val;
    else if (strcmp("RDVRRNG", str) == 0)
        RDVRRNG = val;
    else if (strcmp("ERARRNG", str) == 0)
        ERARRNG = val;
    else if (strcmp("PRGRNGE", str) == 0)
        PRGRNGE = val;
    else if (strcmp("FLBPR", str) == 0)
        FLBPR = val;
    else if (strcmp("MONDATA", str) == 0)
        MONDATA = val;
    else if (strcmp("PAGESIZE", str) == 0)
        PAGESIZE = val;
    else if (strcmp("MONRTN", str) == 0)
        MONRTN = val;
    else if (strcmp("EADDR", str) == 0)
        EADDR = val;
    else {
        flsprintf(stderr, "Attempt to override unrecognized variable %s\n",
                  str);
        abort();
    }
}

void parseArgs(int argc, char* argv[]) {
    int c;
    while ((c = getopt(argc, argv, "a:b:c:d:efg:hikl:mno:pqr:s:t:uvx:z")) !=
           -1) {
        switch (c) {
            case 'a':
                dumpStart = getIntArg(optarg);
                break;
            case 'b':
                sscanf(optarg, "%d", &baudRate);
                break;
            case 'c':
                COM = optarg;
                break;
            case 'd':
                dumpFormat = optarg;
                break;
            case 'e':
                eraseFlash = 1;
                break;
            case 'f':
                useFastProg = 1;
                break;
            case 'g':
                executeCode = optarg;
                break;
            case 'h':
                printHelp();
                break;
            case 'i':
                useStdin = 1;
                break;
            case 'k':
                killPrevious = 1;
                break;
            case 'l':
                sscanf(optarg, "%d", &verbose);
                break;
            case 'm':
                terminalMode = 1;
                break;
            case 'n':
                flsprintf(stdout, "%s\n", version);
                break;
            case 'p':
                pageErase = 1;
                break;
            case 'o':
                parseOverride(optarg);
                break;
            case 'q':
                verbose = 0;
                break;
            case 'r':
                resetPulse = getIntArg(optarg);
                break;
            case 's':
                dumpSize = getIntArg(optarg);
                break;
            case 't':
                setCPUtype(optarg);
                break;
            case 'u':
                uploadOnly = 1;
                break;
            case 'v':
                verify = 1;
                break;
            case 'x':
                sscanf(optarg, "%d", &CPUSPEED);
                break;
            case 'z':
                loadOnly = 1;
                break;
            case '?':
                if (isprint(optopt))
                    fprintf(stderr, "Unknown option `-%c'.\n", optopt);
                else
                    fprintf(stderr, "Unknown option character `\\x%x'.\n",
                            optopt);
            default:
                fprintf(stderr, "Bug, unhandled option '%c'\n", c);
                abort();
        }
    }
    if (argc <= 1) printHelp();
}

void ioctlErrCheck(int e) {
    if (e) {
        flsprintf(stdout, "ioctl returned %d\n", e);
        abort();
    }
}

void generateReset() {
#define TIOCM_DTR 0x002
    int s;
    ioctlErrCheck(ioctl(com, TIOCMGET, &s));

    s |= TIOCM_DTR;
    ioctlErrCheck(ioctl(com, TIOCMSET, &s));

    struct timespec tspec;
    tspec.tv_sec = resetPulse / 1000;
    tspec.tv_nsec = (resetPulse % 1000) * 1000000;
    nanosleep(&tspec, 0);

    s &= ~TIOCM_DTR;
    ioctlErrCheck(ioctl(com, TIOCMSET, &s));
}

void deletePidFile() {
    int stat = remove(PIDFILENAME);
    if (stat) printf("remove returned %d\n", stat);
}

void killPreviousInstance() {
    atexit(deletePidFile);
    int pid;
    FILE* pidf = fopen(PIDFILENAME, "r");
    if (pidf) {
        fscanf(pidf, "%d", &pid);
        int stat = kill(pid, SIGKILL);
        if (stat != 0) printf("kill returned %d\n", stat);

        fclose(pidf);
        waitpid(pid, &stat, 0);
        if (WIFEXITED(stat) == 0)
            printf("waitpid returned %d\n", WIFEXITED(stat));
    }
    pidf = fopen(PIDFILENAME, "w");
    fprintf(pidf, "%d\n", getpid());
    fclose(pidf);
}

int main(int argc, char* argv[]) {
    // default values
    setCPUtype("gz16");
    CPUSPEED = 8;
    baudRate = 14400;

    parseArgs(argc, argv);

    if (killPrevious) killPreviousInstance();

    if (verbose)
        flsprintf(stdout, "bl08 - MC68HC908 Bootloader - version %s\n",
                  version);

    memset(image, 0xFF, sizeof(image));

    int maxrc = 256;
    int ranges[maxrc];
    int rc = 0;

    if (useStdin)
        rc += readSrec(verbose, stdin, image, sizeof(image), 0x0000,
                       ranges + rc, maxrc - rc);

    int i;
    for (i = optind; i < argc; i++) {
        char* filename = argv[i];
        FILE* sf = fopen(filename, "r");
        if (sf == NULL) {
            flsprintf(stderr, "Failed to open '%s'\n", filename);
            abort();
        }

        int rn = readSrec(verbose, sf, image, sizeof(image), 0x0000,
                          ranges + rc, maxrc - rc);
        if (rn < 0) {
            flsprintf(
                stderr,
                "Too many discontinuous data ranges in S-record file '%s'\n",
                filename);
            abort();
        }
        rc += rn;
        fclose(sf);
    }

    if (!loadOnly) {
        initSerialPort();

        if (resetPulse) generateReset();

        if (eraseFlash) {
            connectTarget();
            massErase();
        }

        if (rc > 0) {
            connectTarget();
            if (pageErase) {
                for (i = 0; i < rc;) {
                    int addr = ranges[i++];
                    int size = ranges[i++];
                    int a = addr / PAGESIZE * PAGESIZE;
                    while (a < addr + size) {
                        if (verbose)
                            flsprintf(stdout, "Erase %04X - %04X\n", a,
                                      PAGESIZE);

                        callMonitor(ERARRNG, 0, 0, a, 0);
                        a += PAGESIZE;
                    }
                }
            } else
                massErase();

            int i;
            for (i = 0; i < rc;) {
                int addr = ranges[i++];
                int size = ranges[i++];
                if (uploadOnly) {
                    if (verbose)
                        flsprintf(stdout, "Uploading memory contents\n");
                    writeMemory(addr, size, verbose);
                    if (verbose) flsprintf(stdout, "\n");
                } else
                    flashProgram(addr, size, verify);
                if (verbose > 1) {
                    flsprintf(stdout, "Reading back memory/flash content");
                    readMemory(addr, size, verbose);
                    flsprintf(stdout, "\n");
                    dumpMemory(addr, size);
                }
            }
        }

        if (executeCode) {
            connectTarget();
            int addr;
            if (strcmp("reset", executeCode) == 0) {
                readMemory(0xFFFE, 2, 0);
                addr = ((image[0xFFFE] & 0xFF) << 8) | (image[0xFFFF] & 0xFF);
            } else
                addr = getIntArg(executeCode);

            if (verbose) flsprintf(stdout, "Execute code from %04X\n", addr);
            runFrom(addr, 0, 0, 0);
        }

        if (terminalMode) termEmu();
    }

    if (dumpSize > 0) {
        if (!loadOnly) {
            connectTarget();
            if (verbose) flsprintf(stdout, "Reading memory\n");
            readMemory(dumpStart, dumpSize, verbose);
        }
        if (verbose) flsprintf(stdout, "\n");

        if (dumpFormat) {
            if (strcmp("srec", dumpFormat) == 0)
                dumpMemorySrec(dumpStart, dumpSize);
            else if (strcmp("hex", dumpFormat) == 0)
                dumpMemory(dumpStart, dumpSize);
            else
                flsprintf(stderr, "Unknown dump format '%s'\n", dumpFormat);
        }
    }

    return 0;
}