Source file: /~heha/hs/avrpp.zip/src/avrpp.cpp

/*----------------------------------------------------------------------*
 * AVRPP - AVR Parallel Programming Controller				*
 *									*
 * R0.46 (C)ChaN, 2015 Haftmann 2017					*
 *----------------------------------------------------------------------*
 * R0.31  Nov 11, '04  Migration from MS-DOS based AVRXP R0.25		*
 * R0.32  Feb 02, '05  mega406						*
 * R0.33  Feb 11, '05  90PWM2/3						*
 * R0.34  Feb 15, '05  tiny25/45/85					*
 * R0.35  Mar 12, '05  mega640/1280/1281/2560/2561			*
 * R0.36  Aug 10, '05  tiny25/45/85					*
 * R0.37  Jan 30, '06  90CAN32/64/128, -q switch			*
 * R0.38  Mar 15, '06  ATmega644, Fixed number of cals for tiny2313	*
 * R0.39  Mar 18, '07  ATmega164P/324P/644P, ATtiny261/461/861		*
 * R0.40  Aug 08, '07  ATmega48P/88P/168P/328P				*
 * R0.41  Dec  7, '08  ATmega325P/3250P/324PA, AT90PWM216/316		*
 * R0.42  Feb  8, '10  ATtiny43U/48/88/87/167				*
 * R0.42T1 Jul 9, '10  Tomkiewicz: linux, inpout32			*
 * R0.43  Jul 21, '10  Supported Flash/EEPROM/Fuse combined hex files	*
 * R0.43b Sep  2, '10  Added -ff switch, ATtiny4313			*
 * R0.43d Dec 23, '12  ATtiny1634					*
 * R0.44  Jun 13, '15  ATmega48PB/88PB/168PB, ATtiny828			*
 * R0.45  Jul 11, '17  ELF, Signature check, TPI, MessageBox		*
 * R0.46  Apr  2, '19  fuse.txt inclusion+highlight, EESAVE check	*
 * R0.46  July 1, '19  command-line write to flash/eeprom		*
 * R0.47  Dec  2, '19  corrected ELF loading: Now use Program Header	*
 * R0.48  Dec  5, '19  corrected hexdump screen output			*
 * R0.49  Jan 20, '21  corrected R0.47: -ff command writes all fuses	*
 * R0.50  Apr  3, '22  evaluate ELF .note.gnu.avr.deviceinfo section	*
 * R0.51  May 12, '22  ATmega328PB					*
 *----------------------------------------------------------------------*/

#include "avrpp.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#ifdef _WIN32
# include <io.h>	// isatty(), fileno()
#else
# include <sys/ioctl.h>
# include <unistd.h>
#endif


const DEVPROP *Device;	// Pointer to the current AVR property
static struct{
 byte Fuses;		// number of fuse bytes (1..3 = low,high,ext)
 byte FuseMask[3];	// valid bits in up to 3 fuse bytes
}DevInfo;

/*-----------------------------------------------------------------------
  Global variables (initialized by load_commands())
-----------------------------------------------------------------------*/

byte*CodeBuff;		// Program code R/W buffer
byte*DataBuff;		// EEPROM data R/W buffer
byte CaliBuf[4];	// Calibration bytes read buffer
byte FuseBuf[3];	// Fuse bytes read buffer
byte SignBuf[3];	// Device signature read buffer

bool outredir;		// stdout stream redirected
byte termwidth=80;	// width of terminal/console window, for hex/ascii dump
byte linebytes=32;	// HEX line length, in output bytes (1,2,4,..,128)

/*---------- Command Parameters ------------*/
static struct{
 byte fHelp;	// -h (basic help), -m (more info), -d (device list)
 byte fRead;	// -r Read command and flags
		// Bit	0: -rp Read program memory
		//	1: -re Read EEPROM
		//	2: (unused)
		//	3: -ri Get info
		//	4: -rl Read low fuse
		//	5: -rh Read high fuse
		//	6: -rx Read extended fuse
		//	7: -rc Read calibration byte(s)
 byte fOpt;	// Bit	0: -e Erase device
		//	1: -v Verify only
		//	2: -c Copy calibration bytes into end of flash
		//	3: -s Require signature in HEX/ELF file for comparison
		//	4: Check signature
		//	5: -w Pause before exiting program
 byte fWrFuse;	// Bit	0: -fl{bin} Write low fuse
		//	1: -fh{bin} Write high fuse
		//	2: -fx{bin} Write extended fuse
		//	3: -l<bin> Write lock bits
		//	4: Take default value, otherwise Fuse[0] for low fuse
		//	5: Take default value, otherwise Fuse[1] for high fuse
		//	6: Take default value, otherwise Fuse[2] for extended fuse
		//	7: -ff Take fuse values from file
 dword CodeSize;// Loaded program code size (.hex)
 dword DataSize;// Loaded EEPROM data size (.eep)
 byte Fuse[4];	// fuse bytes to be written {Low,High,Extended,Lock}
 byte Sign[3];	// signature bytes to be checked
 unsigned crystal_MHz;
}Cmd;

//--- Hardware Control ---

PORTPROP CtrlPort;

//--- Messages ---
//Indented by 7 spaces to make tabulators appearing same way in editor and shell output

const char MesUsage[]=
       "AVRPP - AVR Parallel Programming tool 2022-05-12  http://elm-chan.org/\n\n"
       "Write code and/or data	: <elf/hex file> [<hex file>] ...\n"
       "Verify code and/or data	: -v <elf/hex file> [<hex file>] ...\n"
       "Read code, data or fuse	: -r[pef]*\n"
       "Write fuse byte		: -f{l|h|x}<binary>\n"
       "Reset fuse byte		: -f{l|h|x}\n"
       "Load fuse from hex file : -ff		may load lock byte too\n"
       "Lock device		: -l[<binary>]\n"
       "Erase device		: -e		automatically before flashing\n"
       "Copy calibration bytes	: -c\n"
       "Parallel port [-p1]	: -p<n>		hexadecimal port address may be given\n";
const char MesMore[]=
       "More options		: -m\n";
const char MesDevices[]=
       "List supported AVRs	: -d\n";
const char MoreInfo[]=
       "Delay port I/O in microseconds	: -i<us>	default:none\n"
       "Use inpout32.dll		: -i\n"
       "Check file's chip signature	: -s	if present, chip signature is checked\n"
       "Short way for 8/14 pin device	: -8	skip checking HVPP devices\n"
       "Short for ATtiny15		: -5\n"
       "Short way for 6 pin device	: -6	skip ckecking HVPP and HVSP devices\n"
       "Oscillator frequency		: -f=<MHz>	for StartUpTimer calculation\n"
       "Set/modify flash/eeprom content	: -w[f|e]adr=u8u8'str'u8 or u16,u16,'str',u16\n";

const char Devices[]=
       "Supported AVRs:\n"
       "AT90S	1200,2313,2323,2333,2343,4414,4433,4434,8515,8535\n"
       "ATtiny	4,5,9,10,11,12,13,15,20,22,24,25,26,28,40,43U,\n"
       "	44,45,48,84,85,87,88,167,261,441,461,841,861,2313,1634\n"
       "ATmega	8,16,32,48(P),64,88(P),103,128,161,162,163,164P,165,168(P),169,323,\n"
       "	324P(A),325(P),329,3250P,328(P)(B),406,603,640,644(P),1284P,645/649,\n"
       "	1280,1281,2560,2561,3250/3290,6450/6490,8515,8535\n"
       "AT90CAN32,64,128, AT90PWM 2,3,216,316\n";

static dword FlashSize(const DEVPROP*Device) {
 byte b=Device->FlashPageSHL,s=Device->FlashPageS;
 return b<16?1<<(b+s):b<<s;	// in one case, ATmega406, is b==80
}

// Output the device information
static void output_deviceinfo() {
 putchar('\n');
 printf("Device Signature  = 1E-%02x-%02X",Device->Sign[0],Device->Sign[1]);
 putchar('\n');
 dword s=FlashSize(Device);
 dword p=1<<Device->FlashPageS;
 printf("Flash Memory Size = %d bytes",s);
 if (p>1) printf(" (%d bytes x %d pages)",p,s/p);
 putchar('\n');
 if (Device->EepromSizeS) {
  s=1<<Device->EepromSizeS;
  p=1<<Device->EepromPageS;
  printf("EEPROM Size\t  = %d bytes",s);
  if (p>1) printf(" (%d bytes x %d pages)",p,s/p);
  putchar('\n');
 }
}
// Win32 Console vs. DOS/Linux ANSI sequences for output colorization
// Windows 10 can support ANSI sequences too, important for underline etc. later
#ifdef _WIN32
static HANDLE hStdOut;
#else
static void colorize(int c) {printf("\33[1;%dm",c);}
#endif
static void cyan() {
#ifdef _WIN32
 SetConsoleTextAttribute(hStdOut,FOREGROUND_GREEN|FOREGROUND_BLUE|FOREGROUND_INTENSITY);
#else
 colorize(36);
#endif
}
static void green() {
#ifdef _WIN32
 SetConsoleTextAttribute(hStdOut,FOREGROUND_GREEN|FOREGROUND_INTENSITY);
#else
 colorize(32);
#endif
}
static void yellow() {
#ifdef _WIN32
 SetConsoleTextAttribute(hStdOut,FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_INTENSITY);
#else
 colorize(33);
#endif
}
static void hilite() {
#ifdef _WIN32
 SetConsoleTextAttribute(hStdOut,FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_BLUE|FOREGROUND_INTENSITY);
#else
 fputs("\33[1m",stdout);
#endif
}
static void normal() {
#ifdef _WIN32
 SetConsoleTextAttribute(hStdOut,FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_BLUE);
#else
 fputs("\33[m",stdout);
#endif
}

// Output a fuse byte and description
// NEW: No dependency to fuse.txt anymore
//	Highlighted non-default fuse bits
//	Current fuse selection shown, in cyan
static void put_fuseval(int i) {
 const FUSEPROP&f=FuseProps[Device->fusepropidx];
 const byte*p=f.ids+(i<<3);
 static const char*head[]={"Low: ","High:","Ext: "};
 fputs(head[i], stdout);
 byte val=FuseBuf[i];
 byte def=f.FuseDefault[i];
 for (int n=8;--n>=0;) {
  if ((val^def)&0x80) hilite();
  putchar(p[n]? val & 0x80 ? '1' : '0' : '-');
  if ((val^def)&0x80) normal();
  val<<=1; def<<=1;
 }
 putchar('\n');
 for (int w,b=0;b<8;b+=w) {
  w=1;
  byte j=p[b];				// load fuse type
  if (j) {
   int c;
   for (c=b+1;c<8;c++) if (p[c]==j) ++w; else break;	// count (adjanced) bits for fuse type <j>
   for (c=0;c<5;c++) putchar(' ');
// BUGBUG: Depending on terminal font, Windows accepts UTF-8 characters or OEM characters for line drawing.
#ifdef _WIN32
   for (c=8;--c>=b+w;) putchar(p[c]?0xB3:' ');
   putchar(0xC0);
   for (;--c>=b;) putchar(0xC1);
   for (;--c>=-2;) putchar(0xC4);
#else
   for (c=8;--c>=b+w;) fputs(p[c]?"│":" ",stdout);
   fputs("└",stdout);
   for (;--c>=b;) fputs("┴",stdout);
   for (;--c>=-2;) fputs("─",stdout);
#endif
   putchar(' ');
   const char*q=FuseNames[j];		// string: "Name <space> Description with [|] alternatives"
   while (*q!=' ') putchar(*q++);	// name of the fuse
   if (w>1) {
    putchar('[');
    putchar('0'+w);			// bit width of fuse cluster
    putchar(']');
   }
   putchar(':');
   const char*qb=strchr(q,'[');		// begin of "[a|b|c]" selection
   if (qb) {
    const char*qw=qb+1;			// begin of first word ("a")
    const char*qe=strchr(qw,']');	// end of selection
    if (qe) {
     val=(~FuseBuf[i]>>b)&(1<<w)-1;	// fuse cluster, inverted
     const char*qn;
     do{
      qn=strchr(qw,'|');
      if (!qn || qn>qe) qn=qe;		// end of word
      if (!val) {			// zero reached?
       while(q!=qb) putchar(*q++);	// before "["
       cyan();
       while(qw!=qn) putchar(*qw++);	// the right selection
       normal();
       q=qe+1;				// after "]"
       goto e;
      }
      qw=qn+1;
      --val;				// count down possibilities
     }while (*qn!=']');
    }
   }
// TODO: Calculate SUT (StartUpTimer) fuse values.
// Practical values heavily depend on CKSEL fuse settings and crystal frequency,
// so having a clue about crystal frequency (-rq Mhz) would be helpful
e: puts(q);
  }
 }
}

// Output fuse bytes and calibration byte
static void output_fuse(byte fRead) {
 MESS("\n");
 for (int i=0; i<DevInfo.Fuses; i++) if (fRead&0x10<<i) put_fuseval(i);
/* Output calibration values */
 const FUSEPROP&f=FuseProps[Device->fusepropidx];
 if (fRead&0x80) if (f.Cals) {
  fputs("Cal:", stdout);
  for (int n=0; n<f.Cals; n++)
   printf(" %d", CaliBuf[n]);
  putchar('\n');
 }
}

// Store bytes into data buffer
// addr = Physical Memory Address (linearized)
void store_buffer(const byte*dat,dword count,dword addr) {
 if (addr>=BASE_FLASH && addr+count<=BASE_FLASH+MAX_FLASH) {
  addr-=BASE_FLASH;
  memcpy(CodeBuff+addr,dat,count);
  addr+=count;
  if (Cmd.CodeSize<addr) Cmd.CodeSize=addr;
  return;
 }
 if (addr>=BASE_EEPROM && addr+count<=BASE_EEPROM+MAX_EEPROM) {
  addr-=BASE_EEPROM;
  memcpy(DataBuff+addr,dat,count);
  addr+=count;
  if (Cmd.DataSize<addr) Cmd.DataSize=addr;
  return;
 }
 if (addr>=BASE_SIGNATURE && addr+count<=BASE_SIGNATURE+MAX_SIGNATURE) {
  addr-=BASE_SIGNATURE;
  for(;count;addr++,count--) Cmd.Sign[2-addr]=dat[addr];
  // WinAVR aka avr-gcc signature array is reversed, for unknown reason
  Cmd.fOpt|=0x10;
  return;
 }
 if (!(Cmd.fWrFuse&0x80)) return;	// load from file?

 if (addr>=BASE_LOCK && addr+count<=BASE_LOCK+MAX_LOCK) {
  addr=3;
  goto locklikefuse;
 }
 if (addr>=BASE_FUSE && addr+count<=BASE_FUSE+MAX_FUSE) {
  addr-=BASE_FUSE;
locklikefuse:
  memcpy(Cmd.Fuse+addr,dat,count);
  Cmd.fWrFuse|=((1<<count)-1)<<addr;
  return;
 }
}
// Single-byte version
void store_buffer(byte b,dword a) {store_buffer(&b,1,a);}


// Either load ELF or HEX file, return error line number
static int32 loadfile(const byte*fdata,int32 fsize) {
 if (loadelf(fdata,fsize)) {
  Deviceinfo di;
  if (di.load(fdata,fsize)) {
   di.check();
  }
  return 0;
 }
 return loadhex((const char*)fdata,fsize);
}

// Put an Intel Hex data block to file
static void put_hexline (
 const byte*buffer,	// pointer to data buffer
 word ofs,		// block offset address
 byte count,		// data byte count
 byte type=0		// block type
) {
 printf(":%02X%04X%02X",count,ofs,type);	// Byte count, Offset address and Record type
 byte sum = count + (ofs >> 8) + ofs + type;
 for (byte b=0;b<count;b++) {	// Data bytes
  printf("%02X",*buffer);
  sum += *buffer++;
 }
 printf("%02X\n",(byte)-sum);	// Check sum
}

static byte memAnd(const byte*buf,int len,byte start=0xFF) {
 if (len) do start&=*buf++; while(--len);
 return start;
}
static int findFirstProgrammed(const byte*buf,int len) {
 int i; for(i=0;i<len;i++) if (buf[i]!=0xFF) break; return i;
}
// Returns index beyond last non-0xFF byte in buffer.
static int findLastProgrammed(const byte*buf,int len) {
 while (--len>=0) if (buf[len]!=0xFF) break;
 return ++len;
}

// Output data buffer in Intel Hex format to file
static void put_hex(
 const byte*buf,	// pointer to data buffer
 int siz,		// number of bytes to be output
 int adr=0		// start address (32 bit)
) {
 int len = findLastProgrammed(buf,siz);
 while (len) {
  if (adr && !(adr&0xFFFF)) {	// New 64K segment?
   byte data[2]={0,byte(adr>>16)};	// 0x10000 will be "0001"
   put_hexline(data,0,2,4);
  }
  int l=linebytes;
  if (l>len) l=len;		// full or possibly partial data block
  if ((adr+l-1^adr)&0xFFFF0000) l=-adr&0xFFFF;	// don't straddle 64K boundary
  if (memAnd(buf,l)!=0xFF) put_hexline(buf,adr,l);	// Don't emit erased memory
  buf+=l;
  adr+=l;
  len-=l;
 }
}

// Display Hex/ASCII dump data line to console, colored
static void output_hex (
 const byte*buffer,	// pointer to data buffer
 int adr,		// address to show
 int count,		// data byte count, must be even when wordwise set
 int alen,		// address field width
 bool wordwise
) {
 cyan();		// Address
 printf("%0*X",alen,adr);
 green();		// Hex Dump
 for (int b=0;b<count;b+=1+wordwise) {
  putchar(' ');
  if (wordwise) printf("%04X",*(word*)(buffer+b));
  else printf("%02X",buffer[b]);
 }
 int spaces=2+(wordwise?((linebytes-count)>>1)*5:(linebytes-count)*3);
 if (spaces<1) spaces=1;
 do putchar(' '); while (--spaces);
 yellow();		// ASCII dump (no UTF-8 detection yet)
 for (int i=0;i<count;i++) {
  byte c=*buffer++;
  putchar(c>=' '&&c<0x7F?c:'.');
 }
 normal();
 putchar('\n');
}

// Display (Flash or) EEPROM data to console, colored
static void output_data(
 const byte*buf,	// pointer to data buffer
 int siz,		// number of bytes to be output
 bool wordwise=false	// normally in bytes
) {
 int len = findLastProgrammed(buf,siz);
 if (wordwise && len&1) len++;	// round up length to be even
 int adr = findFirstProgrammed(buf,len);
 adr&=~(linebytes-1);	// round down to start on even start address
 if (!len) {puts("== Empty =="); return;}
 len-=adr; buf+=adr;
 printf("== Fill: %d %% ==\n",len*100/siz);
// Calculate space needed for address column, based on device's size, not fill
 int alen=0; for(--siz;siz;siz>>=4) alen++;
 while (len) {
  int l=linebytes;
  if (l>len) l=len;		// full or possibly partial data block
  output_hex(buf,adr,l,alen,wordwise);
  buf+=l;
  adr+=l;
  len-=l;
 }
}

static void output_code(const byte*buf,int siz) {
 output_data(buf,siz,true);	// later: disassembly!
}

static bool initBuffers() {
 if (!CodeBuff) {
  CodeBuff=new byte[MAX_FLASH];
  if (!CodeBuff) {
   fprintf(stderr,"memory allocation (%d) failed.\n",MAX_FLASH);
   return false;
  }
  memset(CodeBuff,0xFF,MAX_FLASH);
 }
 if (!DataBuff) {
  DataBuff=new byte[MAX_EEPROM];
  if (!DataBuff) {
   fprintf(stderr,"memory allocation (%d) failed.\n",MAX_EEPROM);
   return false;
  }
  memset(DataBuff,0xFF,MAX_EEPROM);
 }
 return true;
}

/*-----------------------------------------------------------------------
  Device control functions
-----------------------------------------------------------------------*/

// get address of memory region in von-neumannized TPI address space
static word nvmAddress(adsp src, word addr) {
		//	FLASH	EEPROM	FUSE	LOCK	SIGNAT	CALIBS	
 static word offsets[]={0x4000, 0x2000,	0x3F40,	0x3F00,	0x3FC0,	0x3F80};
 if (src==FUSE && addr==3) {src=LOCK; addr=0;}
 return addr+offsets[src&7];
}

// set address for TPI devices
static bool nvmSetAddress(word lin) {
 return tpiSend(0x68,LOBYTE(lin))	// SSTPRL, Send Store Pointer Low
     && tpiSend(0x69,HIBYTE(lin));	// SSTPRH, Send Store Pointer High
}

// Read a byte from device
// !! Data is expected to be read consecutive, starting from address 0
static byte read_byte (adsp src,	// read from.. FLASH/EEPROM/SIGNATURE/CALIBS/FUSE
			dword adr)	// byte address
{
 byte s;

 switch (CtrlPort.Mode) {
  case 0: switch (src) {	// Parallel Mode
   case FLASH :
   if (adr & 1) return rcv_byte(BS_1);
   adr >>= 1;
   if (!adr) set_byte(XA_1, C_RD_PRG);
   if (!(adr&0xFFFF) && FlashSize(Device)>128*1024) set_byte(BS_2, (byte)(adr >> 16));
   if (!LOBYTE(adr)) set_byte(BS_1,HIBYTE(adr));
   set_byte(0, (byte)adr);
   return rcv_byte(0);

   case EEPROM :
   if (!adr) set_byte(XA_1, C_RD_EEP);
   if (!(adr&0xFF)) set_byte(BS_1, (byte)(adr >> 8));
   set_byte(0, (byte)adr);
   return rcv_byte(0);

   case SIGNATURE :
   set_byte(XA_1, C_RD_SIG);
   set_byte(0, (byte)adr);
   return rcv_byte(0);

   case CALIBS :
   set_byte(XA_1, C_RD_SIG);
   set_byte(0, (byte)adr);
   return rcv_byte(BS_1);

   case FUSE :
   set_byte(XA_1, C_RD_FB);
   switch (adr) {
    case 2 : s = XA_1 | BS_2; break;
    case 1 : s = XA_1 | BS_2 | BS_1; break;
    default : s = DevInfo.Fuses ? 0 : BS_1;
   }
   return rcv_byte(s);
  }break;
  case 1:
  case 2: switch (src) {	// HVS Mode
   case FLASH :
   if (!adr) xfer8(I_LDCMD, C_RD_PRG);
   if (adr&1) {
    xfer8(I_RDLH1, 0);
    return xfer8(I_RDLH2, 0);
   }
   if (!(adr&0x1FF)) xfer8(I_LDAH, (byte)(adr >> 9));
   xfer8(I_LDAL, (byte)(adr >> 1));
   xfer8(I_RDLL1, 0);
   return xfer8(I_RDLL2, 0);

   case EEPROM :
   if (!adr) xfer8(I_LDCMD, C_RD_EEP);
   if (!(adr&0xFF)) xfer8(I_LDAH, (byte)(adr >> 8));
   xfer8(I_LDAL, (byte)adr);
   xfer8(I_RDLL1, 0);
   return xfer8(I_RDLL2, 0);

   case SIGNATURE :
   xfer8(I_LDCMD, C_RD_SIG);
   xfer8(I_LDAL, (byte)adr);
   xfer8(I_RDLL1, 0);
   return xfer8(I_RDLL2, 0);

   case CALIBS :
   xfer8(I_LDCMD, C_RD_SIG);
   xfer8(I_LDAL, (byte)adr);
   xfer8(I_RDLH1, 0);
   return xfer8(I_RDLH2, 0);

   case FUSE :
   xfer8(I_LDCMD, C_RD_FB);
   switch (adr) {
    case 2 :	/* Extended */
    xfer8(I_RDHL1, 0);
    return xfer8(I_RDHL2, 0);
    case 1 :	/* High */
    xfer8(I_RDHH1, 0);
    return xfer8(I_RDHH2, 0);
    default :	/* Low */
    if (DevInfo.Fuses) {
     xfer8(I_RDLL1, 0);
     return xfer8(I_RDLL2, 0);
    }
    xfer8(I_RDLH1, 0);
    return xfer8(I_RDLH2, 0);
   }
  }break;
  case 3: {
   word lin=nvmAddress(src,word(adr));
   if (!adr) nvmSetAddress(lin);
   return tpiRecv(0x24);	// SLD+, Send Load and Increment (Pointer)
  }break;
 }
 return 0xFF;
}

// Write a byte into memory, for non-paged memory areas
// !! Data is expected to be written consecutive, starting from address 0
static int write_byte (
 adsp dst,	// write to.. FLASH/EEPROM
 dword adr,	// byte address
 byte wd	// data to be written
) {
 switch (CtrlPort.Mode) {
  case 0: switch (dst) {	/* Parallel Mode */
   case FLASH :
   if (!(adr&1)) {
    if (!adr) set_byte(XA_1, C_WR_PRG);
    if (!(adr&0x1FF)) set_byte(BS_1, (byte)(adr >> 9));
    set_byte(0, (byte)(adr >> 1));
   }
   if (wd == 0xFF) return 1;	/* Skip if the value is 0xFF */
   set_byte(XA_0, wd);
   stb_wr((byte)(adr & 1 ? BS_1 : 0), 0);
   break;

   case EEPROM :
   if (!adr) set_byte(XA_1, C_WR_EEP);
   if (!(adr&0xFF)) set_byte(BS_1, (byte)(adr >> 8));
   set_byte(0, (byte)adr);
   set_byte(XA_0, wd);
   stb_wr(0, 0);
   break;
  }break;
  case 1:
  case 2: switch (dst) {	/* HVS mode */
   case FLASH :
   if (!adr) xfer8(I_LDCMD, C_WR_PRG);
   if (!(adr&1)) {
    if (!(adr&0x1FF)) xfer8(I_LDAH, (byte)(adr >> 9));
    xfer8(I_LDAL, (byte)(adr >> 1));
    if (wd == 0xFF) return 1;	/* Skip if the value is 0xFF */
    xfer8(I_LDDL, wd);
    xfer8(I_WRLL1, 0);
    xfer8(I_WRLL2, 0);
   }else{
    if (wd == 0xFF) return 1;	/* Skip if the value is 0xFF */
    xfer8(I_LDDH, wd);
    xfer8(I_WRLH1, 0);
    xfer8(I_WRLH2, 0);
   }
   break;

   case EEPROM :
   if (!adr) xfer8(I_LDCMD, C_WR_EEP);
   if (!(adr&0xFF)) xfer8(I_LDAH, (byte)(adr >> 8));
   xfer8(I_LDAL, (byte)adr);
   xfer8(I_LDDL, wd);
   xfer8(I_WRLL1, 0);
   xfer8(I_WRLL2, 0);
   break;
  }break;
  case 3: {			// TPI mode
   word lin=nvmAddress(dst,word(adr));
   if (dst && !adr) {		// erase before write
    if (!tpiSend(0xF3,0x14)	// SOUT(1aa1aaaa) NVMCMD(0x33): Section Erase
     || !nvmSetAddress(lin+1)
     || !tpiSend(0x60,0)	// SST+, Send Store and Increment (Pointer)
     || !wait_ready())
    return false;
   }
   if (!(adr&1)) {
    tpiSend(0xF3,0x1D);	// SOUT(1aa1aaaa) NVMCMD(0x33): Word_Write
   }
   if (!adr) nvmSetAddress(lin);
   tpiSend(0x64,wd);	// SST+, Send Store and Increment (Pointer)
   if (!(adr&1)) return 1;	// don't wait for completion
  }break;
 }
 return wait_ready();	/* Wait for end of internal process */
}


// Write a page into memory
static int write_page (
 adsp dst,		/* write to.. FLASH/EEPROM */
 dword adr,		/* byte address (must be page boundary) */
 const byte *wd	// pointer to the page data
) {
 int n;
 switch (CtrlPort.Mode) {
  case 0: switch (dst) {	/* Parallel Mode */
   case FLASH: {
    byte d=0xFF;		/* Skip page if all data in the page are 0xFF */
    for (n=0; n<1<<Device->FlashPageS; n++) d&=wd[n];
    if (d == 0xFF) return 1;
    set_byte(XA_1, C_WR_PRG);
    for (n=0; n<1<<Device->FlashPageS; n+=2) {
     set_byte(0, (byte)((adr + n) >> 1));
     set_byte(XA_0, wd[n]);
     set_byte(XA_0 | BS_1, wd[n+1]);
     stb_pagel();
    }
    if (FlashSize(Device)>(128*1024)) set_byte(BS_2, (byte)(adr >> 17));
    set_byte(BS_1, (byte)(adr >> 9));
    stb_wr(0, 0);
   }break;

   case EEPROM: {
    set_byte(XA_1, C_WR_EEP);
    int e = 1<<Device->EepromPageS;
    for (n = 0; n < e; n++) {
     set_byte(0, (byte)(adr + n));
     set_byte(XA_0, wd[n]);
     stb_pagel();
    }
    set_byte(BS_1, (byte)(adr >> 8));
    stb_wr(0, 0);
   }break;
  }break;
  case 1:
  case 2: switch (dst) {		/* HVS mode */
   case FLASH: {
    byte d=0xFF;		/* Skip page if all data in the page are 0xFF */
    int e = 1<<Device->FlashPageS;
    for (n=0; n < e; n++) d&=wd[n];
    if (d == 0xFF) return 1;
    xfer8(I_LDCMD, C_WR_PRG);
    for (n=0; n < e; n+=2) {
     xfer8(I_LDAL, (byte)((adr + n) >> 1));
     xfer8(I_LDDL, wd[n]);
     xfer8(I_LDDH, wd[n+1]);
     xfer8(I_PSTH1, 0);
     xfer8(I_PSTH2, 0);
    }
    xfer8(I_LDAH, (byte)(adr >> 9));
    xfer8(I_WRLL1, 0);
    xfer8(I_WRLL2, 0);
   }break;

   case EEPROM: {
    xfer8(I_LDCMD, C_WR_EEP);
    int e = 1<<Device->EepromPageS;
    for (n=0; n < e; n++) {
     xfer8(I_LDAL, (byte)(adr + n));
     xfer8(I_LDDL, wd[n]);
     xfer8(I_PSTL1, 0);
     xfer8(I_PSTL2, 0);
    }
    xfer8(I_LDAH, (byte)(adr >> 8));
    xfer8(I_WRLL1, 0);
    xfer8(I_WRLL2, 0);
   }break;
  }break;
 }
 return wait_ready();	/* Wait for end of internal process */
}


/* Write Fuse or Lock byte */

static int write_fuselock (
 int dst,	/* write to... F_LOCK/F_LOW/F_HIGH/F_EXTEND */
 byte val	/* byte value to be written */
) {
 const FUSEPROP&f=FuseProps[Device->fusepropidx];
 switch (CtrlPort.Mode) {
  case 0:	/* Parallel Mode */
  if (dst==3) {	// Device Lock byte
   set_byte(XA_1, C_WR_LB);
   set_byte(XA_0, val);
   stb_wr(0, 0);
  }else{
   set_byte(XA_1, C_WR_FB);
   set_byte(XA_0, val);
   switch (dst) {
    case 0:	// Fuse Low byte
    stb_wr(0, f.FuseWait);
    break;
    case 1:	// Fuse High byte
    stb_wr(BS_1, f.FuseWait);
    break;
    case 2:	// Fuse Extend byte
    stb_wr(XA_1 | BS_2, f.FuseWait);
   }
  }break;
  case 1:
  case 2:			/* HVS mode */
  if (dst==3) {	// Device Lock byte
   xfer8(I_LDCMD, C_WR_LB);
   xfer8(I_LDDL, val);
   xfer8(I_WRLL1, 0);
   xfer8(I_WRLL2, 0);
  }else{
   xfer8(I_LDCMD, C_WR_FB);
   xfer8(I_LDDL, val);
   switch (dst) {
    case 0:	// Fuse Low byte
    xfer8(I_WRLL1, 0);
    delay_ms(f.FuseWait);
    xfer8(I_WRLL2, 0);
    break;
    case 1:	// Fuse High byte
    xfer8(I_WRLH1, 0);
    xfer8(I_WRLH2, 0);
    break;
    case 2:	// Fuse Extended byte
    xfer8(I_WRHL1, 0);
    xfer8(I_WRHL2, 0);
    break;
   }
  }break;
  case 3:{
   if (dst==3) {
    write_byte(LOCK,0,val);
    return write_byte(LOCK,1,0xFF);
   }else{
    write_byte(FUSE,0,val);
    return write_byte(FUSE,1,0xFF);
   }
  }break;
 }
 return wait_ready();	/* Wait for end of internal process */
}

// Chip erase
static int erase_memory() {
 const FUSEPROP&f=FuseProps[Device->fusepropidx];
 switch (CtrlPort.Mode) {
  case 0: {		/* Parallel Mode */
   set_byte(XA_1, C_ERASE);
   stb_wr(0, f.EraseWait);
  }break;
  case 1:
  case 2: {			/* HVS mode */
   xfer8(I_LDCMD, C_ERASE);
   xfer8(I_WRLL1, 0);
   xfer8(I_WRLL2, 0);
   delay_ms(f.EraseWait);
   if (!Device->EepromPageS) xfer8(I_LDCMD, C_NOP);
  }break;
  case 3: {
   tpiSend(0xF3,0x10);	// SOUT(1aa1aaaa) NVMCMD(0x33): Chip_Erase
   nvmSetAddress(nvmAddress(FLASH,1));
   tpiSend(0x60,0xFF);	// SST, Send Store (Pointer): Dummy Write
  }break;
 }
 return wait_ready();
}

/* Initialize control port */
static bool initialize_port() {
// Open interface port and check status
 switch (open_ifport()) {	// Main result code (error status)
  case RES_DRVFAIL:
#ifdef WIN32
  MESS(CtrlPort.inpout32?"INPOUT32":"GIVEIO");
  MESS(" initialization failed.\n");
  if (!CtrlPort.inpout32) MESS ("Try -i switch using InpOut32.dll!\n");
#else
  MESS("ioperm() failed. Use 'sudo' to access hardware!\n");
#endif
  break;
  case RES_BADENV:
  MESS("Unsupported environment (mostly too old hardware or operating system).\n");
  break;
  case RES_NOPORT:
  fprintf(stderr, "No LPT port at 0x%X.\n",CtrlPort.PortAddr);
#ifdef WIN32
  MESS("Refer to Device Manager (run 'devmgmt.msc') for correct port address!\n");
#endif
  break;
  case RES_NOADAPTER:
  fprintf(stderr, "Programmer is not attached on LPT port 0x%X.\n", CtrlPort.PortAddr);
  break;
  case RES_OPENED:
  return true;
 }
 return false;
}

#ifndef WIN32
enum querytype {MB_OK,MB_OKCANCEL,MB_ABORTRETRYIGNORE,MB_YESNOCANCEL,MB_YESNO,MB_RETRYCANCEL,
		MB_ICONHAND=0x10,MB_ICONQUESTION=0x20,MB_ICONEXCLAMATION=0x30,MB_ICONASTERISK=0x40};
enum queryresult {IDNULL,IDOK,IDCANCEL,IDABORT,IDRETRY,IDIGNORE,IDYES,IDNO};
#endif

// Normally, let the user hit ENTER.
// However, this isn't possible for most IDEs (Notepad++, Programmers Notepad) output window.
// Therefore, if stdin is not connected to console, use MessageBox()
static int query(const char*msg,int type) {
#ifdef WIN32
 if (outredir) {
  return MessageBox(0,msg,"avrpp",type);
 }
#endif
 MESS(msg);
 switch (int c=getchar()) {
  case '\r':
  case '\n': return IDOK;
  case 'n': return IDNO;
  case 'y': return IDYES;
  case 'i': return IDIGNORE;
  case 'r': return IDRETRY;
  case 'a': return IDABORT;
  case 27: return IDCANCEL;
  default: return c;
 }
}

static const DEVPROP*findSignature(byte sign[3]) {
 if (sign[0]!=0x1E) return 0;	// Must be ATMEL
 for (const DEVPROP*dev=DevLst;dev->Name[0];dev++)
   if (!memcmp(sign+1,dev->Sign,2)) return dev;
 return 0;
}

// Initialize control port and detect device type
static int init_devices() {
 dword adr;
 const char *const DetMode[] = {"PAR","HVS","HVS15","TPI"};

	/* Execute initialization if not initialized yet */
 if (Device) return 0;
 if (!initialize_port()) return RC_INIT;

 const char*name="a device";	// Give a hint that the desired chip name is already known,
 const DEVPROP*dev=findSignature(Cmd.Sign); // either by given signature in ELF/HEX file,
 if (dev) name=dev->Name;	// or by .note.gnu.avr.deviceinfo section in ELF file
 char buf[128];
 sprintf(buf,"Put %s%s on the socket and type Enter...",dev?"AT":"",name);
 if (query(buf,MB_OK|MB_ICONASTERISK)!=IDOK) return RC_FAIL;

 for (; CtrlPort.Mode<4; CtrlPort.Mode++) {
  power_on();
  // read device signature
  for (adr=0; adr<3; adr++) SignBuf[adr] = read_byte(SIGNATURE, adr);
  // search device table
  if (Device=findSignature(SignBuf)) goto found;
  power_off();
  fprintf(stderr, "%s->Unknown device (%02X-%02X-%02X).\n",
    DetMode[CtrlPort.Mode],SignBuf[0],SignBuf[1],SignBuf[2]);
  delay_ms(50);
 }
 return RC_DEV;	// Failed to detect device type
found:	// Show the device name
 fprintf(stderr, "%s->Detected device is AT%s.\n",
   DetMode[CtrlPort.Mode],Device->Name);
// Fill DevInfo structure with information
 const FUSEPROP&f = FuseProps[Device->fusepropidx];
 for (int i=0; i<24; i++) if (f.ids[i]) {
  DevInfo.FuseMask[i>>3]|=1<<(i&7);
  DevInfo.Fuses=(i>>3)+1;
 }
 return 0;
}


/* Read fuse bytes and calibration bytes into buffer */
static void read_fusecal(byte fRead) {
 for (int i=0;i<DevInfo.Fuses;i++)
   if (fRead&0x10<<i) FuseBuf[i]=read_byte(FUSE,i);
 const FUSEPROP&f = FuseProps[Device->fusepropidx];
 if (fRead&0x80) for (int a=0;a<f.Cals;a++)
   CaliBuf[a]=read_byte(CALIBS,a);
}


/*-----------------------------------------------------------------------
  Programming functions
-----------------------------------------------------------------------*/
// Get bit number for EESAVE bit for this device
static int find_eesave(byte id=3) {	// ID=3 is EESAVE bit
 const FUSEPROP&f = FuseProps[Device->fusepropidx];
 const byte*p=f.ids;
 const byte*q=(const byte*)memchr(p,id,3<<3);
 if (q) return int(q-p);	// This AVR has EESAVE bit there
 return -1;
}

static int erase_avr() {
// Check the need for erasing EEPROM
// 1. If any fuse given, reset EESAVE fuse first
 if (Cmd.fWrFuse&0x07) { // Fuse given either by command line or by -ff and valid file data
  int bit=find_eesave();
  if (bit>=0) {
   int byt=bit>>3; bit&=7;
   read_fusecal(0x10<<byt);	// Read only the one needed fuse byte
   if (!(FuseBuf[byt]&1<<bit)) {	// EESAVE programmed (= zero)
    MESS("Reset EESAVE...");
    FuseBuf[byt]|=1<<bit;
    write_fuselock(byt,FuseBuf[byt]);
   }
  }
 }
 MESS("Erase...");		// Erase device before programming
 return erase_memory();
}

// -e command
static int erase_device() {
 int rc;

 if (rc=init_devices()) return rc;

 if (!erase_avr()) {
  MESS("Failed.\n");
  return RC_FAIL;
 }
 MESS("Erased.\n");
 return 0;
}


// -r command
static int read_device(byte fRead) {
 dword adr,siz;
 int rc;

 if (rc=init_devices()) return rc;

 if (fRead&8 && !outredir) output_deviceinfo();
 if (fRead&1) {	// -rp : read program memory
  siz=FlashSize(Device);
  MESS("Read Flash...");
  if (!CodeBuff) CodeBuff=new byte[siz];
  for (adr = 0; adr < siz; adr++)
    CodeBuff[adr] = read_byte(FLASH, adr);
  MESS("Okay.\n");
  if (outredir) put_hex(CodeBuff,siz);
  else output_code(CodeBuff,siz);
 }
 if (fRead&2) {	// -re : read eeprom
  siz=1U<<Device->EepromSizeS;
  if (siz==1) {
   MESS("No EEPROM.\n");
  }else{
   MESS("Read EEPROM...");
   if (!DataBuff) DataBuff=new byte[siz];
   for (adr = 0; adr < siz; adr++)
     DataBuff[adr] = read_byte(EEPROM, adr);
   MESS("Okay.\n");
   if (outredir) put_hex(DataBuff,siz,fRead==2?0:0x810000);	// Don't emit address for single section
   else output_data(DataBuff,siz);
  }
 }
 if (fRead&0xF0) {	// -rf : read fuses and cals
  read_fusecal(fRead);
  if (outredir) put_hex(FuseBuf,DevInfo.Fuses,0x820000);
  else output_fuse(fRead);
 }
 if (outredir) {
  if (fRead&8) {
   byte data[]={Device->Sign[1],Device->Sign[0],0x1E};
   put_hex(data,3,0x840000);
  }
  put_hexline(NULL,0,0,1);	// End block
 }
 return 0;
}

static bool check_signature() {
 if (Cmd.fOpt&0x10	// HEX/ELF file didn't contain signature (old style): Don't check
 && memcmp(Cmd.Sign,SignBuf,3)) {	// File Signature matches Device Signature: Okay
  char buf[128];
  sprintf(buf,"File signature (AT%s) doesn't match to device signature (AT%s)! Continue?",
    findSignature(Cmd.Sign)->Name,
    findSignature(SignBuf)->Name);
  return query(buf,MB_YESNO|MB_ICONQUESTION)==IDYES;
 }else return true;
}

// TODO: Memory sizes will not be checked for now.
bool Deviceinfo::check() {
 if (Cmd.fOpt&0x10) {	// ELF file contains signature? Compare!
  const char*a=findSignature(Cmd.Sign)->Name;
  if (!strcasecmp(a,device_name)) return true;	// good
  char buf[128];
  sprintf(buf,"File signature (AT%s) doesn't match to File DeviceInfo section (AT%s)!",
    a,device_name);
  return query(buf,MB_OK|MB_ICONEXCLAMATION),false;
 }else{			// ELF file doesn't contain signature: Take from table
  for (const DEVPROP*dev=DevLst;dev->Name[0];dev++) {
   if (!strcasecmp(dev->Name,device_name)) {	// name found
    Cmd.fOpt|=0x10;
    Cmd.Sign[0]=0x1E;		// Atmel
    Cmd.Sign[1]=dev->Sign[0];
    Cmd.Sign[2]=dev->Sign[1];
    return true;
   }
  }
  char buf[128];
  sprintf(buf,"File DeviceInfo section contains unknown chip name (AT%s)!",
    device_name);
  return query(buf,MB_OK|MB_ICONEXCLAMATION),false;
 }
}

/* .hex files write command */
static int write_flash() {
 dword adr;
 byte rd;
 int rc, n;
 const FUSEPROP&f = FuseProps[Device->fusepropidx];

 if (rc = init_devices()) return rc;
 MESS("Flash: ");

 if (Cmd.CodeSize > FlashSize(Device)) {
  MESS("error: program size > memory size.\n");
  return RC_FAIL;
 }
 Cmd.CodeSize = FlashSize(Device);

 if (!check_signature()) return RC_FAIL;

 if (!(Cmd.fOpt&0x02)) {	// -v : Skip programming process when verify mode

  if (!erase_avr()) {
   MESS("Failed.\n");
   return RC_FAIL;
  }

  if (Cmd.fOpt&0x04 && f.Cals) {	// -c : Copy calibration bytes
   read_fusecal(0x80);
   for (n = 0; n < f.Cals; n++)
     CodeBuff[FlashSize(Device)-1-n] = CaliBuf[n];
   Cmd.CodeSize=FlashSize(Device);
  }
  MESS("Write...");
  if (Device->FlashPageS) {		/* Write flash in page mode */
   for (adr = 0; adr < Cmd.CodeSize; adr += 1<<Device->FlashPageS) {
    if (!write_page(FLASH, adr, &CodeBuff[adr])) {
     MESS("Failed.\n");
     return RC_FAIL;
    }
   }
  }else{					/* Write flash in byte-by-byte mode */
   for (adr = 0; adr < Cmd.CodeSize; adr++) {
    if (!write_byte(FLASH, adr, CodeBuff[adr])) {
     MESS("Failed.\n");
     return RC_FAIL;
    }
   }
  }
 }
 MESS("Verify...");
 for (adr = 0; adr < Cmd.CodeSize; adr++) {
  rd = read_byte(FLASH, adr);
  if (rd != CodeBuff[adr]) {
   fprintf(stderr, "Failed at %04X:%02X-%02X\n", adr, CodeBuff[adr], rd);
   return RC_FAIL;
  }
 }
 MESS("Okay.\n");
 return 0;
}

// .eep files write command
static int write_eeprom() {
 dword adr;
 byte rd;
 int rc;

 if (rc = init_devices()) return rc;
 if (!Device->EepromSizeS) return 0;
 MESS("EEPROM: ");
// Check whether EESAVE fuse is set
 int bit=find_eesave();
 if (bit>=0) {	// This AVR has EESAVE bit, get it
  int byt=bit>>3; bit&=7;
  read_fusecal(0x10<<byt);	// Read only the one needed fuse byte
  if (!(FuseBuf[byt]>>bit&1)) {	// EESAVE programmed (= zero)
// There are at least four solutions:
// 1. Ignore and end up in verify error except no bit changes from 0 to 1
// 2. Skip overwriting EEPROM (no error)
// 3. Bail out with error
// 4. Erase chip with EESAVE cleared beforehand
   MESS("Skip programming as EESAVE fuse is programmed.\n");
   return 0;
  }
 }

 if (Cmd.DataSize > 1U<<Device->EepromSizeS) {
  MESS("error: data size > memory size.\n");
  return RC_FAIL;
 }

 if (!(Cmd.fOpt&0x02)) {	// -v : Skip programming process when verify mode
  MESS("Write...");
  if (Device->EepromPageS) {	/* Write flash in page mode */
   for (adr = 0; adr < Cmd.DataSize; adr += 1<<Device->EepromPageS) {
    if (!write_page(EEPROM, adr, DataBuff+adr)) {
     MESS("Failed.\n");
     return RC_FAIL;
    }
   }
  }else{			/* Write flash in byte-by-byte mode */
   for (adr = 0; adr < Cmd.DataSize; adr++) {
    if (!write_byte(EEPROM, adr, DataBuff[adr])) {
     MESS("Failed.\n");
     return RC_FAIL;
    }
   }
  }
 }
 MESS("Verify...");
 for (adr = 0; adr < Cmd.DataSize; adr++) {
  rd = read_byte(EEPROM, adr);
  if (rd != DataBuff[adr]) {
   fprintf(stderr, "Failed at %04X:%02X-%02X\n", adr, DataBuff[adr], rd);
   return RC_FAIL;
  }
 }
 MESS("Okay.\n");
 return 0;
}


/* -f{l|h|x}, -l command */
static int write_fuse() {
 int rc=init_devices();
 if (rc) return rc;
 const FUSEPROP&f = FuseProps[Device->fusepropidx];
 for (int i=0; i<DevInfo.Fuses; i++) if (Cmd.fWrFuse&1<<i) {
  MESS("Fuse(");
  fputc("lhx"[i],stderr);
  MESS("): ");
  if (Cmd.fWrFuse&0x10<<i) Cmd.Fuse[i] = f.FuseDefault[i];
  if (!(Cmd.fOpt&0x02)) {
   MESS("Write...");
   write_fuselock(i,(byte)(Cmd.Fuse[i] | ~DevInfo.FuseMask[i]));
  }
  MESS("Verify...");
  byte d = read_byte(FUSE,i);
  if ((d ^ Cmd.Fuse[i]) & DevInfo.FuseMask[i]) {
   MESS("Failed.\n");
   return RC_FAIL;
  }
  MESS("Okay.\n");
 }
 if (Cmd.fWrFuse&8 && !(Cmd.fOpt&0x02)) {
  MESS("Lock byte: Write...");
  write_fuselock(3,Cmd.Fuse[3] ? Cmd.Fuse[3] : f.LockDefault);
  MESS("Okay.\n");
 }
 return 0;
}

/*---------------------
  Command line analysis
  ---------------------*/
static dword mystrtoul(const char*&s,byte radix=0) {
 switch (*s) {
  case '#': radix=10; ++s; break;
  case '$': radix=16; ++s; break;
 }
 return strtoul(s,const_cast<char**>(&s),radix);
}

static byte strtobyte(const char*&s, byte radix, byte&chars) {
 char buf[4];
 *(dword*)buf=*(dword*)s;
 buf[chars]=0;	// chars must be less than 4
 char*e;
 byte b=(byte)strtoul(buf,&e,radix);
 chars=byte(e-buf);
 s+=chars;
 return b;
}

static int process_argv(const char*cp) {
 if (*cp=='-') {	// Command switches...
  cp++;
  switch (tolower(*cp++)) {
   case 'e': Cmd.fOpt|=0x01; break;	// -e
   case 'v': Cmd.fOpt|=0x02; break;	// -v
   case 'c': Cmd.fOpt|=0x04; break;	// -c

   case 'r' :	/* -r[peiflhxc]+ */
   if (!*cp) Cmd.fRead = 0xFF;	// read all by default
   else while (*cp) switch (tolower(*cp++)) {
    case 'p': Cmd.fRead|=0x01; break;
    case 'e': Cmd.fRead|=0x02; break;
    case 'i': Cmd.fRead|=0x08; break;	// -ri (info only)
    case 'l': Cmd.fRead|=0x10; break;
    case 'h': Cmd.fRead|=0x20; break;
    case 'x': Cmd.fRead|=0x40; break;
    case 'c': Cmd.fRead|=0x80; break;
    case 'f': Cmd.fRead|=0xF0; break;
    default: return RC_SYNTAX;
   }break;

   case 'f': {	/* -f{l|h|x}[<bin>] */
    int i=-1;
    switch (tolower(*cp++)) {
     case '=': Cmd.crystal_MHz=mystrtoul(cp); break;
     case 'f': Cmd.fWrFuse|=0x80; break;	// -ff = take fuses from file
     case 'l': i=0; break;			// -fl[<bin>] = define low fuse
     case 'h': i=1; break;			// -fh[<bin>] = define high fuse
     case 'x': i=2; break;			// -fx[<bin>] = define extended fuse
     case 0: --cp;
     case 'd': Cmd.fWrFuse|=0x77; break;	// -fd = set all fuses to default
     default: return RC_SYNTAX;
    }
    if (i>=0) {
     Cmd.fWrFuse|=(*cp?1:0x11)<<i;
     if (*cp) {
      Cmd.Fuse[i]=(byte)mystrtoul(cp,2);
     }
    }
   }break;

   case 'l': {	/* -l[<bin>] */
    Cmd.fWrFuse|=0x08;
    Cmd.Fuse[3] = (byte)mystrtoul(cp,2);
   }break;

   case 'p': {	/* -p<hex> */
    unsigned ln=mystrtoul(cp,16);
    if ((ln >= 1 && ln <= 3) || ln >= 0x100)
      CtrlPort.PortAddr=(word)ln;
    else return RC_SYNTAX;
   }break;

   case 'i': if (*cp=='=') {cp++; goto num;}
   if ('1'<=*cp && *cp<='9') {	// -i100 = microseconds between every OUT command
num:unsigned r=mystrtoul(cp);
    if (r) CtrlPort.iodelay=r;
    else return RC_SYNTAX;
   }else CtrlPort.inpout32=true; break;
   case 'h': Cmd.fHelp|=1; break;
   case 'm': Cmd.fHelp|=3; break;
   case 'd': Cmd.fHelp|=4; break;

   case 'w': if (*cp) {			// -wf, -we, -wabsadr=databytes/string: Write flash/eeprom bytes
    if (!initBuffers()) return RC_FILE;
    if (Cmd.fRead) {
     int rc=init_devices();
     if (rc) return rc;
     dword adr,siz;
     if (Cmd.fRead&1) {	// -rp : modify program memory
      siz=FlashSize(Device);
      MESS("Read Flash...");
      for (adr = 0; adr < siz; adr++) CodeBuff[adr] = read_byte(FLASH, adr);
      MESS("Okay.\n");
     }
     if (Cmd.fRead&2) {// -re : modify eeprom
      siz=1U<<Device->EepromSizeS;
      MESS("Read EEPROM...");
      for (adr = 0; adr < siz; adr++) DataBuff[adr] = read_byte(EEPROM, adr);
      MESS("Okay.\n");
     }
     if (Cmd.fRead&0xF0) {	// -rf : read fuses and cals
      read_fusecal(Cmd.fRead);
     }
     Cmd.fRead=0;
    }
    dword a=BASE_FLASH;		// 0
    switch (*cp) {
     case 'e': a=BASE_EEPROM; nobreak;
     case 'f': ++cp; break;
    }
    a+=mystrtoul(cp,16);
    byte asciimode=0;
    bool straighthex=!strchr(cp,',');	// no delimiters means concatenated hex bytes
    for(;;) switch (char c=*cp++) {
     case 0: return 0;
     case '\'':
     case '"': if (asciimode==c) asciimode=0; else asciimode=c; nobreak;
     default: if (asciimode) {
      if (c=='\\') switch (c=*cp++) {
       case 0: return RC_SYNTAX;
       case 'r': c=10; break;
       case 'n': c=13; break;
       case 'a': c=7; break;
       case 'e': c=27; break;
       case 'b': c=8; break;
       case 't': c=9; break;
       case 'x': {byte chars=2; c=strtobyte(cp,16,chars); if (!chars) return RC_SYNTAX;} break;
       default: if ('0'<=c && c<='7') {byte chars=3; c=strtobyte(cp,8,chars);}
      }
      store_buffer(c,a++);
     }else switch (c) {
      case ',':
      case '=': break;		// ignore delimiters
      default: --cp;
      if (straighthex) {
       byte chars=2;
       c=strtobyte(cp,16,chars);
       if (!chars) return RC_SYNTAX;
       store_buffer(c,a++);
      }else{
       byte len=2;
       const char*savecp=cp;
       dword v=mystrtoul(cp);
       if (savecp==cp) return RC_SYNTAX;
       switch (_tolower(*cp)) {
        case 'l': len=4; cp++; break;
	case 'w': cp++; break;
	case 'b': len=1; cp++; break;
       }
       switch (*cp) {
        case 0: break;
	case ',':++cp; break;
	default: return RC_SYNTAX;
       }
       if (v>dword((1UL<<(len<<3))-1)) return RC_SYNTAX;	// too large for size suffix
       do{
        store_buffer((byte)v,a++);
	v>>=8;
       }while(--len);
      }
     }
    }
   }else Cmd.fOpt|=0x20; break;		// -w (pause before exit)


   case '8': CtrlPort.Mode=1; break;	// -8: detect device as 8-pin / 14-pin serial-only device
   case '5': CtrlPort.Mode=2; break;	// -5: detect device as tn15
   case '6': CtrlPort.Mode=3; break;	// -6: detect 6-pin ATtiny4,5,9,10 with Tiny Programming Interface TPI
   case 'q': CtrlPort.Quick=true; break;	// -q (quick power-up)
   case 's': Cmd.fOpt|=0x08; break;	// -s (require chip signature in HEX/ELF file)
   default: return RC_SYNTAX;	// invalid command
  }
  if (*cp) return RC_SYNTAX;	// option trails garbage
 }else{		// HEX/ELF File (Chip Write command)
  FILE *f=fopen(cp,"rb");	// TODO: UTF-8 file names (Windows)
  if (!f) {
   fprintf(stderr, "%s : Unable to open.\n", cp);
   return RC_FILE;
  }
  fseek(f,0,SEEK_END);
  int32 fsize=ftell(f);
  rewind(f);
  byte*fdata=new byte[fsize];
  if (!fdata) {
   fprintf(stderr,"memory allocation (%d) failed.\n",fsize);
   return RC_FILE;
  }
  if (fread(fdata,1,fsize,f)!=(size_t)fsize) {
   fprintf(stderr,"%s : File access failure.\n",cp);
   return RC_FILE;
  }
  fclose(f);
  if (!initBuffers()) return RC_FILE;
	// .eep files are read as EEPROM data, others are read as program code
  dword ln;
  if (strstr(cp,".EEP") || strstr(cp,".eep")) {
   ln=loadhex((const char*)fdata,fsize,BASE_EEPROM);
  }else{
   ln=loadfile(fdata,fsize);	// All other .hex or .elf files may contain multiple sections
  }
  free(fdata);
  if (ln) {
   fprintf(stderr, "%s (%d) : Hex format error.\n", cp, ln);
   return RC_FILE;
  }
 }
 return 0;
}

static int load_commands(char *argv[]) {
// Process INI file as command line parameters
 FILE*f=open_cfgfile(INIFILE);
 if (f) {
  char s[260];
  s[elemof(s)-1]=0;
  while (fgets(s,elemof(s)-1,f)) {
   char *e=s+strlen(s);
   while (e!=s) {
    if ((byte)*--e>0x20) break;
    *e=0;	// trim right (newline and spaces/tabs)
   }
   if (*s) break;	// halt on first empty line
   int ret;
   if (ret=process_argv(s)) return ret;
  }
  fclose(f);
 }
// Process command line parameters
 while (*++argv) {
  int ret;
  if (ret=process_argv(*argv)) return ret;
 }
 return 0;
}

/* Terminate process */
static void terminate() {
 close_ifport();
 Device = NULL;

 if (Cmd.fOpt&0x20) {	// Pause
  MESS("\n");
  query("Type Enter to exit!",MB_OK|MB_ICONASTERISK);
 }
}

int _cdecl main (int argc, char*argv[]) {
 outredir=!isatty(fileno(stdout));
#ifdef WIN32
 hStdOut=GetStdHandle(STD_OUTPUT_HANDLE);
 CONSOLE_SCREEN_BUFFER_INFO sbi;
 GetConsoleScreenBufferInfo(hStdOut,&sbi);
 termwidth=(byte)sbi.dwSize.X;
#else
 winsize ws;
 ioctl(fileno(stdout),TIOCGWINSZ,&ws);
 termwidth=(byte)ws.ws_col;
#endif
 if (!outredir && termwidth<118) linebytes=16;
 int rc=load_commands(argv);
 if (rc==RC_SYNTAX) Cmd.fHelp|=1;
 if (Cmd.fHelp&1) {
  MESS(MesUsage);
  if (!(Cmd.fHelp&2)) MESS(MesMore);
  if (!(Cmd.fHelp&4)) MESS(MesDevices);
 }
 if (Cmd.fHelp&2) MESS(MoreInfo);
 if (Cmd.fHelp&4) MESS(Devices);
 if (Cmd.fHelp) return rc;
 if (Cmd.fWrFuse&0x80 && !(Cmd.fWrFuse&0x0F))
   MESS("'-ff' given but file doesn't contain fuse definition!\n");
 if (Cmd.fOpt&0x08 && !(Cmd.fOpt&0x10))
   MESS("'-s' given but file doesn't contain chip signature!\n");
 if (Cmd.fOpt&0x10) {
// preselect right programming interface (to save time or command-line parameter)
  const DEVPROP*dev=findSignature(Cmd.Sign);
  if (!dev) {
   MESS("Chip signature in file is unknown!\n");
  }else{
   byte mode=0;
   const FUSEPROP&f = FuseProps[dev->fusepropidx];
   for(byte m=f.PgmType;!(m&1);mode++,m>>=1);
   CtrlPort.Mode=mode;
  }
 }
	// Read device and terminate if -r{p|e|f} command is specified
 if (Cmd.fRead) {
  rc = read_device(Cmd.fRead);
  terminate();
  return rc;
 }
	// Erase device if -e command is specified
 if (Cmd.fOpt&0x01) rc = erase_device();
	// Write to device if any file is loaded
 if (Cmd.CodeSize) {
  if (rc = write_flash()) {
   terminate();
   return rc;
  }
 }
 if (Cmd.DataSize) {
  if (rc = write_eeprom()) {
   terminate();
   return rc;
  }
 }
	// Write fuse,lock if -f{l|h|x}, -l are specified
 if (Cmd.fWrFuse&0x0F) {
  if (rc = write_fuse()) {
   terminate();
   return rc;
  }
 }

 if (!Device) MESS(MesUsage);
 terminate();
 return 0;
}

// Bisher war die 64-Bit-Version an msvcrt90.dll gebunden, was bei Windows 10 problematisch ist!
// Irrtum 2021 bemerkt und auf mainCRTStartup() zurckgeschwenkt
#ifdef _WIN64
#if 0
#pragma bss_seg(".CRT$XCA")
static void* Anfang;	// Keine Initialisierung
#pragma bss_seg(".CRT$XCZ")
static void* Ende;
#pragma bss_seg()	// zurckschalten zu ".bss"
extern "C" _CRTIMP void _cdecl _initterm(void*&,void*&);
#endif
extern "C" _CRTIMP int _cdecl __getmainargs(int&,char**&,char**&,int,int&);

void mainCRTStartup() {
#if 0
 _initterm(Anfang,Ende);
#endif
 int argc,newmode;
 char**argv,**envp;
 __getmainargs(argc,argv,envp,false,newmode);
 UINT e=main(argc,argv);
 ExitProcess(e);
}
#endif
Detected encoding: UTF-80