Files
avrdude/src/fileio.c
2025-10-22 22:40:42 +02:00

1875 lines
52 KiB
C

/*
* avrdude - A Downloader/Uploader for AVR device programmers
* Copyright (C) 2000-2004 Brian S. Dean <bsd@bdmicro.com>
* Copyright (C) 2023 Stefan Rueger <stefan.rueger@urclocks.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <ac_cfg.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <stdint.h>
#ifdef HAVE_LIBELF
#ifdef HAVE_LIBELF_H
#include <libelf.h>
#elif defined(HAVE_LIBELF_LIBELF_H)
#include <libelf/libelf.h>
#endif
#ifndef EM_AVR32
#define EM_AVR32 0x18ad // Unofficial
#endif
#ifndef EM_AVR
#define EM_AVR 83 // OpenBSD lacks it
#endif
#endif
#include "avrdude.h"
#include "libavrdude.h"
// Common internal record structure for ihex and srec files
struct ihexsrec {
unsigned char reclen;
unsigned int loadofs;
unsigned char rectyp;
unsigned char data[256];
unsigned char cksum;
};
char *fileio_fmtstr(FILEFMT format) {
switch(format) {
case FMT_AUTO:
return "auto-detect";
case FMT_SREC:
return "Motorola S-Record";
case FMT_IHEX:
return "Intel Hex";
case FMT_IHXC:
return "Intel Hex with comments";
case FMT_RBIN:
return "raw binary";
case FMT_ELF:
return "ELF";
case FMT_IMM:
return "in-place immediate";
case FMT_EEGG:
return "R byte list";
case FMT_BIN:
return "0b-binary byte list";
case FMT_DEC:
return "decimal byte list";
case FMT_HEX:
return "0x-hexadecimal byte list";
case FMT_OCT:
return "octal byte list";
default:
return "invalid format";
}
}
int fileio_fmtchr(FILEFMT format) {
switch(format) {
case FMT_AUTO:
return 'a';
case FMT_SREC:
return 's';
case FMT_IHEX:
return 'i';
case FMT_IHXC:
return 'I';
case FMT_RBIN:
return 'r';
case FMT_ELF:
return 'e';
case FMT_IMM:
return 'm';
case FMT_EEGG:
return 'R';
case FMT_BIN:
return 'b';
case FMT_DEC:
return 'd';
case FMT_HEX:
return 'h';
case FMT_OCT:
return 'o';
default:
return '?';
}
}
FILEFMT fileio_format(char c) {
switch(c) {
case 'a':
return FMT_AUTO;
case 's':
return FMT_SREC;
case 'i':
return FMT_IHEX;
case 'I':
return FMT_IHXC;
case 'r':
return FMT_RBIN;
case 'e':
return FMT_ELF;
case 'm':
return FMT_IMM;
case 'R':
return FMT_EEGG;
case 'b':
return FMT_BIN;
case 'd':
return FMT_DEC;
case 'h':
return FMT_HEX;
case 'o':
return FMT_OCT;
default:
return FMT_ERROR;
}
}
// Same as fileio_format(ch) but show error message with originator who and list possible formats
FILEFMT fileio_format_with_errmsg(char ch, const char *who) {
FILEFMT format = fileio_format(ch);
if(format == FMT_ERROR) {
pmsg_error("%sinvalid file format :%c; known formats are\n", who? who: "", ch);
for(int f, c, i = 0; i < 62; i++) {
c = i < 10? '0' + i: (i & 1? 'A': 'a') + (i - 10)/2;
f = fileio_format(c);
if(f != FMT_ERROR)
msg_error(" :%c %s\n", c, fileio_fmtstr(f));
}
}
return format;
}
// Multi-memory file flat address space layout (also used by avr-gcc's elf)
enum {
MULTI_FLASH, MULTI_DATA, MULTI_EEPROM,
MULTI_FUSES, MULTI_LOCK,
MULTI_SIGROW, MULTI_USERROW, MULTI_BOOTROW,
MULTI_N,
};
static const struct {
unsigned base, size;
const char *name;
} mulmem[] = {
{0, 0x800000, "flash"}, // rjmp/call can only address 8 MiB in AVR8 architectures
{0x800000, 0x10000, "data"}, // IO/SRAM
{0x810000, 0x10000, "EEPROM"},
{0x820000, 0x10000, "fuses"},
{0x830000, 0x10000, "lock"},
{0x840000, 0x10000, "sigrow"},
{0x850000, 0x10000, "userrow"},
{0x860000, 0x10000, "bootrow"},
};
#define ANY_MEM_SIZE (mulmem[MULTI_N-1].base + mulmem[MULTI_N-1].size)
#define MBASE(n) (mulmem[MULTI_ ## n].base)
#define MSIZE(n) (mulmem[MULTI_ ## n].size)
#define MEND(n) (MBASE(n) + MSIZE(n) - 1)
// Memory that holds all possible multi-memories as laid out above
AVRMEM *fileio_any_memory(const char *name) {
return avr_new_memory(name, ANY_MEM_SIZE);
}
#define boffset(p, basemem) baseoffset((p), avr_locate_ ## basemem(p), # basemem)
static int baseoffset(const AVRPART *p, const AVRMEM *base, const char *memname) {
if(!base)
pmsg_error("failed to locate %s memory in %s\n", memname, p->desc);
return base? base->offset: 0;
}
// Extends where memory is put in flat address space of .elf files
unsigned fileio_mem_offset(const AVRPART *p, const AVRMEM *mem) {
if(mem->type == 0 && mem->size == (int) ANY_MEM_SIZE)
return 0;
unsigned location =
mem_is_in_flash(mem)? MBASE(FLASH) + mem->offset - boffset(p, flash):
mem_is_io(mem) || mem_is_sram(mem)? MBASE(DATA) + mem->offset:
mem_is_eeprom(mem)? MBASE(EEPROM):
mem_is_in_fuses(mem)? MBASE(FUSES) + mem_fuse_offset(mem): mem_is_lock(mem)? MBASE(LOCK):
// Classic parts intersperse signature and calibration bytes, this code places them together
is_classic(p) && mem_is_signature(mem)? MBASE(SIGROW):
is_classic(p) && mem_is_calibration(mem)? MBASE(SIGROW) + 3:
is_classic(p) && mem_is_in_sigrow(mem)? MBASE(SIGROW) + 0x10 + mem->offset - boffset(p, sigrow):
// XMEGA parts have signature separate from prodsig, place prodsig at +0x10 as above
is_pdi(p) && mem_is_signature(mem)? MBASE(SIGROW):
is_pdi(p) && mem_is_in_sigrow(mem)? MBASE(SIGROW) + 0x10 + mem->offset - boffset(p, sigrow):
is_updi(p) && mem_is_in_sigrow(mem)? MBASE(SIGROW) + mem->offset - boffset(p, sigrow):
mem_is_sib(mem)? MBASE(SIGROW) + 0x1000: // Arbitrary 0x1000 offset in signature section for sib
mem_is_userrow(mem)? MBASE(USERROW): mem_is_bootrow(mem)? MBASE(BOOTROW): ~0U;
if(location == ~0U)
pmsg_error("unable to locate %s's %s in multi-memory address space\n", p->desc, mem->desc);
else if(location >= ANY_MEM_SIZE || location + mem->size > ANY_MEM_SIZE) { // Overflow
pmsg_error("%s's %s location [0x%06x, 0x%06x] outside flat address space [0, 0x%06x]\n",
p->desc, mem->desc, location, location + mem->size - 1, ANY_MEM_SIZE - 1);
location = ~0U;
} else if(location <= MEND(FLASH) && location + mem->size > MEND(FLASH) + 1) {
pmsg_error("%s's %s location [0x%06x, 0x%06x] straddles flash section boundary 0x%06x\n",
p->desc, mem->desc, location, location + mem->size - 1, MEND(FLASH) + 1);
location = ~0U;
} else if(location > MEND(FLASH) && location/0x10000 != (location + mem->size - 1)/0x10000) {
pmsg_error("%s's %s memory location [0x%06x, 0x%06x] straddles memory section boundary 0x%02x0000\n",
p->desc, mem->desc, location, location + mem->size - 1, 1 + location/0x10000);
location = ~0U;
}
return location;
}
static const char *memlabel(const AVRPART *p, const AVRMEM *m, unsigned addr, int n) {
if(m->size < (int) ANY_MEM_SIZE) // Ordinary (single) memory
return addr? NULL: m->desc;
// Inverse lookup of which memory could have been mapped to this address
for(LNODEID lm = lfirst(p->mem); lm; lm = lnext(lm))
if(fileio_mem_offset(p, (m = ldata(lm))) == addr && n == m->size)
return avr_mem_name(p, m);
for(LNODEID lm = lfirst(p->mem); lm; lm = lnext(lm))
if(fileio_mem_offset(p, (m = ldata(lm))) == addr)
return avr_mem_name(p, m);
return NULL;
}
// Tells lower level .hex/.srec routines whether to write intros/outros
typedef enum {
FIRST_SEG = 1,
LAST_SEG = 2,
} Segorder;
static void print_ihex_extended_addr(int n_64k, FILE *outf) {
unsigned char hi = (n_64k >> 8);
unsigned char lo = n_64k;
unsigned char cksum = -(2 + 0 + 4 + hi + lo);
fprintf(outf, ":02000004%02X%02X%02X\n", hi, lo, cksum);
}
/*
* Binary buffer to Intel Hex, see https://en.wikipedia.org/wiki/Intel_HEX
*
* Given a buffer and a single segment, segp, an open file 'outf' to which to
* write Intel Hex formatted data, the desired record size recsize, an
* AVR32-specific memory offset startaddr and the name of the output file,
* write a valid Intel Hex file. Where indicates whether this is the first
* segment to be written to the file or the last segment (or both).
*
* Return the maximum memory address within mem->buf that was read from plus
* one. If an error occurs, return -1.
*/
static int b2ihex(const AVRPART *p, const AVRMEM *mem, const Segment *segp, Segorder where,
int recsize, int startaddr, const char *outfile_unused, FILE *outf, FILEFMT ffmt) {
const unsigned char *buf = mem->buf;
int bufsize = segp->len;
unsigned int nextaddr;
int n, hiaddr, n_64k;
if(recsize < 1 || recsize > 255) {
pmsg_error("recsize %d must be in [1, 255]\n", recsize);
return -1;
}
nextaddr = (unsigned) (startaddr + segp->addr)%0x10000;
n_64k = (unsigned) (startaddr + segp->addr)/0x10000;
hiaddr = segp->addr;
buf += segp->addr;
// Give address unless it's the first segment and it would be the default 0
if(!((where & FIRST_SEG) && n_64k == 0))
print_ihex_extended_addr(n_64k, outf);
while(bufsize) {
n = recsize;
if(n > bufsize)
n = bufsize;
if((nextaddr + n) > 0x10000)
n = 0x10000 - nextaddr;
if(n) {
fprintf(outf, ":%02X%04X00", n, nextaddr);
unsigned char c, cksum = n + ((nextaddr >> 8) & 0x0ff) + (nextaddr & 0x0ff);
for(int i = 0; i < n; i++) {
fprintf(outf, "%02X", buf[i]);
cksum += buf[i];
}
cksum = -cksum;
fprintf(outf, "%02X", cksum);
if(ffmt == FMT_IHXC) { // Print comment with address and ASCII dump
const char *name = memlabel(p, mem, n_64k*0x10000 + nextaddr, n);
for(int i = n; i < recsize; i++)
fprintf(outf, " ");
fprintf(outf, " // %05x> ", n_64k*0x10000 + nextaddr);
for(int i = 0; i < n; i++)
if(n < 9 && name)
fprintf(outf, "%s0x%02x", i? " ": "", buf[i]);
else
putc((c = buf[i] & 0x7f) < ' ' || c == 0x7f? '.': c, outf);
if(name) {
fprintf(outf, " %s", name);
if((str_eq(name, "sigrow") || str_eq(name, "signature")) && !nextaddr) {
const char *mculist = str_ccmcunames_signature(buf, PM_ALL);
if(*mculist)
fprintf(outf, " (%s)", mculist);
}
}
}
putc('\n', outf);
nextaddr += n;
hiaddr += n;
}
if(nextaddr >= 0x10000 && bufsize > n) {
// Output an extended address record
n_64k++;
print_ihex_extended_addr(n_64k, outf);
nextaddr = 0;
}
// Advance to next recsize bytes
buf += n;
bufsize -= n;
}
// Add the end of record data line if it's the last segment
if(where & LAST_SEG)
fprintf(outf, ":00000001FF\n");
return hiaddr;
}
static int ihex_readrec(struct ihexsrec *ihex, char *rec) {
int i, j;
char buf[8];
int offset, len;
char *e;
unsigned char cksum;
len = strlen(rec);
offset = 1;
cksum = 0;
// Reclen
if(offset + 2 > len)
return -1;
for(i = 0; i < 2; i++)
buf[i] = rec[offset++];
buf[i] = 0;
ihex->reclen = strtoul(buf, &e, 16);
if(e == buf || *e != 0)
return -1;
// Load offset
if(offset + 4 > len)
return -1;
for(i = 0; i < 4; i++)
buf[i] = rec[offset++];
buf[i] = 0;
ihex->loadofs = strtoul(buf, &e, 16);
if(e == buf || *e != 0)
return -1;
// Record type
if(offset + 2 > len)
return -1;
for(i = 0; i < 2; i++)
buf[i] = rec[offset++];
buf[i] = 0;
ihex->rectyp = strtoul(buf, &e, 16);
if(e == buf || *e != 0)
return -1;
cksum = ihex->reclen + ((ihex->loadofs >> 8) & 0x0ff) + (ihex->loadofs & 0x0ff) + ihex->rectyp;
// Data
for(j = 0; j < ihex->reclen; j++) {
if(offset + 2 > len)
return -1;
for(i = 0; i < 2; i++)
buf[i] = rec[offset++];
buf[i] = 0;
ihex->data[j] = strtoul(buf, &e, 16);
if(e == buf || *e != 0)
return -1;
cksum += ihex->data[j];
}
// Cksum
if(offset + 2 > len)
return -1;
for(i = 0; i < 2; i++)
buf[i] = rec[offset++];
buf[i] = 0;
ihex->cksum = strtoul(buf, &e, 16);
if(e == buf || *e != 0)
return -1;
pmsg_debug("read ihex record type 0x%02x at 0x%04x with %2d bytes and chksum 0x%02x (0x%02x)\n",
ihex->rectyp, ihex->loadofs, ihex->reclen, ihex->cksum, -cksum & 0xff);
return -cksum & 0xff;
}
// Extract correct memory from large any memory assuming multi-memory model
static int any2mem(const AVRPART *p, const AVRMEM *mem, const Segment *segp, const AVRMEM *any, unsigned maxsize) {
// Compute location for multi-memory file input
unsigned location = maxsize > MEND(FLASH) + 1? fileio_mem_offset(p, mem): 0;
if(location == ~0U)
return -1;
unsigned ret = 0;
// Copy over memory to right place and return highest written address plus one
for(unsigned i = segp->addr, end = segp->addr + segp->len; i < end; i++)
if(any->tags[location + i]) {
mem->buf[i] = any->buf[location + i];
mem->tags[i] = any->tags[location + i];
ret = i + 1;
}
return ret;
}
/*
* Intel Hex to binary buffer
*
* Given an open file 'inf' which contains Intel Hex formatted data, parse the
* file, which potentially contains many AVR memories, and lay it out in a
* temporary AVR "any memory". This also determines whether inf contains the
* AVR memory mem to write to. Only the segment within mem->buf, segp, is
* written to.
*
* Return 0 if nothing was written, otherwise the maximum memory address within
* mem->buf that was written plus one. On error, return -1.
*/
static int ihex2b(const char *infile, FILE *inf, const AVRPART *p, const AVRMEM *mem,
const Segment *segp, unsigned int fileoffset, FILEFMT ffmt) {
const char *errstr;
unsigned int nextaddr, baseaddr, maxaddr;
int lineno, rc;
struct ihexsrec ihex;
lineno = 0;
baseaddr = 0;
maxaddr = 0;
nextaddr = 0;
rewind(inf);
AVRMEM *any = fileio_any_memory("any");
for(char *buffer; (buffer = str_fgets(inf, &errstr)); mmt_free(buffer)) {
lineno++;
int len = strlen(buffer);
if(len > 0 && buffer[len - 1] == '\n')
buffer[--len] = 0;
if(len == 0 || buffer[0] != ':')
continue;
rc = ihex_readrec(&ihex, buffer);
if(rc < 0) {
pmsg_error("invalid record at line %d of %s\n", lineno, infile);
mmt_free(buffer);
goto error;
}
if(rc != ihex.cksum) {
if(ffmt == FMT_IHEX) {
pmsg_error("checksum mismatch at line %d of %s\n", lineno, infile);
imsg_error("checksum=0x%02x, computed checksum=0x%02x\n", ihex.cksum, rc);
mmt_free(buffer);
goto error;
}
// Just warn with more permissive format FMT_IHXC
pmsg_notice("checksum mismatch at line %d of %s\n", lineno, infile);
imsg_notice("checksum=0x%02x, computed checksum=0x%02x\n", ihex.cksum, rc);
}
unsigned below = 0, anysize = any->size;
switch(ihex.rectyp) {
case 0: // Data record
if(ihex.loadofs + baseaddr < fileoffset) {
if(!ovsigck) {
pmsg_error("address 0x%06x below memory offset 0x%x at line %d of %s;\n",
ihex.loadofs + baseaddr, fileoffset, lineno, infile);
imsg_error("use -F to skip this check\n");
mmt_free(buffer);
goto error;
}
pmsg_warning("address 0x%06x below memory offset 0x%x at line %d of %s: ",
ihex.loadofs + baseaddr, fileoffset, lineno, infile);
below = fileoffset - baseaddr - ihex.loadofs;
if(below < ihex.reclen) { // Clip record
ihex.reclen -= below;
ihex.loadofs += below;
} else { // Nothing to write
ihex.reclen = 0;
}
msg_warning("%s record\n", ihex.reclen? "clipping": "ignoring");
}
nextaddr = ihex.loadofs + baseaddr - fileoffset;
if(ihex.reclen && nextaddr + ihex.reclen > anysize) {
if(!ovsigck) {
pmsg_error("Intel Hex record [0x%06x, 0x%06x] out of range [0, 0x%06x]\n",
nextaddr, nextaddr + ihex.reclen - 1, anysize - 1);
imsg_error("at line %d of %s; use -F to skip this check\n", lineno, infile);
mmt_free(buffer);
goto error;
}
pmsg_warning("Intel Hex record [0x%06x, 0x%06x] out of range [0, 0x%06x]: ",
nextaddr, nextaddr + ihex.reclen - 1, anysize - 1);
if(ihex.reclen && nextaddr + ihex.reclen > anysize) {
unsigned above = nextaddr + ihex.reclen - anysize;
ihex.reclen = above < ihex.reclen? ihex.reclen - above: 0; // Clip or zap
}
msg_warning("%s it\n", ihex.reclen? "clipping": "ignoring");
}
for(int i = 0; i < ihex.reclen; i++) {
any->buf[nextaddr + i] = ihex.data[below + i];
any->tags[nextaddr + i] = TAG_ALLOCATED;
}
if(!ovsigck && nextaddr == mulmem[MULTI_SIGROW].base && ihex.reclen >= 3)
if(!avr_sig_compatible(p->signature, any->buf + nextaddr)) {
pmsg_error("signature of %s incompatible with file's (%s);\n", p->desc,
str_ccmcunames_signature(any->buf + nextaddr, PM_ALL));
imsg_error("use -F to override this check\n");
mmt_free(buffer);
goto error;
}
if(ihex.reclen && nextaddr + ihex.reclen > maxaddr)
maxaddr = nextaddr + ihex.reclen;
break;
case 1: // End of file record
mmt_free(buffer);
goto done;
case 2: // Extended segment address record
baseaddr = (ihex.data[0] << 8 | ihex.data[1]) << 4;
break;
case 3: // Start segment address record
// We don't do anything with the start address
break;
case 4: // Extended linear address record
baseaddr = (ihex.data[0] << 8 | ihex.data[1]) << 16;
break;
case 5: // Start linear address record
// We don't do anything with the start address
break;
default:
pmsg_error("do not know how to deal with rectype=%d " "at line %d of %s\n", ihex.rectyp, lineno, infile);
mmt_free(buffer);
goto error;
}
}
if(errstr) {
pmsg_error("read error in Intel Hex file %s: %s\n", infile, errstr);
goto error;
}
if(maxaddr == 0) {
pmsg_error("no valid record found in Intel Hex file %s\n", infile);
goto error;
}
pmsg_warning("no end of file record found for Intel Hex file %s\n", infile);
done:
rc = any2mem(p, mem, segp, any, maxaddr);
avr_free_mem(any);
if(!rc)
pmsg_warning("no %s data found in Intel Hex file %s\n", mem->desc, infile);
return rc;
error:
avr_free_mem(any);
return -1;
}
static unsigned int cksum_srec(const unsigned char *buf, int n, unsigned addr, int addr_width) {
unsigned char cksum = n + addr_width + 1;
for(int i = 0; i < addr_width; i++)
cksum += addr, addr >>= 8;
for(int i = 0; i < n; i++)
cksum += buf[i];
cksum = 0xff - cksum;
return cksum;
}
// Binary to Motorola S-Record, see https://en.wikipedia.org/wiki/SREC_(file_format)
static int b2srec(const AVRMEM *mem, const Segment *segp, Segorder where,
int recsize, int startaddr, const char *outfile_unused, FILE *outf) {
const unsigned char *buf;
unsigned int nextaddr;
int n, hiaddr, addr_width;
buf = mem->buf + segp->addr;
nextaddr = startaddr + segp->addr;
hiaddr = 0;
unsigned highest = startaddr + mem->size - 1;
// Assume same address width throughout, even across different segments
char datarec, endrec;
if(highest <= 0xffffu) {
addr_width = 2;
datarec = '1';
endrec = '9';
} else if(highest <= 0xffffffu) {
addr_width = 3;
datarec = '2';
endrec = '8';
} else {
addr_width = 4;
datarec = '3';
endrec = '7';
}
if(recsize < 1 || recsize > 255 - 1 - addr_width) {
pmsg_error("recsize %d must be in [1, %d]\n", recsize, 255 - 1 - addr_width);
return -1;
}
if(where & FIRST_SEG) { // Write header record
const char *s = "https://github.com/avrdudes/avrdude";
unsigned char len = strlen(s);
fprintf(outf, "S0%02X0000", len + 3);
for(int i = 0; i < len; i++)
fprintf(outf, "%02X", s[i]);
fprintf(outf, "%02X\n", cksum_srec((unsigned char *) s, len, 0, 2));
cx->reccount = 0;
}
for(int bufsize = segp->len; bufsize; bufsize -= n) {
n = recsize;
if(n > bufsize)
n = bufsize;
fprintf(outf, "S%c%02X%0*X", datarec, n + addr_width + 1, 2*addr_width, nextaddr);
for(int i = 0; i < n; i++)
fprintf(outf, "%02X", buf[i]);
fprintf(outf, "%02X\n", cksum_srec(buf, n, nextaddr, addr_width));
buf += n;
nextaddr += n;
hiaddr += n;
cx->reccount++;
}
// Add S5/6 record count record and S7/8/9 end of data record
if(where & LAST_SEG) {
if(cx->reccount >= 0 && cx->reccount <= 0xffffff) {
int wd = cx->reccount <= 0xffff? 2: 3;
fprintf(outf, "S%c%02X%0*X%02X\n", '5' + (wd == 3), wd + 1, 2*wd,
cx->reccount, cksum_srec(NULL, 0, cx->reccount, wd));
}
fprintf(outf, "S%c%02X%0*X", endrec, addr_width + 1, 2*addr_width, startaddr);
fprintf(outf, "%02X\n", cksum_srec(NULL, 0, startaddr, addr_width));
}
return hiaddr;
}
static int srec_readrec(struct ihexsrec *srec, char *rec) {
int i, j;
char buf[8];
int offset, len, addr_width;
char *e;
unsigned char cksum;
int rc;
len = strlen(rec);
offset = 1;
cksum = 0;
addr_width = 2;
// Record type
if(offset + 1 > len)
return -1;
srec->rectyp = rec[offset++];
if(srec->rectyp == 0x32 || srec->rectyp == 0x38)
addr_width = 3; // S2 or S8-record
else if(srec->rectyp == 0x33 || srec->rectyp == 0x37)
addr_width = 4; // S3 or S7-record
// Reclen
if(offset + 2 > len)
return -1;
for(i = 0; i < 2; i++)
buf[i] = rec[offset++];
buf[i] = 0;
srec->reclen = strtoul(buf, &e, 16);
cksum += srec->reclen;
srec->reclen -= (addr_width + 1);
if(e == buf || *e != 0)
return -1;
// Load offset
if(offset + addr_width > len)
return -1;
for(i = 0; i < addr_width*2; i++)
buf[i] = rec[offset++];
buf[i] = 0;
srec->loadofs = strtoull(buf, &e, 16);
if(e == buf || *e != 0)
return -1;
for(i = addr_width; i > 0; i--)
cksum += (srec->loadofs >> (i - 1)*8) & 0xff;
// Data
for(j = 0; j < srec->reclen; j++) {
if(offset + 2 > len)
return -1;
for(i = 0; i < 2; i++)
buf[i] = rec[offset++];
buf[i] = 0;
srec->data[j] = strtoul(buf, &e, 16);
if(e == buf || *e != 0)
return -1;
cksum += srec->data[j];
}
// Cksum
if(offset + 2 > len)
return -1;
for(i = 0; i < 2; i++)
buf[i] = rec[offset++];
buf[i] = 0;
srec->cksum = strtoul(buf, &e, 16);
if(e == buf || *e != 0)
return -1;
rc = 0xff - cksum;
return rc;
}
// Motorola S-Record to binary
static int srec2b(const char *infile, FILE *inf, const AVRPART *p,
const AVRMEM *mem, const Segment *segp, unsigned int fileoffset) {
const char *errstr;
unsigned int nextaddr, maxaddr;
struct ihexsrec srec;
int lineno, rc, hexdigs;
unsigned int reccount;
unsigned char datarec;
lineno = 0;
maxaddr = 0;
reccount = 0;
rewind(inf);
AVRMEM *any = fileio_any_memory("any");
for(char *buffer; (buffer = str_fgets(inf, &errstr)); mmt_free(buffer)) {
lineno++;
int len = strlen(buffer);
if(len > 0 && buffer[len - 1] == '\n')
buffer[--len] = 0;
if(len == 0 || buffer[0] != 'S')
continue;
rc = srec_readrec(&srec, buffer);
if(rc < 0) {
pmsg_error("invalid record at line %d of %s\n", lineno, infile);
mmt_free(buffer);
goto error;
}
if(rc != srec.cksum) {
pmsg_error("checksum mismatch at line %d of %s\n", lineno, infile);
imsg_error("checksum=0x%02x, computed checksum=0x%02x\n", srec.cksum, rc);
mmt_free(buffer);
goto error;
}
datarec = 0;
hexdigs = 4;
switch(srec.rectyp) {
case '0': // S0: header record, ignore
break;
case '1': // S1: 16 bit address data record
datarec = 1;
break;
case '2': // S2: 24 bit address data record
datarec = 1;
hexdigs = 6;
break;
case '3': // S3: 32 bit address data record
datarec = 1;
hexdigs = 8;
break;
case '4': // S4: symbol record (LSI extension)
pmsg_error("not supported record at line %d of %s\n", lineno, infile);
mmt_free(buffer);
goto error;
case '5': // S5: count of S1, S2 and S3 records previously tx'd
if(srec.loadofs != reccount) {
pmsg_error("count of transmitted data records mismatch at line %d of %s\n", lineno, infile);
imsg_error("transmitted data records= %d, expected value= %d\n", reccount, srec.loadofs);
mmt_free(buffer);
goto error;
}
break;
case '7': // S7: end record for 32 bit addresses
case '8': // S8: end record for 24 bit addresses
case '9': // S9: end record for 16 bit addresses
mmt_free(buffer);
goto done;
default:
pmsg_error("do not know how to deal with rectype S%d at line %d of %s\n",
srec.rectyp, lineno, infile);
mmt_free(buffer);
goto error;
}
if(datarec == 1) {
nextaddr = srec.loadofs;
unsigned below = 0, anysize = any->size;
if(nextaddr < fileoffset) {
if(!ovsigck) {
pmsg_error("address 0x%0*x below memory offset 0x%x at line %d of %s\n",
hexdigs, nextaddr, fileoffset, lineno, infile);
imsg_error("use -F to skip this check\n");
mmt_free(buffer);
goto error;
}
pmsg_warning("address 0x%0*x below memory offset 0x%x at line %d of %s: ",
hexdigs, nextaddr, fileoffset, lineno, infile);
below = fileoffset - nextaddr;
if(below < srec.reclen) { // Clip record
nextaddr += below;
srec.reclen -= below;
} else { // Ignore record
srec.reclen = 0;
}
msg_warning("%s record\n", srec.reclen? "clipping": "ignoring");
}
nextaddr -= fileoffset;
if(srec.reclen && nextaddr + srec.reclen > anysize) {
if(!ovsigck) {
pmsg_error("Motorola S-Record [0x%06x, 0x%06x] out of range [0, 0x%06x]\n",
nextaddr, nextaddr + srec.reclen - 1, anysize - 1);
imsg_error("at line %d of %s; use -F to skip this check\n", lineno, infile);
mmt_free(buffer);
goto error;
}
pmsg_warning("Motorola S-Record [0x%06x, 0x%06x] out of range [0, 0x%06x]: ",
nextaddr, nextaddr + srec.reclen - 1, anysize - 1);
if(srec.reclen && nextaddr + srec.reclen > anysize) {
unsigned above = nextaddr + srec.reclen - anysize;
srec.reclen = above < srec.reclen? srec.reclen - above: 0; // Clip or zap
}
msg_warning("%s it\n", srec.reclen? "clipping": "ignoring");
}
for(int i = 0; i < srec.reclen; i++) {
any->buf[nextaddr + i] = srec.data[below + i];
any->tags[nextaddr + i] = TAG_ALLOCATED;
}
if(!ovsigck && nextaddr == mulmem[MULTI_SIGROW].base && srec.reclen >= 3)
if(!avr_sig_compatible(p->signature, any->buf + nextaddr)) {
pmsg_error("signature of %s incompatible with file's (%s);\n", p->desc,
str_ccmcunames_signature(any->buf + nextaddr, PM_ALL));
imsg_error("use -F to override this check\n");
mmt_free(buffer);
goto error;
}
if(srec.reclen && nextaddr + srec.reclen > maxaddr)
maxaddr = nextaddr + srec.reclen;
reccount++;
}
}
if(errstr) {
pmsg_error("read error in Motorola S-Record file %s: %s\n", infile, errstr);
goto error;
}
pmsg_warning("no end of file record found for Motorola S-Records file %s\n", infile);
done:
rc = any2mem(p, mem, segp, any, maxaddr);
avr_free_mem(any);
if(!rc)
pmsg_warning("no %s data found in Motorola S-Record file %s\n", mem->desc, infile);
return rc;
error:
avr_free_mem(any);
return -1;
}
#ifdef HAVE_LIBELF
/*
* Determine whether the ELF file section pointed to by `sh' fits completely
* into the program header segment pointed to by `ph'.
*
* Assumes the section has been checked already before to actually contain data
* (SHF_ALLOC, SHT_PROGBITS, sh_size > 0).
*
* Sometimes, program header segments might be larger than the actual file
* sections. On VM architectures, this is used to allow mmapping the entire
* ELF file "as is" (including things like the program header table itself).
*/
static inline int is_section_in_segment(Elf32_Shdr *sh, Elf32_Phdr *ph) {
if(sh->sh_offset < ph->p_offset)
return 0;
if(sh->sh_offset + sh->sh_size > ph->p_offset + ph->p_filesz)
return 0;
return 1;
}
static int elf_mem_limits(const AVRMEM *mem, const AVRPART *p,
unsigned int *lowbound, unsigned int *highbound, unsigned int *fileoff) {
int rv = 0;
if(is_awire(p)) { // AVR32
if(mem_is_flash(mem)) {
*lowbound = 0x80000000;
*highbound = 0xffffffff;
*fileoff = 0;
} else {
rv = -1;
}
} else {
if(mem_is_in_flash(mem)) {
*lowbound = MBASE(FLASH);
*highbound = MEND(FLASH); // Max 8 MiB
*fileoff = 0;
} else if(mem_is_io(mem) || mem_is_sram(mem)) { // IO & SRAM in data space
*lowbound = MBASE(DATA) + mem->offset;
*highbound = MEND(DATA);
*fileoff = 0;
} else if(mem_is_eeprom(mem)) {
*lowbound = MBASE(EEPROM);
*highbound = MEND(EEPROM); // Max 64 KiB
*fileoff = 0;
} else if(mem_is_a_fuse(mem) || mem_is_fuses(mem)) {
*lowbound = MBASE(FUSES);
*highbound = MEND(FUSES);
*fileoff = mem_is_a_fuse(mem)? mem_fuse_offset(mem): 0;
} else if(mem_is_lock(mem)) { // Lock or lockbits
*lowbound = MBASE(LOCK);
*highbound = MEND(LOCK);
*fileoff = 0;
} else if(mem_is_signature(mem)) { // Read only
*lowbound = MBASE(SIGROW);
*highbound = MEND(SIGROW);
*fileoff = 0;
} else if(mem_is_userrow(mem)) { // usersig or userrow
*lowbound = MBASE(USERROW);
*highbound = MEND(USERROW);
*fileoff = 0;
} else if(mem_is_bootrow(mem)) {
*lowbound = MBASE(BOOTROW);
*highbound = MEND(BOOTROW);
*fileoff = 0;
} else {
rv = -1;
}
}
return rv;
}
// ELF format to binary (the memory segment to read into is ignored)
static int elf2b(const char *infile, FILE *inf, const AVRMEM *mem,
const AVRPART *p, const Segment *segp_unused, unsigned int fileoffset_unused) {
Elf *e;
int rv = 0, size = 0;
unsigned int low, high, foff;
if(elf_mem_limits(mem, p, &low, &high, &foff) != 0) {
pmsg_error("cannot handle %s memory region from ELF file\n", mem->desc);
return -1;
}
/*
* The Xmega memory regions for "boot", "application", and "apptable" are
* actually sub-regions of "flash". Refine the applicable limits. This
* allows to select only the appropriate sections out of an ELF file that
* contains section data for more than one sub-segment.
*/
if(is_pdi(p) && mem_is_in_flash(mem) && !mem_is_flash(mem)) {
AVRMEM *flashmem = avr_locate_flash(p);
if(flashmem == NULL) {
pmsg_error("no flash memory region found, cannot compute bounds of %s sub-region\n", mem->desc);
return -1;
}
// The config file offsets are PDI offsets, rebase to 0
low = mem->offset - flashmem->offset;
high = low + mem->size - 1;
}
if(elf_version(EV_CURRENT) == EV_NONE) {
pmsg_error("ELF library initialization failed: %s\n", elf_errmsg(-1));
return -1;
}
if((e = elf_begin(fileno(inf), ELF_C_READ, NULL)) == NULL) {
pmsg_error("cannot open %s as an ELF file: %s\n", infile, elf_errmsg(-1));
return -1;
}
if(elf_kind(e) != ELF_K_ELF) {
pmsg_error("cannot use %s as an ELF input file\n", infile);
goto done;
}
size_t i, isize;
const char *id = elf_getident(e, &isize);
if(id == NULL) {
pmsg_error("unable to read ident area of %s: %s\n", infile, elf_errmsg(-1));
goto done;
}
const char *endianname;
unsigned char endianness;
if(is_awire(p)) { // AVR32
endianness = ELFDATA2MSB;
endianname = "little";
} else {
endianness = ELFDATA2LSB;
endianname = "big";
}
if(id[EI_CLASS] != ELFCLASS32 || id[EI_DATA] != endianness) {
pmsg_error("ELF file %s is not a 32-bit, %s-endian file that was expected\n", infile, endianname);
goto done;
}
Elf32_Ehdr *eh;
if((eh = elf32_getehdr(e)) == NULL) {
pmsg_error("unable to read ehdr of %s: %s\n", infile, elf_errmsg(-1));
goto done;
}
if(eh->e_type != ET_EXEC) {
pmsg_error("ELF file %s is not an executable file\n", infile);
goto done;
}
const char *mname;
uint16_t machine;
if(is_awire(p)) {
machine = EM_AVR32;
mname = "AVR32";
} else {
machine = EM_AVR;
mname = "AVR";
}
if(eh->e_machine != machine) {
pmsg_error("ELF file %s is not for machine %s\n", infile, mname);
goto done;
}
if(eh->e_phnum == 0xffff /* PN_XNUM */) {
pmsg_error("ELF file %s uses extended program header numbers which are not expected\n", infile);
goto done;
}
Elf32_Phdr *ph;
if((ph = elf32_getphdr(e)) == NULL) {
pmsg_error("unable to read program header table of %s: %s\n", infile, elf_errmsg(-1));
goto done;
}
size_t sndx;
if(elf_getshdrstrndx(e, &sndx) != 0) {
pmsg_error("unable to obtain section name string table: %s\n", elf_errmsg(-1));
sndx = 0;
}
/*
* Walk the program header table, pick up entries that are of type PT_LOAD,
* and have a non-zero p_filesz.
*/
for(i = 0; i < eh->e_phnum; i++) {
if(ph[i].p_type != PT_LOAD || ph[i].p_filesz == 0)
continue;
pmsg_debug("considering PT_LOAD program header entry #%d\n", (int) i);
imsg_debug("p_vaddr 0x%x, p_paddr 0x%x, p_filesz %d\n", ph[i].p_vaddr, ph[i].p_paddr, ph[i].p_filesz);
Elf_Scn *scn = NULL;
while((scn = elf_nextscn(e, scn)) != NULL) {
size_t ndx = elf_ndxscn(scn);
Elf32_Shdr *sh = elf32_getshdr(scn);
if(sh == NULL) {
pmsg_error("unable to read section #%u header: %s\n", (unsigned int) ndx, elf_errmsg(-1));
rv = -1;
continue;
}
// Only interested in PROGBITS, ALLOC sections
if((sh->sh_flags & SHF_ALLOC) == 0 || sh->sh_type != SHT_PROGBITS)
continue;
// Not interested in empty sections
if(sh->sh_size == 0)
continue;
// Section must belong to this segment
if(!is_section_in_segment(sh, ph + i))
continue;
const char *sname = sndx? elf_strptr(e, sndx, sh->sh_name): "*unknown*";
unsigned int lma = ph[i].p_paddr + sh->sh_offset - ph[i].p_offset;
pmsg_debug("found section %s, LMA 0x%x, sh_size %u\n", sname, lma, sh->sh_size);
if(!(lma >= low && lma + sh->sh_size < high)) {
pmsg_debug("skipping %s (inappropriate for %s)\n", sname, mem->desc);
continue;
}
/*
* 1-byte sized memory regions are special: they are used for fuse bits,
* where multiple regions (in the config file) map to a single, larger
* region in the ELF file (e.g. "lfuse", "hfuse", and "efuse" all map to
* ".fuse"). We silently accept a larger ELF file region for these, and
* extract the actual byte to write from it, using the "foff" offset
* obtained above.
*/
if(mem->size != 1 && sh->sh_size > (unsigned) mem->size) {
pmsg_error("section %s of size %u does not fit into %s of size %d\n",
sname, sh->sh_size, mem->desc, mem->size);
rv = -1;
continue;
}
Elf_Data *d = NULL;
while((d = elf_getdata(scn, d)) != NULL) {
pmsg_debug("data block: d_buf %p, d_off 0x%x, d_size %ld\n",
d->d_buf, (unsigned int) d->d_off, (long) d->d_size);
if(mem->size == 1) {
if(d->d_off != 0) {
pmsg_error("unexpected data block at offset != 0\n");
rv = -1;
} else if(foff >= d->d_size) {
pmsg_error("ELF file section does not contain byte at offset %d\n", foff);
rv = -1;
} else {
pmsg_debug("extracting one byte from file offset %d\n", foff);
mem->buf[0] = ((unsigned char *) d->d_buf)[foff];
mem->tags[0] = TAG_ALLOCATED;
size = 1;
}
} else {
int idx = lma - low + d->d_off;
int end = idx + d->d_size;
if(idx >= 0 && idx < mem->size && end >= 0 && end <= mem->size && end - idx >= 0) {
if(end > size)
size = end;
pmsg_debug("writing %d bytes to mem offset 0x%x\n", end - idx, idx);
memcpy(mem->buf + idx, d->d_buf, end - idx);
memset(mem->tags + idx, TAG_ALLOCATED, end - idx);
} else {
pmsg_error("section %s [0x%04x, 0x%04x] does not fit into %s [0, 0x%04x]\n",
sname, idx, (int) (idx + d->d_size - 1), mem->desc, mem->size - 1);
rv = -1;
}
}
}
}
}
done:
(void) elf_end(e);
return rv < 0? rv: size;
}
#endif // HAVE_LIBELF
// Read/write binary files and return highest memory addr set + 1
static int fileio_rbin(struct fioparms *fio, const char *filename, FILE *f, const AVRMEM *mem, const Segment *segp) {
int rc;
switch(fio->op) {
case FIO_READ:
rc = fread(mem->buf + segp->addr, 1, segp->len, f);
if(rc > 0)
memset(mem->tags + segp->addr, TAG_ALLOCATED, rc);
break;
case FIO_WRITE:
rc = fwrite(mem->buf + segp->addr, 1, segp->len, f);
break;
default:
pmsg_error("invalid fileio operation=%d\n", fio->op);
return -1;
}
if(rc < 0 || (fio->op == FIO_WRITE && rc < segp->len)) {
pmsg_ext_error("%s error %s %s: %s; %s %d of the expected %d bytes\n",
fio->iodesc, fio->dir, filename, strerror(errno), fio->rw, rc, segp->len);
return -1;
}
return segp->addr + rc;
}
static int fileio_imm(struct fioparms *fio, const char *fname, FILE *f_unused,
const AVRMEM *mem, const Segment *segp) {
char *tok, *p, *line;
const char *errstr;
int n = segp->addr, end = segp->addr + segp->len;
p = line = mmt_strdup(fname);
switch(fio->op) {
case FIO_READ:
while(*(tok = str_nexttok(p, ", \t\n\r\v\f", &p)) && n < end) {
int set = str_membuf(tok, STR_ANY, mem->buf + n, end - n, &errstr);
if(errstr || set < 0) {
pmsg_error("invalid data %s in immediate mode: %s\n", tok, errstr);
mmt_free(line);
return -1;
}
memset(mem->tags + n, TAG_ALLOCATED, set);
n += set;
}
break;
case FIO_WRITE:
pmsg_error("invalid file format 'immediate' for output\n");
mmt_free(line);
return -1;
default:
pmsg_error("invalid operation=%d\n", fio->op);
mmt_free(line);
return -1;
}
mmt_free(line);
return n;
}
static int fileio_ihex(struct fioparms *fio, const char *filename, FILE *f,
const AVRPART *p, const AVRMEM *mem, const Segment *segp, FILEFMT ffmt, Segorder where) {
int rc;
switch(fio->op) {
case FIO_WRITE:
rc = b2ihex(p, mem, segp, where, 32, fio->fileoffset, filename, f, ffmt);
break;
case FIO_READ:
rc = ihex2b(filename, f, p, mem, segp, fio->fileoffset, ffmt);
break;
default:
rc = -1;
pmsg_error("invalid Intel Hex file I/O operation=%d\n", fio->op);
}
return rc < 0? -1: rc;
}
static int fileio_srec(struct fioparms *fio, const char *filename, FILE *f,
const AVRPART *p, const AVRMEM *mem, const Segment *segp, Segorder where) {
int rc;
switch(fio->op) {
case FIO_WRITE:
rc = b2srec(mem, segp, where, 32, fio->fileoffset, filename, f);
break;
case FIO_READ:
rc = srec2b(filename, f, p, mem, segp, fio->fileoffset);
break;
default:
rc = -1;
pmsg_error("invalid Motorola S-Records file I/O operation=%d\n", fio->op);
}
return rc < 0? -1: rc;
}
#ifdef HAVE_LIBELF
static int fileio_elf(struct fioparms *fio, const char *filename, FILE *f,
const AVRMEM *mem, const AVRPART *p, const Segment *segp) {
int rc;
switch(fio->op) {
case FIO_WRITE:
pmsg_error("write operation not supported for ELF\n");
return -1;
break;
case FIO_READ:
rc = elf2b(filename, f, mem, p, segp, fio->fileoffset);
return rc;
default:
pmsg_error("invalid ELF file I/O operation=%d\n", fio->op);
return -1;
break;
}
}
#endif
static int b2num(const char *filename, FILE *f, const AVRMEM *mem, const Segment *segp, FILEFMT fmt) {
const char *prefix;
int base;
switch(fmt) {
case FMT_HEX:
prefix = "0x";
base = 16;
break;
default:
case FMT_DEC:
prefix = "";
base = 10;
break;
case FMT_OCT:
prefix = "0";
base = 8;
break;
case FMT_BIN:
prefix = "0b";
base = 2;
break;
case FMT_EEGG:
prefix = "";
base = 'r';
break;
}
for(int seen = 0, i = segp->addr; i < segp->addr + segp->len; i++) {
char cbuf[81];
if(seen++)
if(putc(',', f) == EOF)
goto writeerr;
unsigned num = mem->buf[i];
/*
* For a base of 8 and a value < 8 to convert, don't write the prefix. The
* conversion will be indistinguishable from a decimal one then.
*/
if(prefix[0] != '\0' && !(base == 8 && num < 8)) {
if(fputs(prefix, f) == EOF)
goto writeerr;
}
str_utoa(num, cbuf, base);
if(fputs(cbuf, f) == EOF)
goto writeerr;
}
if(putc('\n', f) == EOF)
goto writeerr;
return segp->addr + segp->len;
writeerr:
pmsg_ext_error("unable to write to %s: %s\n", filename, strerror(errno));
return -1;
}
static int num2b(const char *filename, FILE *f, const AVRMEM *mem, const Segment *segp) {
const char *geterr = NULL;
char *line;
int n = segp->addr, end = segp->addr + segp->len;
while(n < end && (line = str_fgets(f, &geterr))) {
char *p = line, *tok;
while(*p && isspace(*p & 0xff)) // Skip white space, comments and empty lines
p++;
if(*p && *p != '#') {
while(*(tok = str_nexttok(p, ", \t\n\r\v\f", &p)) && n < end) {
const char *errstr;
if(*tok == '#') // Ignore rest of line after #
break;
int set = str_membuf(tok, STR_ANY, mem->buf + n, end - n, &errstr);
if(errstr || set < 0) {
pmsg_error("invalid data %s in number :[hdob] format: %s\n", tok, errstr);
mmt_free(line);
return -1;
}
memset(mem->tags + n, TAG_ALLOCATED, set);
n += set;
}
}
mmt_free(line);
}
if(geterr) {
pmsg_error("fgets() errror: %s\n", geterr);
n = -1;
}
return n;
}
static int fileio_num(struct fioparms *fio, const char *filename, FILE *f,
const AVRMEM *mem, const Segment *segp, FILEFMT fmt) {
switch(fio->op) {
case FIO_WRITE:
return b2num(filename, f, mem, segp, fmt);
case FIO_READ:
return num2b(filename, f, mem, segp);
default:
pmsg_error("invalid operation=%d\n", fio->op);
return -1;
}
}
static int fileio_setparms(int op, struct fioparms *fp, const AVRPART *p, const AVRMEM *m) {
fp->op = op;
switch(op) {
case FIO_READ:
fp->mode = "r";
fp->iodesc = "input";
fp->dir = "from";
fp->rw = "read";
break;
case FIO_WRITE:
fp->mode = "w";
fp->iodesc = "output";
fp->dir = "to";
fp->rw = "wrote";
break;
default:
pmsg_error("invalid I/O operation %d\n", op);
return -1;
break;
}
/*
* AVR32 devices maintain their load offset within the file itself, but
* AVRDUDE maintains all memory images 0-based.
*/
fp->fileoffset = is_awire(p)? m->offset: 0;
return 0;
}
FILE *fileio_fopenr(const char *fname) {
#if !defined(WIN32)
return fopen(fname, "r");
#else
return fopen(fname, "rb");
#endif
}
static FILEFMT couldbe(int first, unsigned char *line) {
int found;
unsigned long i, nxdigs, len;
// Check for ELF file
if(first && line[0] == 0177 && str_starts((char *) line + 1, "ELF"))
return FMT_ELF;
len = strlen((char *) line);
while(len > 0 && line[len - 1] && isspace(line[len - 1])) // Cr/lf etc
line[--len] = 0;
// Check for binary data
for(i = 0; i < len; i++)
if(line[i] > 127)
return FMT_RBIN;
// Check for lines that look like Intel HEX
if(line[0] == ':' && len >= 11 && isxdigit(line[1]) && isxdigit(line[2])) {
nxdigs = sscanf((char *) line + 1, "%2lx", &nxdigs) == 1? 2*nxdigs + 8: len;
for(found = 3 + nxdigs <= len, i = 0; found && i < nxdigs; i++)
if(!isxdigit(line[3 + i]))
found = 0;
if(found)
return FMT_IHEX;
}
// Check for lines that look like Motorola S-record
if(line[0] == 'S' && len >= 10 && isdigit(line[1]) && isxdigit(line[2]) && isxdigit(line[3])) {
nxdigs = sscanf((char *) line + 2, "%2lx", &nxdigs) == 1? 2*nxdigs: len;
for(found = 4 + nxdigs <= len, i = 0; found && i < nxdigs; i++)
if(!isxdigit(line[4 + i]))
found = 0;
if(found)
return FMT_SREC;
}
// Check for terminal-type data entries
char *p = (char *) line, *tok;
int failed = 0, idx[4] = { 0, 0, 0, 0 };
while(*p && isspace(*p & 0xff))
p++;
if(*p && *p != '#') {
while(!failed && *(tok = str_nexttok(p, ", \t\n\r\v\f", &p))) {
const char *errstr;
unsigned char mem[8];
if(*tok == '#')
break;
int set = str_membuf(tok, STR_ANY, mem, sizeof mem, &errstr);
if(errstr || set < 0)
failed++;
else if(set > 0)
idx[str_casestarts(tok, "0x")? 0: str_casestarts(tok, "0b")? 1:
*tok == '0' && tok[1] && strchr("01234567", tok[1])? 2: 3]++;
}
if(!failed && idx[0] + idx[1] + idx[2] + idx[3]) {
// Doesn't matter which one: they all parse numbers universally
int i0 = idx[0] >= idx[1]? 0: 1;
int i2 = idx[2] > idx[3]? 2: 3;
const int fmts[4] = { FMT_HEX, FMT_BIN, FMT_OCT, FMT_DEC };
return fmts[idx[i0] >= idx[i2]? i0: i2];
}
}
return FMT_ERROR;
}
int fileio_fmt_autodetect_fp(FILE *f) {
const char *err = NULL;
int ret = FMT_ERROR;
if(f) {
unsigned char *buf;
for(int first = 1; ret == FMT_ERROR && (buf = (unsigned char *) str_fgets(f, &err)); first = 0) {
ret = couldbe(first, buf);
mmt_free(buf);
}
if(err)
pmsg_error("fgets() error: %s\n", err);
}
return ret;
}
// Read-only files that avrdude generates itself
int is_generated_fname(const char *fname) {
return str_starts(fname, "urboot:");
}
// Does generated file have contents, ie, no option parsing error, no _list/_show?
int generated_file_has_contents(const AVRPART *part, const char *fname) {
if(str_starts(fname, "urboot:"))
return urboot_has_contents(part, fname);
return 1;
}
int fileio_fmt_autodetect(const char *fname) {
if(is_generated_fname(fname))
return FMT_IHEX;
FILE *f = fileio_fopenr(fname);
if(f == NULL) {
pmsg_ext_error("unable to open %s: %s\n", fname, strerror(errno));
return -1;
}
int format = fileio_fmt_autodetect_fp(f);
fclose(f);
return format;
}
int fileio_mem(int op, const char *filename, FILEFMT format, const AVRPART *p, const AVRMEM *mem, int msize) {
if(msize < 0 || op == FIO_READ || op == FIO_READ_FOR_VERIFY)
msize = mem->size;
if(str_starts(filename, "urboot:") && (op == FIO_READ || op == FIO_READ_FOR_VERIFY))
return urbootautogen(p, mem, filename);
const Segment seg = { 0, msize };
return fileio_segments(op, filename, format, p, mem, 1, &seg);
}
int fileio(int op, const char *filename, FILEFMT format, const AVRPART *p, const char *memstr, int size) {
AVRMEM *mem = avr_locate_mem(p, memstr);
if(mem == NULL) {
pmsg_error("memory %s not configured for device %s\n", memstr, p->desc);
return -1;
}
return fileio_mem(op, filename, format, p, mem, size);
}
// Normalise segment address and length to be non-negative
int segment_normalise(const AVRMEM *mem, Segment *segp) {
int addr = segp->addr, len = segp->len, maxsize = mem->size;
int digits = maxsize > 0x10000? 5: 4;
if(addr < 0)
addr = maxsize + addr;
if(addr < 0 || addr >= maxsize) {
pmsg_error("%s address 0x%0*x is out of range [-0x%0*x, 0x%0*x]\n",
mem->desc, digits, segp->addr, digits, maxsize, digits, maxsize - 1);
return -1;
}
if(len < 0)
len = maxsize + len - addr + 1;
if(len < 0 || len > maxsize) {
pmsg_error("invalid segment length %d for %s address 0x%0*x\n", segp->len, mem->desc, digits, addr);
return -1;
}
if(len > 0 && addr+len-1 >= maxsize) {
pmsg_error("segment length %d with starting address 0x%0*x reaches beyond memory end 0x%0*x\n",
segp->len, digits, addr, digits, maxsize-1);
return -1;
}
segp->addr = addr;
segp->len = len;
return 0;
}
static int fileio_segments_normalise(int oprwv, const char *filename, FILEFMT format,
const AVRPART *p, const AVRMEM *mem, int n, Segment *seglist) {
int op, rc;
FILE *f;
const char *fname;
struct fioparms fio;
int using_stdio;
op = oprwv == FIO_READ_FOR_VERIFY? FIO_READ: oprwv;
rc = fileio_setparms(op, &fio, p, mem);
if(rc < 0)
return -1;
for(int i = 0; i < n; i++)
if(segment_normalise(mem, seglist + i) < 0)
return -1;
using_stdio = 0;
fname = filename;
f = NULL;
if(str_eq(filename, "-")) {
using_stdio = 1;
fname = fio.op == FIO_READ? "<stdin>": "<stdout>";
f = fio.op == FIO_READ? stdin: stdout;
}
if(format == FMT_AUTO) {
int format_detect;
if(using_stdio) {
pmsg_error("cannot auto detect file format when using stdin/out;\n");
imsg_error("please specify a file format and try again\n");
return -1;
}
format_detect = fileio_fmt_autodetect(fname);
if(format_detect < 0) {
pmsg_error("cannot determine file format for %s, specify explicitly\n", fname);
return -1;
}
format = format_detect;
if(quell_progress < 2)
pmsg_notice("%s file %s auto detected as %s\n", fio.iodesc, fname, fileio_fmtstr(format));
}
#if defined(WIN32)
// Open Raw Binary and ELF format in binary mode on Windows
if(format == FMT_RBIN || format == FMT_ELF) {
if(fio.op == FIO_READ) {
fio.mode = "rb";
}
if(fio.op == FIO_WRITE) {
fio.mode = "wb";
}
}
#endif
if(format != FMT_IMM) {
if(!using_stdio) {
f = fopen(fname, fio.mode);
if(f == NULL) {
pmsg_ext_error("cannot open %s file %s: %s\n", fio.iodesc, fname, strerror(errno));
return -1;
}
}
}
rc = 0;
for(int i = 0; i < n; i++) {
int addr = seglist[i].addr, len = seglist[i].len;
if(len == 0 && fio.op != FIO_WRITE)
continue;
if(fio.op == FIO_READ) // Fill unspecified memory in segment
memset(mem->buf + addr, 0xff, len);
memset(mem->tags + addr, 0, len);
Segorder where = i == 0? FIRST_SEG: 0;
if(i + 1 == n)
where |= LAST_SEG;
int thisrc = 0;
switch(format) {
case FMT_IHEX:
case FMT_IHXC:
thisrc = fileio_ihex(&fio, fname, f, p, mem, seglist + i, format, where);
break;
case FMT_SREC:
thisrc = fileio_srec(&fio, fname, f, p, mem, seglist + i, where);
break;
case FMT_RBIN:
thisrc = fileio_rbin(&fio, fname, f, mem, seglist + i);
break;
case FMT_ELF:
#ifdef HAVE_LIBELF
thisrc = fileio_elf(&fio, fname, f, mem, p, seglist + i);
break;
#else
pmsg_error("cannot handle ELF file %s, ELF file support was not compiled in\n", fname);
return -1;
#endif
case FMT_IMM:
thisrc = fileio_imm(&fio, fname, f, mem, seglist + i);
break;
case FMT_HEX:
case FMT_DEC:
case FMT_OCT:
case FMT_BIN:
case FMT_EEGG:
thisrc = fileio_num(&fio, fname, f, mem, seglist + i, format);
break;
default:
pmsg_error("invalid %s file format: %d\n", fio.iodesc, format);
return -1;
}
if(thisrc < 0)
return thisrc;
if(thisrc > rc)
rc = thisrc;
}
// On reading flash other than for verify set the size to location of highest non-0xff byte
if(rc > 0 && oprwv == FIO_READ) {
int hiaddr = avr_mem_hiaddr(mem); // @@@ Should check segments only, not all file
if(hiaddr < rc) // If trailing-0xff not disabled
rc = hiaddr;
}
if(format != FMT_IMM && !using_stdio)
fclose(f);
return rc;
}
int fileio_segments(int oprwv, const char *filename, FILEFMT format,
const AVRPART *p, const AVRMEM *mem, int n, const Segment *list) {
Segment *seglist = mmt_malloc(n*sizeof *seglist);
memcpy(seglist, list, n*sizeof *seglist);
int ret = fileio_segments_normalise(oprwv, filename, format, p, mem, n, seglist);
mmt_free(seglist);
return ret;
}