Files
avrdude/src/config.c
2025-05-08 00:10:36 +02:00

1084 lines
28 KiB
C

/*
* avrdude - A Downloader/Uploader for AVR device programmers
* Copyright (C) 2000-2004 Brian S. Dean <bsd@bdmicro.com>
* Copyright (C) 2022 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 <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stddef.h>
#include <string.h>
#include <ctype.h>
#include <wchar.h>
#include "avrdude.h"
#include "libavrdude.h"
#include "config.h"
#include "config_gram.h"
const char *avrdude_conf_version;
const char *default_programmer;
const char *default_parallel;
const char *default_serial;
const char *default_spi;
int default_baudrate;
double default_bitclock;
char const *default_linuxgpio;
int allow_subshells;
LISTID string_list;
LISTID number_list;
PROGRAMMER *current_prog;
AVRPART *current_part;
AVRMEM *current_mem;
int current_strct;
LISTID part_list;
LISTID programmers;
bool is_alias;
int cfg_lineno;
char *cfg_infile;
extern char *yytext;
#define pgm_comp_desc(x, type) { #x, COMP_PROGRAMMER, offsetof(PROGRAMMER, x), sizeof(((PROGRAMMER *) NULL)->x), type }
#define part_comp_desc(x, type) { #x, COMP_AVRPART, offsetof(AVRPART, x), sizeof(((AVRPART *) NULL)->x), type }
#define mem_comp_desc(x, type) { #x, COMP_AVRMEM, offsetof(AVRMEM, x), sizeof(((AVRMEM *) NULL)->x), type }
// Component description for config_gram.y, will be sorted appropriately on first use
Component avr_comp[] = {
// PROGRAMMER
pgm_comp_desc(desc, COMP_STRING),
pgm_comp_desc(prog_modes, COMP_INT),
pgm_comp_desc(is_serialadapter, COMP_INT),
pgm_comp_desc(extra_features, COMP_INT),
pgm_comp_desc(baudrate, COMP_INT),
pgm_comp_desc(usbvid, COMP_INT),
pgm_comp_desc(usbdev, COMP_STRING),
pgm_comp_desc(usbsn, COMP_STRING),
pgm_comp_desc(usbvendor, COMP_STRING),
pgm_comp_desc(usbproduct, COMP_STRING),
// AVRPART
part_comp_desc(desc, COMP_STRING),
part_comp_desc(family_id, COMP_STRING),
part_comp_desc(prog_modes, COMP_INT),
part_comp_desc(mcuid, COMP_INT),
part_comp_desc(archnum, COMP_INT),
part_comp_desc(n_interrupts, COMP_INT),
part_comp_desc(n_page_erase, COMP_INT),
part_comp_desc(n_boot_sections, COMP_INT),
part_comp_desc(boot_section_size, COMP_INT),
part_comp_desc(hvupdi_variant, COMP_INT),
part_comp_desc(stk500_devcode, COMP_INT),
part_comp_desc(avr910_devcode, COMP_INT),
part_comp_desc(chip_erase_delay, COMP_INT),
part_comp_desc(pagel, COMP_CHAR),
part_comp_desc(bs2, COMP_CHAR),
part_comp_desc(timeout, COMP_INT),
part_comp_desc(stabdelay, COMP_INT),
part_comp_desc(cmdexedelay, COMP_INT),
part_comp_desc(synchloops, COMP_INT),
part_comp_desc(bytedelay, COMP_INT),
part_comp_desc(pollindex, COMP_INT),
part_comp_desc(pollvalue, COMP_CHAR),
part_comp_desc(predelay, COMP_INT),
part_comp_desc(postdelay, COMP_INT),
part_comp_desc(pollmethod, COMP_INT),
part_comp_desc(hventerstabdelay, COMP_INT), // STK500 v2 hv mode parameters
part_comp_desc(progmodedelay, COMP_INT),
part_comp_desc(latchcycles, COMP_INT),
part_comp_desc(togglevtg, COMP_INT),
part_comp_desc(poweroffdelay, COMP_INT),
part_comp_desc(resetdelayms, COMP_INT),
part_comp_desc(resetdelayus, COMP_INT),
part_comp_desc(hvleavestabdelay, COMP_INT),
part_comp_desc(resetdelay, COMP_INT),
part_comp_desc(chiperasepulsewidth, COMP_INT),
part_comp_desc(chiperasepolltimeout, COMP_INT),
part_comp_desc(chiperasetime, COMP_INT),
part_comp_desc(programfusepulsewidth, COMP_INT),
part_comp_desc(programfusepolltimeout, COMP_INT),
part_comp_desc(programlockpulsewidth, COMP_INT),
part_comp_desc(programlockpolltimeout, COMP_INT),
part_comp_desc(synchcycles, COMP_INT),
part_comp_desc(hvspcmdexedelay, COMP_INT),
part_comp_desc(idr, COMP_CHAR),
part_comp_desc(rampz, COMP_CHAR),
part_comp_desc(spmcr, COMP_CHAR),
part_comp_desc(eecr, COMP_CHAR),
part_comp_desc(eind, COMP_CHAR),
part_comp_desc(mcu_base, COMP_INT),
part_comp_desc(nvm_base, COMP_INT),
part_comp_desc(ocd_base, COMP_INT),
part_comp_desc(syscfg_base, COMP_INT),
part_comp_desc(ocdrev, COMP_INT),
part_comp_desc(autobaud_sync, COMP_CHAR),
part_comp_desc(factory_fcpu, COMP_INT),
// AVRMEM
mem_comp_desc(paged, COMP_BOOL),
mem_comp_desc(size, COMP_INT),
mem_comp_desc(num_pages, COMP_INT),
mem_comp_desc(initval, COMP_INT),
mem_comp_desc(bitmask, COMP_INT),
mem_comp_desc(n_word_writes, COMP_INT),
mem_comp_desc(offset, COMP_INT),
mem_comp_desc(min_write_delay, COMP_INT),
mem_comp_desc(max_write_delay, COMP_INT),
mem_comp_desc(pwroff_after_write, COMP_INT),
{"readback_p1", COMP_AVRMEM, offsetof(AVRMEM, readback) + 0, 1, COMP_CHAR},
{"readback_p2", COMP_AVRMEM, offsetof(AVRMEM, readback) + 1, 1, COMP_CHAR},
mem_comp_desc(mode, COMP_INT),
mem_comp_desc(delay, COMP_INT),
mem_comp_desc(pollindex, COMP_INT),
mem_comp_desc(blocksize, COMP_INT),
mem_comp_desc(readsize, COMP_INT),
};
#define DEBUG 0
void cleanup_config(void) {
ldestroy_cb(part_list, (void (*)(void *)) avr_free_part);
ldestroy_cb(programmers, (void (*)(void *)) pgm_free);
ldestroy_cb(string_list, (void (*)(void *)) free_token);
ldestroy_cb(number_list, (void (*)(void *)) free_token);
}
int init_config(void) {
string_list = lcreat(NULL, 0);
number_list = lcreat(NULL, 0);
current_prog = NULL;
current_part = NULL;
current_mem = NULL;
part_list = lcreat(NULL, 0);
programmers = lcreat(NULL, 0);
is_alias = false;
cfg_lineno = 1;
cfg_infile = NULL;
return 0;
}
void *cfg_malloc(const char *funcname, size_t n) {
void *ret = malloc(n);
if(!ret) {
pmsg_error("out of memory in %s() for malloc(); needed %lu bytes\n", funcname, (unsigned long) n);
exit(1);
}
memset(ret, 0, n);
return ret;
}
void *cfg_realloc(const char *funcname, void *p, size_t n) {
void *ret;
if(!(ret = p? realloc(p, n): calloc(1, n))) {
pmsg_error("out of memory in %s() for %salloc(); needed %lu bytes\n", funcname, p? "re": "c", (unsigned long) n);
exit(1);
}
return ret;
}
char *cfg_strdup(const char *funcname, const char *s) {
char *ret = strdup(s);
if(!ret) {
pmsg_error("out of memory in %s() for strdup()\n", funcname);
exit(1);
}
return ret;
}
void mmt_f_free(void *ptr) {
mmt_free(ptr);
}
int yywrap() {
return 1;
}
int yyerror(char *errmsg, ...) {
va_list args;
char message[512];
va_start(args, errmsg);
vsnprintf(message, sizeof(message), errmsg, args);
pmsg_error("%s [%s:%d]\n", message, cfg_infile, cfg_lineno);
va_end(args);
return 0;
}
int yywarning(char *errmsg, ...) {
va_list args;
char message[512];
va_start(args, errmsg);
vsnprintf(message, sizeof(message), errmsg, args);
pmsg_warning("%s [%s:%d]\n", message, cfg_infile, cfg_lineno);
va_end(args);
return 0;
}
TOKEN *new_token(int primary) {
TOKEN *tkn = (TOKEN *) mmt_malloc(sizeof(TOKEN));
tkn->primary = primary;
return tkn;
}
void free_token(TOKEN *tkn) {
if(tkn) {
switch(tkn->value.type) {
case V_STR:
if(tkn->value.string)
mmt_free(tkn->value.string);
tkn->value.string = NULL;
break;
}
mmt_free(tkn);
}
}
void free_tokens(int n, ...) {
TOKEN *t;
va_list ap;
va_start(ap, n);
while(n--) {
t = va_arg(ap, TOKEN *);
free_token(t);
}
va_end(ap);
}
TOKEN *new_number(const char *text) {
const char *errstr;
TOKEN *tkn = new_token(TKN_NUMBER);
tkn->value.type = V_NUM;
tkn->value.number = str_int(text, STR_INT32, &errstr);
if(errstr) {
yyerror("integer %s in config file: %s", text, errstr);
free_token(tkn);
return NULL;
}
#if DEBUG
msg_notice("NUMBER(%d)\n", tkn->value.number);
#endif
return tkn;
}
TOKEN *new_number_real(const char *text) {
char *endptr;
TOKEN *tkn = new_token(TKN_NUMBER);
tkn->value.type = V_NUM_REAL;
tkn->value.number_real = strtod(text, &endptr);
if(endptr == text || *endptr) {
yyerror("real number in config file %s: parsing error", text);
free_token(tkn);
return NULL;
}
#if DEBUG
msg_notice("NUMBER(%g)\n", tkn->value.number_real);
#endif
return tkn;
}
TOKEN *new_constant(const char *con) {
TOKEN *tkn = new_token(TKN_NUMBER);
int assigned = 1;
tkn->value.type = V_NUM;
tkn->value.number =
str_eq(con, "PM_SPM")? PM_SPM:
str_eq(con, "PM_TPI")? PM_TPI:
str_eq(con, "PM_ISP")? PM_ISP:
str_eq(con, "PM_PDI")? PM_PDI:
str_eq(con, "PM_UPDI")? PM_UPDI:
str_eq(con, "PM_HVSP")? PM_HVSP:
str_eq(con, "PM_HVPP")? PM_HVPP:
str_eq(con, "PM_debugWIRE")? PM_debugWIRE:
str_eq(con, "PM_JTAG")? PM_JTAG:
str_eq(con, "PM_JTAGmkI")? PM_JTAGmkI:
str_eq(con, "PM_XMEGAJTAG")? PM_XMEGAJTAG:
str_eq(con, "PM_AVR32JTAG")? PM_AVR32JTAG:
str_eq(con, "PM_aWire")? PM_aWire:
str_eq(con, "HAS_SUFFER")? HAS_SUFFER:
str_eq(con, "HAS_VTARG_SWITCH")? HAS_VTARG_SWITCH:
str_eq(con, "HAS_VTARG_ADJ")? HAS_VTARG_ADJ:
str_eq(con, "HAS_VTARG_READ")? HAS_VTARG_READ:
str_eq(con, "HAS_FOSC_ADJ")? HAS_FOSC_ADJ:
str_eq(con, "HAS_VAREF_ADJ")? HAS_VAREF_ADJ:
str_eq(con, "HAS_BITCLOCK_ADJ")? HAS_BITCLOCK_ADJ:
str_eq(con, "pseudo")? 2:
str_eq(con, "yes") || str_eq(con, "true")? 1: str_eq(con, "no") || str_eq(con, "false")? 0: (assigned = 0);
if(!assigned) {
yyerror("can't identify constant %s", con);
free_token(tkn);
return NULL;
}
#if DEBUG
msg_notice("CONSTANT(%s=%d)\n", con, tkn->value.number);
#endif
return tkn;
}
TOKEN *new_string(const char *text) {
TOKEN *tkn = new_token(TKN_STRING);
tkn->value.type = V_STR;
tkn->value.string = mmt_strdup(text);
#if DEBUG
msg_notice("STRING(%s)\n", tkn->value.string);
#endif
return tkn;
}
TOKEN *new_keyword(int primary) {
return new_token(primary);
}
void print_token(TOKEN *tkn) {
if(!tkn)
return;
msg_info("token = %d = ", tkn->primary);
switch(tkn->value.type) {
case V_NUM:
msg_info("NUMBER, value=%d", tkn->value.number);
break;
case V_NUM_REAL:
msg_info("NUMBER, value=%g", tkn->value.number_real);
break;
case V_STR:
msg_info("STRING, value=%s", tkn->value.string);
break;
default:
msg_info("<other>");
break;
}
msg_info("\n");
}
void pyytext(void) {
#if DEBUG
msg_notice("TOKEN: %s\n", yytext);
#endif
}
#ifdef HAVE_YYLEX_DESTROY
// Reset lexer and free any allocated memory
extern int yylex_destroy(void);
#endif
int read_config(const char *file) {
FILE *f;
int r;
if(!(cfg_infile = realpath(file, NULL))) {
pmsg_ext_error("cannot determine realpath() of config file %s: %s\n", file, strerror(errno));
return -1;
}
f = fopen(cfg_infile, "r");
if(f == NULL) {
pmsg_ext_error("cannot open config file %s: %s\n", cfg_infile, strerror(errno));
mmt_free(cfg_infile);
cfg_infile = NULL;
return -1;
}
cfg_lineno = 1;
yyin = f;
r = yyparse();
#ifdef HAVE_YYLEX_DESTROY
// Reset lexer and free any allocated memory
yylex_destroy();
#endif
fclose(f);
if(cfg_infile) {
mmt_free(cfg_infile);
cfg_infile = NULL;
}
return r;
}
// Adapted version of a neat empirical hash function from comp.lang.c by Daniel Bernstein
unsigned strhash(const char *str) {
unsigned c, hash = 5381, n = 0;
while((c = (unsigned char) *str++) && n++ < 20)
hash = 33*hash ^ c;
return hash;
}
// Return a copy of the argument as hashed string
const char *cache_string(const char *p) {
int h, k;
char **hs;
if(!p)
p = "(NULL)";
h = strhash(p)%(sizeof cx->cfg_hstrings/sizeof *cx->cfg_hstrings);
if(!(hs = cx->cfg_hstrings[h]))
hs = cx->cfg_hstrings[h] = (char **) mmt_realloc(NULL, (16 + 1)*sizeof **cx->cfg_hstrings);
for(k = 0; hs[k]; k++)
if(*p == *hs[k] && str_eq(p, hs[k]))
return hs[k];
if(k && k%16 == 0)
cx->cfg_hstrings[h] = (char **) mmt_realloc(cx->cfg_hstrings[h], (k + 16 + 1)*sizeof **cx->cfg_hstrings);
cx->cfg_hstrings[h][k + 1] = NULL;
return cx->cfg_hstrings[h][k] = mmt_strdup(p);
}
COMMENT *locate_comment(const LISTID comments, const char *where, int rhs) {
if(comments)
for(LNODEID ln = lfirst(comments); ln; ln = lnext(ln)) {
COMMENT *n = ldata(ln);
if(n && rhs == n->rhs && n->kw && str_eq(where, n->kw))
return n;
}
return NULL;
}
static void addcomment(int rhs) {
if(cx->cfg_lkw) {
COMMENT *node = mmt_malloc(sizeof(*node));
node->rhs = rhs;
node->kw = mmt_strdup(cx->cfg_lkw);
node->comms = cx->cfg_comms;
cx->cfg_comms = NULL;
if(!cx->cfg_strctcomms)
cx->cfg_strctcomms = lcreat(NULL, 0);
ladd(cx->cfg_strctcomms, node);
}
}
// Capture prologue during parsing (triggered by lexer.l)
void cfg_capture_prologue(void) {
cx->cfg_prologue = cx->cfg_comms;
cx->cfg_comms = NULL;
}
LISTID cfg_get_prologue(void) {
return cx->cfg_prologue;
}
// Captures comments during parsing
void capture_comment_str(const char *com, int lineno) {
if(!cx->cfg_comms)
cx->cfg_comms = lcreat(NULL, 0);
ladd(cx->cfg_comms, mmt_strdup(com));
// Last keyword lineno is the same as this comment's
if(cx->cfg_lkw && cx->cfg_lkw_lineno == lineno)
addcomment(1); // Register comms to show right of lkw = ...;
}
// Capture assignments (keywords left of =) and associate comments to them
void capture_lvalue_kw(const char *kw, int lineno) {
if(str_eq(kw, "memory")) { // Push part comments and start memory comments
if(!cx->cfg_pushed) { // config_gram.y pops the part comments
cx->cfg_pushed = 1;
cx->cfg_pushedcomms = cx->cfg_strctcomms;
cx->cfg_strctcomms = NULL;
}
}
if(str_eq(kw, "programmer") || str_eq(kw, "serialadapter") || str_eq(kw, "part") || str_eq(kw, "memory"))
kw = "*"; // Show comment before programmer/part/memory
if(cx->cfg_lkw)
mmt_free(cx->cfg_lkw);
cx->cfg_lkw = mmt_strdup(kw);
cx->cfg_lkw_lineno = lineno;
if(cx->cfg_comms) // Accrued list of # one-line comments
addcomment(0); // Register comment to appear before lkw assignment
}
// config_gram.y calls this once for each programmer/part/memory structure
LISTID cfg_move_comments(void) {
capture_lvalue_kw(";", -1);
LISTID ret = cx->cfg_strctcomms;
cx->cfg_strctcomms = NULL;
return ret;
}
// config_gram.y calls this after ingressing the memory structure
void cfg_pop_comms(void) {
if(cx->cfg_pushed) {
cx->cfg_pushed = 0;
cx->cfg_strctcomms = cx->cfg_pushedcomms;
}
}
// Convert the next n hex digits of s to a hex number
static unsigned int tohex(const unsigned char *s, unsigned int n) {
int ret, c;
ret = 0;
while(n--) {
ret *= 16;
c = *s++;
ret += c >= '0' && c <= '9'? c - '0': c >= 'a' && c <= 'f'? c - 'a' + 10: c - 'A' + 10;
}
return ret;
}
/*
* Create a utf-8 character sequence from a single unicode character.
* Permissive for some invalid unicode sequences but not for those with high
* bit set). Returns numbers of characters written (0-6).
*/
static int wc_to_utf8str(unsigned int wc, unsigned char *str) {
if(!(wc & ~0x7fu)) {
*str = (char) wc;
return 1;
}
if(!(wc & ~0x7ffu)) {
*str++ = (char) ((wc >> 6) | 0xc0);
*str++ = (char) ((wc & 0x3f) | 0x80);
return 2;
}
if(!(wc & ~0xffffu)) {
*str++ = (char) ((wc >> 12) | 0xe0);
*str++ = (char) (((wc >> 6) & 0x3f) | 0x80);
*str++ = (char) ((wc & 0x3f) | 0x80);
return 3;
}
if(!(wc & ~0x1fffffu)) {
*str++ = (char) ((wc >> 18) | 0xf0);
*str++ = (char) (((wc >> 12) & 0x3f) | 0x80);
*str++ = (char) (((wc >> 6) & 0x3f) | 0x80);
*str++ = (char) ((wc & 0x3f) | 0x80);
return 4;
}
if(!(wc & ~0x3ffffffu)) {
*str++ = (char) ((wc >> 24) | 0xf8);
*str++ = (char) (((wc >> 18) & 0x3f) | 0x80);
*str++ = (char) (((wc >> 12) & 0x3f) | 0x80);
*str++ = (char) (((wc >> 6) & 0x3f) | 0x80);
*str++ = (char) ((wc & 0x3f) | 0x80);
return 5;
}
if(!(wc & ~0x7fffffffu)) {
*str++ = (char) ((wc >> 30) | 0xfc);
*str++ = (char) (((wc >> 24) & 0x3f) | 0x80);
*str++ = (char) (((wc >> 18) & 0x3f) | 0x80);
*str++ = (char) (((wc >> 12) & 0x3f) | 0x80);
*str++ = (char) (((wc >> 6) & 0x3f) | 0x80);
*str++ = (char) ((wc & 0x3f) | 0x80);
return 6;
}
return 0;
}
/*
* Unescape C-style strings, destination d must hold enough space (and can be source s).
* Returns the number of characters in destination *excluding* the terminating 0 from s.
* The destination can hold nul characters owing to, eg, \x00 or \000 escape sequences.
*/
size_t cfg_unescapen(unsigned char *d, const unsigned char *s) {
unsigned char *dstart = d;
int n, k;
while(*s) {
switch(*s) {
case '\\':
switch(*++s) {
case '\n': // String continuation over new line
#if '\n' != '\r'
case '\r':
#endif
--d;
break;
case 'n':
*d = '\n';
break;
case 't':
*d = '\t';
break;
case 'a':
*d = '\a';
break;
case 'b':
*d = '\b';
break;
case 'e': // Non-standard ESC
*d = 27;
break;
case 'f':
*d = '\f';
break;
case 'r':
*d = '\r';
break;
case 'v':
*d = '\v';
break;
case '?':
*d = '?';
break;
case '`':
*d = '`';
break;
case '"':
*d = '"';
break;
case '\'':
*d = '\'';
break;
case '\\':
*d = '\\';
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7': // 1-3 octal digits
n = *s - '0';
for(k = 0; k < 2 && s[1] >= '0' && s[1] <= '7'; k++) // Max 2 more octal characters
n *= 8, n += s[1] - '0', s++;
*d = n;
break;
case 'x': // Unlimited hex digits
for(k = 0; isxdigit(s[k + 1]); k++)
continue;
if(k > 0) {
*d = tohex(s + 1, k);
s += k;
} else { // No hex digits after \x? copy \x
*d++ = '\\';
*d = 'x';
}
break;
case 'u': // Exactly 4 hex digits and valid unicode
if(isxdigit(s[1]) && isxdigit(s[2]) && isxdigit(s[3]) && isxdigit(s[4]) &&
(n = wc_to_utf8str(tohex(s + 1, 4), d))) {
d += n - 1;
s += 4;
} else { // Invalid \u sequence? copy \u
*d++ = '\\';
*d = 'u';
}
break;
case 'U': // Exactly 6 hex digits and valid unicode
if(isxdigit(s[1]) && isxdigit(s[2]) && isxdigit(s[3]) && isxdigit(s[4]) && isxdigit(s[5]) && isxdigit(s[6]) &&
(n = wc_to_utf8str(tohex(s + 1, 6), d))) {
d += n - 1;
s += 6;
} else { // Invalid \U sequence? copy \U
*d++ = '\\';
*d = 'U';
}
break;
default: // Keep the escape sequence (C would warn and remove \)
*d++ = '\\';
*d = *s;
}
break;
default: // Not an escape sequence: just copy the character
*d = *s;
}
d++;
s++;
}
*d = *s; // Terminate
return d - dstart;
}
// Unescape C-style strings, destination d must hold enough space (and can be source s)
unsigned char *cfg_unescapeu(unsigned char *d, const unsigned char *s) {
(void) cfg_unescapen(d, s);
return d;
}
// Unescape C-style strings, destination d must hold enough space (and can be source s)
char *cfg_unescape(char *d, const char *s) {
return (char *) cfg_unescapeu((unsigned char *) d, (const unsigned char *) s);
}
// Returns the number of characters that a unicode character would need (0-6)
static int utf8width(wint_t wc) {
if(!(wc & ~0x7fu))
return 1;
if(!(wc & ~0x7ffu))
return 2;
if(!(wc & ~0xffffu))
return 3;
if(!(wc & ~0x1fffffu))
return 4;
if(!(wc & ~0x3ffffffu))
return 5;
if(!(wc & ~0x7fffffffu))
return 6;
return 0;
}
// Given the first byte c of a character sequence, how long is the sequence going to be?
static int utf8headlen(int c) {
return (c & 0xe0) == 0xc0? 2:
(c & 0xf0) == 0xe0? 3:
(c & 0xf8) == 0xf0? 4: (c & 0xfc) == 0xf8? 5: (c & 0xfe) == 0xfc? 6: 1 /* not a utf8 header byte */ ;
}
/*
* Return the next unicode character from a utf-8 string str with at least n
* characters and record the length of the utf-8 string eaten in *lenp. Returns
* U+FFFD (illegal char) if parsing does not go well
*/
static wint_t nextutf8char(const char *str, int n, int *lenp) {
int j, utf8, len;
wint_t c, wc = 0;
c = str[0] & 0xff;
if(!(c & 0x80)) { // Simple ASCII - all done
if(lenp)
*lenp = 1;
return c;
}
utf8 = 0; // Possible UTF-8 character, convert to wint_t
len = utf8headlen((int) c);
if(len > 1 && len <= n) {
switch(len) {
case 2:
wc = c & 0x1f;
break;
case 3:
wc = c & 0xf;
break;
case 4:
wc = c & 0x7;
break;
case 5:
wc = c & 0x3;
break;
case 6:
wc = c & 0x1;
break;
}
for(utf8 = 1, j = 1; j < len; j++) {
if((str[j] & 0xc0) != 0x80) {
utf8 = 0;
break;
}
wc = (wc << 6) + (str[j] & 0x3f);
}
}
if(utf8 && utf8width(wc) != len) // Sequence code was longer than needed be, make invalid
utf8 = 0;
if(!utf8)
len = 1, wc = 0xFFFD; // Illegal character
if(lenp)
*lenp = len;
return wc;
}
// Return an mmt_malloc'd escaped string that looks like a C-style input string incl quotes
char *cfg_escape(const char *s) {
char buf[50*1024], *d = buf;
*d++ = '"';
for(; *s && d - buf < (long) sizeof buf - 10; s++) {
switch(*s) {
case '\n':
*d++ = '\\';
*d++ = 'n';
break;
case '\t':
*d++ = '\\';
*d++ = 't';
break;
case '\a':
*d++ = '\\';
*d++ = 'a';
break;
case '\b':
*d++ = '\\';
*d++ = 'b';
break;
case '\f':
*d++ = '\\';
*d++ = 'f';
break;
#if '\r' != '\n'
case '\r':
*d++ = '\\';
*d++ = 'r';
break;
#endif
case '\v':
*d++ = '\\';
*d++ = 'v';
break;
case '\"':
*d++ = '\\';
*d++ = '\"';
break;
default:
if(*s & 0x80) { // Check for utf8-sequences
int chrlen;
if(0xFFFD == nextutf8char(s, strlen(s), &chrlen)) { // Invalid UTF-8
sprintf(d, "\\%03o", *s & 0xff);
d += strlen(d);
} else { // Copy over valid UTF-8 character
memcpy(d, s, chrlen);
d += chrlen;
s += chrlen - 1;
}
} else if(*s == 0x7f || (unsigned char) *s < 32) {
sprintf(d, "\\%03o", *s);
d += strlen(d);
} else
*d++ = *s;
}
}
*d++ = '"';
*d = 0;
return mmt_strdup(buf);
}
static int cmp_comp(const void *v1, const void *v2) {
const Component *c1 = v1, *c2 = v2;
int ret = strcmp(c1->name, c2->name);
return ret? ret: c1->strct - c2->strct;
}
Component *cfg_comp_search(const char *name, int strct) {
Component key;
if(!cx->cfg_init_search++)
qsort(avr_comp, sizeof avr_comp/sizeof *avr_comp, sizeof(Component), cmp_comp);
key.name = name;
key.strct = strct;
return bsearch(&key, avr_comp, sizeof avr_comp/sizeof *avr_comp, sizeof(Component), cmp_comp);
}
const char *cfg_strct_name(int strct) {
switch(strct) {
case COMP_CONFIG_MAIN:
return "avrdude.conf main";
case COMP_AVRPART:
return "AVRPART";
case COMP_AVRMEM:
return "AVRMEM";
case COMP_PROGRAMMER:
return "PROGRAMMER";
}
return "unknown struct";
}
const char *cfg_v_type(int type) {
switch(type) {
case V_NONE:
return "void";
case V_NUM:
return "number";
case V_NUM_REAL:
return "real";
case V_STR:
return "string";
case V_COMPONENT:
return "component";
}
return "unknown v type";
}
const char *cfg_comp_type(int type) {
switch(type) {
case COMP_INT:
return "number";
case COMP_SHORT:
return "short";
case COMP_CHAR:
return "char";
case COMP_BOOL:
return "bool";
case COMP_STRING:
return "string";
case COMP_CHAR_ARRAY:
return "byte array";
case COMP_INT_LISTID:
return "number list";
case COMP_STRING_LISTID:
return "string list";
case COMP_OPCODE:
return "opcode";
case COMP_PIN:
return "pin";
case COMP_PIN_LIST:
return "pin list";
}
return "unknown comp type";
}
// Used by config_gram.y to assign a component in one of the relevant structures with a value
void cfg_assign(char *sp, int strct, Component *cp, VALUE *v) {
const char *str;
int num;
switch(cp->type) {
case COMP_BOOL:
case COMP_CHAR:
case COMP_SHORT:
case COMP_INT:
if(v->type != V_NUM) {
yywarning("%s in %s expects a %s but is assigned a %s",
cp->name, cfg_strct_name(strct), cfg_comp_type(cp->type), cfg_v_type(v->type));
return;
}
// TODO: consider endianness (code currently assumes little endian)
num = v->number;
memcpy(sp + cp->offset, &num, cp->size);
break;
case COMP_STRING:
if(v->type != V_STR) {
yywarning("%s in %s expects a string but is assigned a %s",
cp->name, cfg_strct_name(strct), cfg_v_type(v->type));
return;
}
str = cache_string(v->string);
memcpy(sp + cp->offset, &str, cp->size);
break;
// TODO: implement COMP_CHAR_ARRAY, COMP_INT_LISTID, COMP_STRING_LISTID, ...
default:
yywarning("%s in %s expects a %s but that is not implemented",
cp->name, cfg_strct_name(strct), cfg_comp_type(cp->type));
}
}
// Automatically assign an mcuid if known from avrintel.c table
void cfg_update_mcuid(AVRPART *part) {
// Don't assign an mcuid for template parts that has a space in desc
if(!part->desc || *part->desc == 0 || strchr(part->desc, ' '))
return;
// Don't assign an mcuid for template parts where id starts with "."
if(!part->id || !*part->id || *part->id == '.')
return;
// Don't assign an mcuid for 32-bit AVR parts
if(is_awire(part))
return;
// Find an entry that shares the same name, overwrite mcuid with known, existing mcuid
for(size_t i = 0; i < sizeof uP_table/sizeof *uP_table; i++) {
if(str_caseeq(part->desc, uP_table[i].name)) {
if(part->mcuid != (int) uP_table[i].mcuid) {
if(part->mcuid >= 0 && verbose >= MSG_DEBUG)
yywarning("overwriting mcuid of part %s to be %d", part->desc, uP_table[i].mcuid);
part->mcuid = uP_table[i].mcuid;
}
return;
}
}
// None have the same name: an entry with part->mcuid might be an error
for(size_t i = 0; i < sizeof uP_table/sizeof *uP_table; i++)
if(part->mcuid == (int) uP_table[i].mcuid) {
// Complain unless it can be considered a variant, eg, ATmega32L and ATmega32
AVRMEM *flash = avr_locate_flash(part);
if(flash) {
size_t l1 = strlen(part->desc), l2 = strlen(uP_table[i].name);
if(strncasecmp(part->desc, uP_table[i].name, l1 < l2? l1: l2) ||
flash->size != uP_table[i].flashsize ||
flash->page_size != uP_table[i].pagesize || part->n_interrupts != (int8_t) uP_table[i].ninterrupts)
yywarning("mcuid %d is reserved for %s, use a free number >= %d",
part->mcuid, uP_table[i].name, sizeof uP_table/sizeof *uP_table);
}
return;
}
// Range check
if(part->mcuid < 0 || part->mcuid >= UB_N_MCU)
yywarning("mcuid %d for %s is out of range [0..%d], use a free number >= %d",
part->mcuid, part->desc, UB_N_MCU - 1, sizeof uP_table/sizeof *uP_table);
}