From 784eda6ff3a665404c00f3f4c3e0761ce04cdd85 Mon Sep 17 00:00:00 2001 From: Stefan Rueger Date: Mon, 22 May 2023 23:18:26 +0100 Subject: [PATCH] Provide terminal config command to set fuses and lock bits Example: $ avrdude -c dryrun -p AVR32DA32 -t avrdude: AVR device initialized and ready to accept instructions avrdude: device signature = 0x1e9533 (probably avr32da32) avrdude> c config period=t_off # 0 config window=t_off # 0 config sleep=bod_disabled # 0 config active=bod_disabled # 0 config sampfreq=sf_128hz # 0 config lvl=bod_1v9 # 0 config clksel=oschf # 0 config eesave=eex_erased # 0 config rstpincfg=gpio # 0 config crcsel=crc16 # 0 config crcsrc=nocrc # 0b11 = 3 config sut=sut_0ms # 0 config codesize=0 # 0 config bootsize=0 # 0 config key=nolock # 0x5cc5c55c avrdude> c active= -vv config active=bod_disabled # 0 (brownout detection disabled) avrdude> c sleep=3 avrdude warning: (config) assigning a reserved value (0x03) to sleep, check data sheet avrdude> c -v sleep= config sleep=0b11 # reserved = 3 avrdude> c sleep=bod_cont avrdude> c -? Syntax: config { | [=]} Function: Show or change configuration properties of the part Options: -f show value of fuse and lock bit memories as well -a output an initialisation script with all possible assignments -v increase verbosity, show explanations alongside output -h show this help message Config alone shows all property names and current settings of the part's hardware configuration in terms of symbolic mnemonics or values. Use avrdude> config to show that of . Wildcards or initial strings are permitted (but not both), in which case all settings of matching properties are displayed. avrdude> config = shows all possible values that can take on with the currently set one being the only that is not commented out. Assignments avrdude> config = modify the corresponding fuse or lock bits immediately but will normally only take effect the next time the part is reset (see the data sheet). Value can be a valid integer or one of the symbolic mnemonics, if known. Wildcards or initial strings are permitted for the mnemonic, but an assignment only happens if both the property and the name can be uniquely resolved. It is quite possible, as is with direct writing to the underlying fuse and lock bits, to brick a part, i.e., make it unresponsive to further programming with the chosen programmer: here be dragons. --- src/term.c | 573 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 572 insertions(+), 1 deletion(-) diff --git a/src/term.c b/src/term.c index de0b291c..cd67d910 100644 --- a/src/term.c +++ b/src/term.c @@ -32,6 +32,9 @@ #include #include +#include "libavrdude.h" +#include "avrintel.h" + #if defined(HAVE_LIBREADLINE) #include #include @@ -67,6 +70,7 @@ static int cmd_flush (PROGRAMMER *pgm, AVRPART *p, int argc, char *argv[]); static int cmd_abort (PROGRAMMER *pgm, AVRPART *p, int argc, char *argv[]); static int cmd_erase (PROGRAMMER *pgm, AVRPART *p, int argc, char *argv[]); static int cmd_pgerase(PROGRAMMER *pgm, AVRPART *p, int argc, char *argv[]); +static int cmd_config (PROGRAMMER *pgm, AVRPART *p, int argc, char *argv[]); static int cmd_sig (PROGRAMMER *pgm, AVRPART *p, int argc, char *argv[]); static int cmd_part (PROGRAMMER *pgm, AVRPART *p, int argc, char *argv[]); static int cmd_help (PROGRAMMER *pgm, AVRPART *p, int argc, char *argv[]); @@ -92,6 +96,7 @@ struct command cmd[] = { { "abort", cmd_abort, _fo(reset_cache), "abort flash and EEPROM writes, ie, reset the r/w cache" }, { "erase", cmd_erase, _fo(chip_erase_cached), "perform a chip or memory erase" }, { "pgerase", cmd_pgerase, _fo(page_erase), "erase one page of flash or EEPROM memory" }, + { "config", cmd_config, _fo(open), "change or show configuration properties of the part" }, { "sig", cmd_sig, _fo(open), "display device signature bytes" }, { "part", cmd_part, _fo(open), "display the current part information" }, { "send", cmd_send, _fo(cmd), "send a raw command to the programmer" }, @@ -779,7 +784,7 @@ static int cmd_erase(PROGRAMMER *pgm, AVRPART *p, int argc, char *argv[]) { static int cmd_pgerase(PROGRAMMER *pgm, AVRPART *p, int argc, char *argv[]) { - if(argc < 3 || (argc > 1 && str_eq(argv[1], "-?"))) { + if(argc != 3 || (argc > 1 && str_eq(argv[1], "-?"))) { msg_error( "Syntax: pgerase \n" "Function: erase one page of flash or EEPROM memory\n" @@ -821,6 +826,572 @@ static int cmd_pgerase(PROGRAMMER *pgm, AVRPART *p, int argc, char *argv[]) { } +// Config command + +typedef union { // Lock memory can be 1 or 4 bytes + uint8_t b[4]; + uint32_t i; +} fl_t; + +typedef struct { // Fuses and lock bits + uint8_t fuses[16]; + uint32_t lock; + int fread[16], lread; + int islock; + uint32_t current; +} Fusel_t; + +typedef struct { + const Configitem_t *t; // Configuration bitfield table + const char *memstr; // Could be "lockbits" + const char *alt; // Set when memstr is an alias + int match; // Matched by user request + int ok, val, initval; // Has value val been read OK? Initval == -1 if not known +} Cfg_t; + +typedef struct { + int verb, allscript, flheaders; +} Cfg_opts_t; + +// Cache the contents of the fuse and lock bits memories that a particular Configitem is involved in +static int getfusel(PROGRAMMER *pgm, AVRPART *p, Fusel_t *fl, const Cfg_t *cci, const char **errpp) { + const char *err = NULL; + char *tofree; + int islock; + + islock = str_starts(cci->memstr, "lock"); + if((islock && cci->t->memoffset != 0) || (!islock && (cci->t->memoffset < 0 || cci->t->memoffset >= (int) sizeof fl->fuses))) { + err = cache_string(tofree = str_sprintf("%s's %s has invalid memoffset %d", p->desc, cci->memstr, cci->t->memoffset)); + free(tofree); + goto back; + } + + if(islock && fl->lread) { // Cached lock OK + fl->current = fl->lock; + fl->islock = 1; + goto back; + } + + if(!islock && fl->fread[cci->t->memoffset]) { // Cached fuse OK + fl->current = fl->fuses[cci->t->memoffset]; + fl->islock = 0; + goto back; + } + + AVRMEM *mem = avr_locate_mem(p, cci->memstr); + if(!mem) { + err = cache_string(tofree = str_sprintf("%s memory type not defined for part %s", cci->memstr, p->desc)); + free(tofree); + goto back; + } + + if((islock && mem->size != 4 && mem->size != 1) || (!islock && mem->size != 1)) { + err = cache_string(tofree = str_sprintf("%s's %s memory has unexpected size %d", p->desc, mem->desc, mem->size)); + free(tofree); + goto back; + } + + fl_t m = {.i = 0}; + for(int i=0; isize; i++) + if(pgm->read_byte(pgm, p, mem, i, m.b+i) < 0) { + err = cache_string(tofree = str_sprintf("cannot read %s's %s memory", p->desc, mem->desc)); + free(tofree); + goto back; + } + + if(islock) { + fl->lock = m.i; + fl->lread = 1; + } else { + fl->fread[cci->t->memoffset] = 1; + fl->fuses[cci->t->memoffset] = *m.b; + } + fl->islock = islock; + fl->current = m.i; + +back: + if(err && errpp) + *errpp = err; + return err? -1: 0; +} + +static int setmatches(const char *str, int n, Cfg_t *cc) { + int matches = 0; + + if(!*str) + return 0; + + for(int i=0; iname, str) || str_match(str, cc[i].t->name)) { + cc[i].match = 1; + matches++; + if(str_eq(cc[i].t->name, str)) { + for(int j=0; jname, cc[i].memstr, errstr); + return -1; + } + // Update fuse intell + for(int fj=0; fjcurrent; + + cc[i].val = (cc->t[i].mask & fuselp->current) >> cc->t[i].lsh; + + return 0; +} + +static char *valuecomment(const Configitem_t *cti, const Valueitem_t *vp, int value, Cfg_opts_t o) { + static char buf[512], bin[129]; + unsigned u = value; + int lsh = cti->lsh; + + if(!vp && cti->vlist) // No symbolic value despite symbol list? + strcpy(buf, "reserved"); // Enter reserved instead of the number + else if(u < 256) // Show as binary + sprintf(buf, "%s%s", u > 1? "0b": "", str_utoa(u, bin, 2)); + else // Show as hex + sprintf(buf, "0x%04x", u); + + if(u > 1 && u < 256) + sprintf(buf+strlen(buf), u<8? " = %d": " = 0x%02x", u); + if(lsh && value > 0 && (o.allscript || o.verb > 1)) { + u <<= lsh; + sprintf(buf+strlen(buf), u<8? " = %d >> %d": " = 0x%02x >> %d", u, lsh); + } + if((vp || !cti->vlist) && o.verb > 1) { + const char *vcom = !cti->vlist? "arbitrary": vp->vcomment; + snprintf(buf+strlen(buf), 256, " (%s)", vcom); + } + return buf; +} + +static void printoneproperty(Cfg_t *cc, int ii, const Valueitem_t *vp, const char *vstr, Cfg_opts_t o) { + int value = vp? vp->value: cc[ii].val; + term_out("%sconfig %s=%s # %s\n", vp && cc[ii].val != vp->value? "# ": "", + cc[ii].t->name, vstr, valuecomment(cc[ii].t, vp, value, o)); +} + +static void printproperty(Cfg_t *cc, int ii, Cfg_opts_t o, int allv) { + const Valueitem_t *vt = cc[ii].t->vlist, *vp; + int nv = cc[ii].t->nvalues; + char buf[131], bin[129]; + const char *ccom = cc->t[ii].ccomment, *col = strchr(ccom, ':'); + + if(o.verb > 0) { + const char *vcom = !cc[ii].t->vlist? "arbitrary": vp? vp->vcomment: ""; + // Remove some redundancy in explanations + int cclen = col && str_ends(vcom, col+1)? (int) (col-ccom-1): (int) strlen(ccom); + + if(o.verb > 1) + term_out("# Mask 0x%02x of %s: %.*s\n", cc->t[ii].mask, cc[ii].memstr, cclen, ccom); + else if(*cc[ii].t->ccomment) + term_out("# %c%.*s\n", toupper(*cc[ii].t->ccomment), cclen-1, cc[ii].t->ccomment+1); + else + term_out("# %s\n", cc[ii].t->name); + } + + int done = 0; + if(allv && vt) { + for(int j=0; j 1? "0b": "", str_utoa(u, bin, 2)); + else + sprintf(buf, "0x%04x", u); + vstr = buf; + } + printoneproperty(cc, ii, vp, vstr, o); +} + +static void printfuse(Cfg_t *cc, int ii, Flock_t *fc, int nf, int printed, Cfg_opts_t o) { + char buf[512]; + int fj; + for(fj=0; fj 0; ) { // Simple option parsing + char *q; + if(*(q=argv[++ai]) != '-' || !q[1]) + argv[itemac++] = argv[ai]; + else { + while(*++q) { + switch(*q) { + case '?': + case 'h': + help++; + break; + case 'v': + o.verb++; + break; + case 'a': + o.allscript++; // Fall through + case 'f': + o.flheaders++; + break; + default: + if(!invalid++) + pmsg_error("(config) invalid option %c, see usage:\n", *q); + q = "x"; + } + } + } + } + argc = itemac; // (arg,c argv) still valid but options have been removed + + if(o.allscript && argc > 1) + pmsg_error("(config) -a does not allow any further arguments\n"); + + if(argc > 2 || help || invalid || (argc >1 && o.allscript)) { + msg_error( + "Syntax: config { | [=]}\n" + "Function: Show or change configuration properties of the part\n" + "Options:\n" + " -f show value of fuse and lock bit memories as well\n" + " -a output an initialisation script with all possible assignments\n" + " -v increase verbosity, show explanations alongside output\n" + " -h show this help message\n" + "\n" + "Config alone shows all property names and current settings of the part's\n" + "hardware configuration in terms of symbolic mnemonics or values. Use\n" + "\n" + "avrdude> config \n" + "\n" + "to show that of . Wildcards or initial strings are permitted (but\n" + "not both), in which case all settings of matching properties are displayed.\n" + "\n" + "avrdude> config =\n" + "\n" + "shows all possible values that can take on with the currently\n" + "set one being the only that is not commented out. Assignments\n" + "\n" + "avrdude> config =\n" + "\n" + "modify the corresponding fuse or lock bits immediately but will normally only\n" + "take effect the next time the part is reset (see the data sheet). Value can\n" + "be a valid integer or one of the symbolic mnemonics, if known. Wildcards or\n" + "initial strings are permitted for the mnemonic, but an assignment only\n" + "happens if both the property and the name can be uniquely resolved.\n" + "\n" + "It is quite possible, as is with direct writing to the underlying fuse and\n" + "lock bits, to brick a part, i.e., make it unresponsive to further programming\n" + "with the chosen programmer: here be dragons.\n" + ); + return !help || invalid? -1: 0; + } + + int idx = -1; // Index in uP_table[] + const Configitem_t *ct; // Configuration bitfield table + int nc; // Number of config properties, some may not be available + Fusel_t fusel; // Copy of fuses and lock bits + const Valueitem_t *vt; // Pointer to symbolic labels and associated values + int nv; // Number of symbolic labels + Cfg_t *cc; // Current configuration; cc[] and ct[] are parallel arrays + Flock_t *fc; // Current fuse and lock bits memories + int nf = 0; // Number of involved fuse and lock bits memories + + memset(&fusel, 0, sizeof fusel); + + if(p->mcuid >= 0) + idx = upidxmcuid(p->mcuid); + if(idx < 0 && p->desc && *p->desc) + idx = upidxname(p->desc); + if(idx < 0) { + pmsg_error("uP_table neither knows mcuid %d nor part %s\n", + p->mcuid, p->desc && *p->desc? p->desc: "???"); + return -1; + } + nc = uP_table[idx].nconfigs; + ct = uP_table[idx].cfgtable; + if(nc <= 0 || !ct) { + pmsg_error("part %s does not have a configuration table\n", p->desc); + return -1; + } + + int ret = 0; + cc = cfg_malloc(__func__, sizeof *cc*nc); + fc = cfg_malloc(__func__, sizeof *fc*nc); + + char *locktype = "lock"; + if(!avr_locate_mem(p, "lock") && avr_locate_mem(p, "lockbits")) + locktype = "lockbits"; + for(int i=0; idesc); + continue; + } + cc[i].ok = 1; + cc[i].alt = str_eq(mem->desc, mt)? NULL: mem->desc; + cc[i].initval = mem->initval; + if(!nf || !str_eq(fc[nf-1].memstr, mt)) + fc[nf++].memstr = mt; + if(fc[nf-1].mask & ct[i].mask) { // This should not happen + pmsg_error("overlapping bit values of %s mask 0x%02x in %s's %s\n", cc[i].t->name, ct[i].mask, p->desc, cc[i].memstr); + ret = -1; + goto finished; + } + fc[nf-1].mask |= ct[i].mask; + } + + char *item = argc < 2? "*": argv[1]; + + char *rhs = strchr(item, '='); + if(rhs) // Right-hand side of assignment + *rhs++ = 0; // Terminate lhs + + int nm = setmatches(item, nc, cc); + if(nm == 0) { + pmsg_warning("non-matching %s; known config items are:\n", argv[1]); + for(int i=0; iname); + ret = -1; + goto finished; + } + + if(!rhs || !*rhs || o.allscript) { // Show (all possible) values + const char *lastfuse = "not a fuse"; + for(int printed = 0, i = 0; i < nc; i++) { + if(!cc[i].match || !cc[i].ok) + continue; + if(gatherval(pgm, p, cc, i, &fusel, fc, nf) < 0) { + for(int ii=i+1; ii 1)) + term_out("\n"); + printproperty(cc, i, o, (rhs && !*rhs) || o.allscript); + printed = 1; + } + goto finished; + } + + // Non-empty rhs: attempt assignment + + if(nm > 1) { + pmsg_warning("ambiguous %s=...; known config items are:\n", argv[1]); + for(int i=0; iname); + ret = -1; + goto finished; + } + + int ci; + for(ci = 0; ci < nc; ci++) + if(cc[ci].match) + break; + + if(ci == nc) { + pmsg_error("unexpected failure to find match index\n"); + ret = -1; + goto finished; + } + + // ci is fixed now: save what we have for sanity check + Cfg_t safecc = cc[ci]; + + nv = ct[ci].nvalues; + vt = ct[ci].vlist; + + // Assignment can be an integer or symbolic value + + // Have checked for digit to exclude Roman numbers here + const char *errptr; + int toassign = str_int(rhs, STR_UINT32, &errptr); + if(!errptr) { + // All good; on error match against symbols + } else if(!vt) { + pmsg_error("(config) no symbols known: assign an appropriate number\n"); + ret = -1; + goto finished; + } else { // Alternatively, assignment can be one of the symbols + int vj = getvalidx(rhs, nv, vt); + if(vj < 0) { // Print error msg to stderr + pmsg_warning("%s %s; known %s symbols are:\n", vj == -1? "non-matching": "ambiguous", + rhs, cc[ci].t->name); + for(int j=0; jname, vt[j].label, valuecomment(ct+ci, vt+j, vt[j].value, o)); + } + ret = -1; + goto finished; + } + toassign = vt[vj].value; + } + + if((toassign<>ct[ci].lsh); + ret = -1; + goto finished; + } + + // Check with safe copies of ct[ci] and cc[ci] + if(memcmp(&safecc, cc+ci, sizeof *cc)) { + pmsg_error("unexpected data changes (this should never happen)\n"); + ret = -1; + goto finished; + } + + if(vt) { + int j; + for(j=0; jname, cc[ci].memstr, errstr); + ret = -1; + goto finished; + } + + fl_t towrite; + towrite.i = (fusel.current & ~ct[ci].mask) | (toassign<desc); + ret = -1; + goto finished; + } + if((fusel.islock && mem->size != 4 && mem->size != 1) || (!fusel.islock && mem->size != 1)) { + pmsg_error("%s's %s memory has unexpected size %d\n", p->desc, mem->desc, mem->size); + ret = -1; + goto finished; + } + for(int i=0; isize; i++) + if(pgm->write_byte(pgm, p, mem, i, towrite.b[i]) < 0) { + pmsg_error("cannot write to %s's %s memory\n", p->desc, mem->desc); + ret = -1; + goto finished; + } + + const char *av[] = { "confirm", cc[ci].t->name }; + if(o.verb > 0 && !str_eq(argv[0], "confirm")) + cmd_config(pgm, p, 2, (char **) av); + +finished: + free(cc); + free(fc); + + return ret; +} + + static int cmd_part(PROGRAMMER *pgm, AVRPART *p, int argc, char *argv[]) { if(argc > 1) { msg_error(