2022-06-28 19:14:29 +00:00
|
|
|
/*
|
|
|
|
* avrdude - A Downloader/Uploader for AVR device programmers
|
|
|
|
* Copyright (C) 2022, Stefan Rueger <smr@theblueorange.space>
|
|
|
|
*
|
|
|
|
* 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/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* $Id$ */
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Code to program an Atmel AVR device through one of the supported
|
|
|
|
* programmers.
|
|
|
|
*
|
|
|
|
* For parallel port connected programmers, the pin definitions can be
|
|
|
|
* changed via a config file. See the config file for instructions on
|
|
|
|
* how to add a programmer definition.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "ac_cfg.h"
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <whereami.h>
|
|
|
|
#include <stdarg.h>
|
|
|
|
#include <errno.h>
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <limits.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <time.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <ctype.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <sys/time.h>
|
|
|
|
|
|
|
|
#include "avrdude.h"
|
|
|
|
#include "libavrdude.h"
|
|
|
|
|
|
|
|
#include "developer_opts.h"
|
|
|
|
#include "developer_opts_private.h"
|
|
|
|
|
2022-07-21 17:36:04 +00:00
|
|
|
char cmdbitchar(CMDBIT cb) {
|
2022-06-28 19:14:29 +00:00
|
|
|
switch(cb.type) {
|
|
|
|
case AVR_CMDBIT_IGNORE:
|
|
|
|
return 'x';
|
|
|
|
case AVR_CMDBIT_VALUE:
|
|
|
|
return cb.value? '1': '0';
|
|
|
|
case AVR_CMDBIT_ADDRESS:
|
|
|
|
return 'a';
|
|
|
|
case AVR_CMDBIT_INPUT:
|
|
|
|
return 'i';
|
|
|
|
case AVR_CMDBIT_OUTPUT:
|
|
|
|
return 'o';
|
|
|
|
default:
|
|
|
|
return '?';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-21 17:36:04 +00:00
|
|
|
|
|
|
|
char *cmdbitstr(CMDBIT cb) {
|
|
|
|
char space[32];
|
2022-07-07 17:32:19 +00:00
|
|
|
|
|
|
|
*space = cmdbitchar(cb);
|
|
|
|
if(*space == 'a')
|
|
|
|
sprintf(space+1, "%d", cb.bitno);
|
|
|
|
else
|
|
|
|
space[1] = 0;
|
|
|
|
|
|
|
|
return strdup(space);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-07-21 17:47:48 +00:00
|
|
|
const char *opcodename(int opnum) {
|
|
|
|
switch(opnum) {
|
2022-06-28 19:14:29 +00:00
|
|
|
case AVR_OP_READ:
|
|
|
|
return "read";
|
|
|
|
case AVR_OP_WRITE:
|
|
|
|
return "write";
|
|
|
|
case AVR_OP_READ_LO:
|
|
|
|
return "read_lo";
|
|
|
|
case AVR_OP_READ_HI:
|
|
|
|
return "read_hi";
|
|
|
|
case AVR_OP_WRITE_LO:
|
|
|
|
return "write_lo";
|
|
|
|
case AVR_OP_WRITE_HI:
|
|
|
|
return "write_hi";
|
|
|
|
case AVR_OP_LOADPAGE_LO:
|
|
|
|
return "loadpage_lo";
|
|
|
|
case AVR_OP_LOADPAGE_HI:
|
|
|
|
return "loadpage_hi";
|
|
|
|
case AVR_OP_LOAD_EXT_ADDR:
|
|
|
|
return "load_ext_addr";
|
|
|
|
case AVR_OP_WRITEPAGE:
|
|
|
|
return "writepage";
|
|
|
|
case AVR_OP_CHIP_ERASE:
|
|
|
|
return "chip_erase";
|
|
|
|
case AVR_OP_PGM_ENABLE:
|
|
|
|
return "pgm_enable";
|
|
|
|
default:
|
|
|
|
return "???";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-07-19 22:44:58 +00:00
|
|
|
// Unique string representation of an opcode
|
2022-07-21 17:47:48 +00:00
|
|
|
char *opcode2str(OPCODE *op, int opnum, int detailed) {
|
2022-06-28 19:14:29 +00:00
|
|
|
char cb, space[1024], *sp = space;
|
2022-07-19 22:44:58 +00:00
|
|
|
int compact = 1;
|
2022-06-28 19:14:29 +00:00
|
|
|
|
2022-07-07 17:32:19 +00:00
|
|
|
if(!op)
|
|
|
|
return strdup("NULL");
|
|
|
|
|
2022-07-19 22:44:58 +00:00
|
|
|
// Can the opcode be printed in a compact way? Only if address bits are systematic.
|
|
|
|
for(int i=31; i >= 0; i--)
|
|
|
|
if(op->bit[i].type == AVR_CMDBIT_ADDRESS)
|
|
|
|
if(i<8 || i>23 || op->bit[i].bitno != (opnum == AVR_OP_LOAD_EXT_ADDR? i+8: i-8))
|
|
|
|
compact = 0;
|
|
|
|
|
2022-06-28 19:14:29 +00:00
|
|
|
if(detailed)
|
|
|
|
*sp++ = '"';
|
2022-07-19 22:44:58 +00:00
|
|
|
|
2022-06-28 19:14:29 +00:00
|
|
|
for(int i=31; i >= 0; i--) {
|
|
|
|
*sp++ = cb = cmdbitchar(op->bit[i]);
|
2022-07-19 22:44:58 +00:00
|
|
|
if(compact) {
|
|
|
|
if(i && i%8 == 0)
|
|
|
|
*sp++ = '-', *sp++ = '-';
|
|
|
|
else if(i && i%4 == 0)
|
|
|
|
*sp++ = '.';
|
|
|
|
} else {
|
|
|
|
if(cb == 'a') {
|
|
|
|
sprintf(sp, "%d", op->bit[i].bitno);
|
|
|
|
sp += strlen(sp);
|
|
|
|
}
|
|
|
|
if(i) {
|
|
|
|
if(detailed)
|
|
|
|
*sp++ = ' ';
|
|
|
|
if(i%8 == 0)
|
|
|
|
*sp++ = ' ';
|
|
|
|
}
|
2022-06-28 19:14:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if(detailed)
|
|
|
|
*sp++ = '"';
|
|
|
|
*sp = 0;
|
|
|
|
|
|
|
|
return strdup(space);
|
|
|
|
}
|
|
|
|
|
2022-07-07 17:32:19 +00:00
|
|
|
|
|
|
|
// return 0 if op code would encode (essentially) the same SPI command
|
2022-07-21 17:47:48 +00:00
|
|
|
int opcodecmp(OPCODE *op1, OPCODE *op2, int opnum) {
|
2022-07-07 17:32:19 +00:00
|
|
|
char *opstr1, *opstr2, *p;
|
|
|
|
int cmp;
|
|
|
|
|
|
|
|
if(!op1 && !op2)
|
|
|
|
return 0;
|
|
|
|
if(!op1 || !op2)
|
|
|
|
return op1? -1: 1;
|
|
|
|
|
2022-07-19 22:44:58 +00:00
|
|
|
opstr1 = opcode2str(op1, opnum, 1);
|
|
|
|
opstr2 = opcode2str(op2, opnum, 1);
|
2022-07-07 17:32:19 +00:00
|
|
|
if(!opstr1 || !opstr2) {
|
|
|
|
dev_info("%s: out of memory\n", progname);
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
// don't care x and 0 are functionally equivalent
|
|
|
|
for(p=opstr1; *p; p++)
|
|
|
|
if(*p == 'x')
|
|
|
|
*p = '0';
|
|
|
|
for(p=opstr2; *p; p++)
|
|
|
|
if(*p == 'x')
|
|
|
|
*p = '0';
|
|
|
|
|
|
|
|
cmp = strcmp(opstr1, opstr2);
|
|
|
|
free(opstr1);
|
|
|
|
free(opstr2);
|
|
|
|
|
|
|
|
return cmp;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-07-21 17:47:48 +00:00
|
|
|
static void printopcode(AVRPART *p, const char *d, OPCODE *op, int opnum) {
|
2022-06-28 19:14:29 +00:00
|
|
|
unsigned char cmd[4];
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if(op) {
|
|
|
|
memset(cmd, 0, sizeof cmd);
|
|
|
|
avr_set_bits(op, cmd);
|
|
|
|
|
2022-07-21 17:47:48 +00:00
|
|
|
dev_info(".op\t%s\t%s\t%s\t0x%02x%02x%02x%02x\t", p->desc, d, opcodename(opnum), cmd[0], cmd[1], cmd[2], cmd[3]);
|
2022-06-28 19:14:29 +00:00
|
|
|
for(i=31; i >= 0; i--) {
|
|
|
|
dev_info("%c", cmdbitchar(op->bit[i]));
|
|
|
|
if(i%8 == 0)
|
2022-07-07 17:32:19 +00:00
|
|
|
dev_info("%c", i? '\t': '\n');
|
2022-06-28 19:14:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void printallopcodes(AVRPART *p, const char *d, OPCODE **opa) {
|
|
|
|
for(int i=0; i<AVR_OP_MAX; i++)
|
|
|
|
printopcode(p, d, opa[i], i);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// returns position 0..31 of highest bit set or INT_MIN if no bit is set
|
2022-07-21 17:36:04 +00:00
|
|
|
int intlog2(unsigned int n) {
|
2022-06-28 19:14:29 +00:00
|
|
|
int ret;
|
|
|
|
|
|
|
|
if(!n)
|
|
|
|
return INT_MIN;
|
|
|
|
|
|
|
|
for(ret = 0; n >>= 1; ret++)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// mnemonic characterisation of flags
|
|
|
|
static char *parttype(AVRPART *p) {
|
|
|
|
static char type[1024];
|
|
|
|
|
|
|
|
switch(p->flags & (AVRPART_HAS_PDI | AVRPART_AVR32 | AVRPART_HAS_TPI | AVRPART_HAS_UPDI)) {
|
|
|
|
case 0: strcpy(type, "ISP"); break;
|
|
|
|
case AVRPART_HAS_PDI: strcpy(type, "PDI"); break;
|
|
|
|
case AVRPART_AVR32: strcpy(type, "AVR32"); break;
|
|
|
|
case AVRPART_HAS_TPI: strcpy(type, "TPI"); break;
|
|
|
|
case AVRPART_HAS_UPDI: strcpy(type, "UPDI"); break;
|
|
|
|
default: strcpy(type, "UNKNOWN"); break;
|
|
|
|
}
|
2022-07-07 17:32:19 +00:00
|
|
|
|
2022-06-28 19:14:29 +00:00
|
|
|
if((p->flags & AVRPART_SERIALOK) == 0)
|
|
|
|
strcat(type, "|NOTSERIAL");
|
|
|
|
if((p->flags & AVRPART_PARALLELOK) == 0)
|
|
|
|
strcat(type, "|NOTPARALLEL");
|
|
|
|
if(p->flags & AVRPART_PSEUDOPARALLEL)
|
|
|
|
strcat(type, "|PSEUDOPARALLEL");
|
|
|
|
if(p->flags & AVRPART_IS_AT90S1200)
|
|
|
|
strcat(type, "|IS_AT90S1200");
|
|
|
|
|
|
|
|
if(p->flags & AVRPART_HAS_DW)
|
|
|
|
strcat(type, "|DW");
|
|
|
|
|
|
|
|
if(p->flags & AVRPART_HAS_JTAG)
|
|
|
|
strcat(type, "|JTAG");
|
|
|
|
if(p->flags & AVRPART_ALLOWFULLPAGEBITSTREAM)
|
|
|
|
strcat(type, "|PAGEBITSTREAM");
|
|
|
|
if((p->flags & AVRPART_ENABLEPAGEPROGRAMMING) == 0)
|
|
|
|
strcat(type, "|NOPAGEPROGRAMMING");
|
|
|
|
|
|
|
|
return type;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// check whether address bits are where they should be in ISP commands
|
2022-07-21 17:47:48 +00:00
|
|
|
static void checkaddr(int memsize, int pagesize, int opnum, OPCODE *op, AVRPART *p, AVRMEM *m) {
|
2022-06-28 19:14:29 +00:00
|
|
|
int i, lo, hi;
|
2022-07-21 17:47:48 +00:00
|
|
|
const char *opstr = opcodename(opnum);
|
2022-06-28 19:14:29 +00:00
|
|
|
|
|
|
|
lo = intlog2(pagesize);
|
|
|
|
hi = intlog2(memsize-1);
|
|
|
|
|
|
|
|
// address bits should be between positions lo and hi (and fall in line), outside should be 0 or don't care
|
|
|
|
for(i=0; i<16; i++) { // ISP programming only deals with 16-bit addresses (words for flash, bytes for eeprom)
|
|
|
|
if(i < lo || i > hi) {
|
|
|
|
if(op->bit[i+8].type != AVR_CMDBIT_IGNORE && !(op->bit[i+8].type == AVR_CMDBIT_VALUE && op->bit[i+8].value == 0)) {
|
2022-07-07 17:32:19 +00:00
|
|
|
char *cbs = cmdbitstr(op->bit[i+8]);
|
2022-07-21 17:47:48 +00:00
|
|
|
dev_info(".cmderr\t%s\t%s-%s\tbit %d outside addressable space should be x or 0 but is %s\n", p->desc, m->desc, opstr, i+8, cbs? cbs: "NULL");
|
2022-07-07 17:32:19 +00:00
|
|
|
if(cbs)
|
|
|
|
free(cbs);
|
2022-06-28 19:14:29 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if(op->bit[i+8].type != AVR_CMDBIT_ADDRESS)
|
2022-07-21 17:47:48 +00:00
|
|
|
dev_info(".cmderr\t%s\t%s-%s\tbit %d is %c but should be a\n", p->desc, m->desc, opstr, i+8, cmdbitchar(op->bit[i+8]));
|
2022-06-28 19:14:29 +00:00
|
|
|
else if(op->bit[i+8].bitno != i)
|
2022-07-21 17:47:48 +00:00
|
|
|
dev_info(".cmderr\t%s\t%s-%s\tbit %d inconsistent: a%d specified as a%d\n", p->desc, m->desc, opstr, i+8, i, op->bit[i+8].bitno);
|
2022-06-28 19:14:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
for(i=0; i<32; i++) // command bits 8..23 should not contain address bits
|
|
|
|
if((i<8 || i>23) && op->bit[i].type == AVR_CMDBIT_ADDRESS)
|
2022-07-21 17:47:48 +00:00
|
|
|
dev_info(".cmderr\t%s\t%s-%s\tbit %d contains a%d which it shouldn't\n", p->desc, m->desc, opstr, i, op->bit[i].bitno);
|
2022-07-07 17:32:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
Provide avr_set_addr_mem() to set addresses in SPI opcodes within boundaries
The function avr_set_addr_mem(AVRMEM *mem, int opnum, unsigned char *cmd,
unsigned long addr) is meant to replace avr_set_addr(OPCODE *op, unsigned
char *cmd, unsigned long addr) in future.
avr_set_addr_mem() has more information about the context of the task in that
it knows the memory size, memory page size, whether or not the memory is a
flash memory (which gets words addressees supplied) and, crucially, knows
which SPI operation it is meant to compute the address bits for.
avr_set_addr_mem() first computes the interval of bit numbers that must be
supplied for the SPI command to stand a chance to work. The function only
sets those address bits that are needed. Once all avr_set_addr() function
calls have been replaced by avr_set_addr_mem(), the SPI commands that need an
address can afford to declare in avrdude.conf all 16 address bits in the
middle two bytes of the SPI command. This over-declaration will be corrected
during runtime by avr_set_addr_mem(). One consequence of this is that parts
can inherit smaller or larger memories from parents without the need to use
different SPI codes in avrdude.conf. Another consequence is that
avr_set_addr_mem() can, and does, tell the caller whether vital address bits
were not declared in the SPI opcode. During parsing of avrdude.conf this
might be utilised to generate a corresponding warning. This will uncover
problematic SPI codes in avrdude.conf that in the past went undetected.
2022-07-21 20:42:07 +00:00
|
|
|
/*
|
|
|
|
* avr_set_addr_mem()
|
|
|
|
*
|
|
|
|
* Set address bits in the specified command based on the memory, opcode and
|
|
|
|
* address; addr must be a word address for flash or, for all other memories,
|
|
|
|
* a byte address; returns 0 on success and -1 on error (no memory or no
|
|
|
|
* opcode) or, if positive, bn+1 where bn is bit number of the highest
|
|
|
|
* necessary bit that the opcode does not provide.
|
|
|
|
*/
|
|
|
|
int avr_set_addr_mem(AVRMEM *mem, int opnum, unsigned char *cmd, unsigned long addr) {
|
|
|
|
int ret, isflash, lo, hi, memsize, pagesize;
|
|
|
|
OPCODE *op;
|
|
|
|
|
|
|
|
if(!mem)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if(!(op = mem->op[opnum]))
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
isflash = !strcmp(mem->desc, "flash"); // ISP parts have only one flash-like memory
|
|
|
|
memsize = mem->size >> isflash; // word addresses for flash
|
|
|
|
pagesize = mem->page_size >> isflash;
|
|
|
|
|
|
|
|
// compute range lo..hi of needed address bits
|
|
|
|
switch(opnum) {
|
|
|
|
case AVR_OP_READ:
|
|
|
|
case AVR_OP_WRITE:
|
|
|
|
case AVR_OP_READ_LO:
|
|
|
|
case AVR_OP_READ_HI:
|
|
|
|
case AVR_OP_WRITE_LO:
|
|
|
|
case AVR_OP_WRITE_HI:
|
|
|
|
lo = 0;
|
|
|
|
hi = intlog2(memsize-1); // memsize = 1 implies no addr bit is needed
|
|
|
|
break;
|
|
|
|
|
|
|
|
case AVR_OP_LOADPAGE_LO:
|
|
|
|
case AVR_OP_LOADPAGE_HI:
|
|
|
|
lo = 0;
|
|
|
|
hi = intlog2(pagesize-1);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case AVR_OP_LOAD_EXT_ADDR:
|
|
|
|
lo = 16;
|
|
|
|
hi = intlog2(memsize-1);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case AVR_OP_WRITEPAGE:
|
|
|
|
lo = intlog2(pagesize);
|
|
|
|
hi = intlog2(memsize-1);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case AVR_OP_CHIP_ERASE:
|
|
|
|
case AVR_OP_PGM_ENABLE:
|
|
|
|
default:
|
|
|
|
lo = 0;
|
|
|
|
hi = -1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Unless it's load extended address, ISP chips only deal with 16 bit addresses
|
|
|
|
if(opnum != AVR_OP_LOAD_EXT_ADDR && hi > 15)
|
|
|
|
hi = 15;
|
|
|
|
|
|
|
|
unsigned char avail[32];
|
|
|
|
memset(avail, 0, sizeof avail);
|
|
|
|
|
|
|
|
for(int i=0; i<32; i++) {
|
|
|
|
if(op->bit[i].type == AVR_CMDBIT_ADDRESS) {
|
|
|
|
int bitno, j, bit;
|
|
|
|
unsigned char mask;
|
|
|
|
|
|
|
|
bitno = op->bit[i].bitno & 31;
|
|
|
|
j = 3 - i / 8;
|
|
|
|
bit = i % 8;
|
|
|
|
mask = 1 << bit;
|
|
|
|
avail[bitno] = 1;
|
|
|
|
|
|
|
|
// 'a' bit with number outside bit range [lo, hi] is set to 0
|
|
|
|
if (bitno >= lo && bitno <= hi? (addr >> bitno) & 1: 0)
|
|
|
|
cmd[j] = cmd[j] | mask;
|
|
|
|
else
|
|
|
|
cmd[j] = cmd[j] & ~mask;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
if(lo >= 0 && hi < 32 && lo <= hi)
|
|
|
|
for(int bn=lo; bn <= hi; bn++)
|
|
|
|
if(!avail[bn]) // necessary bit bn misses in opcode
|
|
|
|
ret = bn+1;
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2022-07-07 17:32:19 +00:00
|
|
|
static char *dev_sprintf(const char *fmt, ...) {
|
|
|
|
int size = 0;
|
|
|
|
char *p = NULL;
|
|
|
|
va_list ap;
|
|
|
|
|
|
|
|
// compute size
|
|
|
|
va_start(ap, fmt);
|
|
|
|
size = vsnprintf(p, size, fmt, ap);
|
|
|
|
va_end(ap);
|
|
|
|
|
|
|
|
if(size < 0)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
size++; // for temrinating '\0'
|
|
|
|
if(!(p = malloc(size)))
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
va_start(ap, fmt);
|
|
|
|
size = vsnprintf(p, size, fmt, ap);
|
|
|
|
va_end(ap);
|
|
|
|
|
|
|
|
if(size < 0) {
|
|
|
|
free(p);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return p;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int dev_nprinted;
|
|
|
|
|
|
|
|
int dev_message(int msglvl, const char *fmt, ...) {
|
|
|
|
va_list ap;
|
|
|
|
int rc = 0;
|
|
|
|
|
|
|
|
if(verbose >= msglvl) {
|
|
|
|
va_start(ap, fmt);
|
|
|
|
rc = vfprintf(stderr, fmt, ap);
|
|
|
|
va_end(ap);
|
|
|
|
if(rc > 0)
|
|
|
|
dev_nprinted += rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
return rc;
|
2022-06-28 19:14:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-07-07 17:32:19 +00:00
|
|
|
|
|
|
|
static int dev_part_strct_entry(bool tsv, char *col0, char *col1, char *col2, const char *name, char *cont) {
|
|
|
|
const char *n = name? name: "name_error";
|
|
|
|
const char *c = cont? cont: "cont_error";
|
|
|
|
|
|
|
|
if(tsv) { // tab separated values
|
|
|
|
if(col0) {
|
|
|
|
dev_info("%s\t", col0);
|
|
|
|
if(col1) {
|
|
|
|
dev_info("%s\t", col1);
|
|
|
|
if(col2) {
|
|
|
|
dev_info("%s\t", col2);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
dev_info("%s\t%s\n", n, c);
|
|
|
|
} else { // grammar conform
|
|
|
|
int indent = col2 && strcmp(col2, "part");
|
|
|
|
|
|
|
|
printf("%*s%-*s = %s;\n", indent? 8: 4, "", indent? 15: 19, n, c);
|
|
|
|
}
|
|
|
|
|
|
|
|
if(cont)
|
|
|
|
free(cont);
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static const char *dev_controlstack_name(AVRPART *p) {
|
|
|
|
return
|
|
|
|
p->ctl_stack_type == CTL_STACK_PP? "pp_controlstack":
|
|
|
|
p->ctl_stack_type == CTL_STACK_HVSP? "hvsp_controlstack":
|
2022-07-19 15:16:55 +00:00
|
|
|
p->ctl_stack_type == CTL_STACK_NONE? "NULL":
|
2022-07-07 17:32:19 +00:00
|
|
|
"unknown_controlstack";
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void dev_stack_out(bool tsv, AVRPART *p, const char *name, unsigned char *stack, int ns) {
|
2022-07-19 15:16:55 +00:00
|
|
|
if(!strcmp(name, "NULL")) {
|
2022-07-07 17:32:19 +00:00
|
|
|
name = "pp_controlstack";
|
|
|
|
ns = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(tsv)
|
|
|
|
dev_info(".pt\t%s\t%s\t", p->desc, name);
|
|
|
|
else
|
|
|
|
dev_info(" %-19s =%s", name, ns <=8? " ": "");
|
|
|
|
|
|
|
|
if(ns <= 0)
|
|
|
|
dev_info(tsv? "NULL\n": "NULL;\n");
|
2022-06-28 19:14:29 +00:00
|
|
|
else
|
2022-07-07 17:32:19 +00:00
|
|
|
for(int i=0; i<ns; i++)
|
|
|
|
dev_info("%s0x%02x%s", !tsv && ns > 8 && i%8 == 0? "\n ": "", stack[i], i+1<ns? ", ": tsv? "\n": ";\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-07-21 17:36:04 +00:00
|
|
|
// order in which memories are processed, runtime adds unknown ones (but there shouldn't be any)
|
2022-07-07 17:32:19 +00:00
|
|
|
static const char *mem_order[100] = {
|
|
|
|
"eeprom", "flash", "application", "apptable",
|
|
|
|
"boot", "lfuse", "hfuse", "efuse",
|
|
|
|
"fuse", "fuse0", "wdtcfg", "fuse1",
|
|
|
|
"bodcfg", "fuse2", "osccfg", "fuse3",
|
|
|
|
"fuse4", "tcd0cfg", "fuse5", "syscfg0",
|
|
|
|
"fuse6", "syscfg1", "fuse7", "append",
|
|
|
|
"codesize", "fuse8", "fuse9", "bootend",
|
|
|
|
"bootsize", "fuses", "lock", "lockbits",
|
|
|
|
"tempsense", "signature", "prodsig", "sernum",
|
|
|
|
"calibration", "osccal16", "osccal20", "osc16err",
|
|
|
|
"osc20err", "usersig", "userrow", "data",
|
|
|
|
};
|
|
|
|
|
|
|
|
static void add_mem_order(const char *str) {
|
2022-07-22 22:50:22 +00:00
|
|
|
for(size_t i=0; i < sizeof mem_order/sizeof *mem_order; i++) {
|
2022-07-07 17:32:19 +00:00
|
|
|
if(mem_order[i] && !strcmp(mem_order[i], str))
|
|
|
|
return;
|
|
|
|
if(!mem_order[i]) {
|
|
|
|
mem_order[i] = strdup(str);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
dev_info("%s: mem_order[] under-dimensioned in developer_opts.c; increase and recompile\n", progname);
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int intcmp(int a, int b) {
|
|
|
|
return a-b;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// deep copies for comparison and raw output
|
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
AVRMEM base;
|
|
|
|
OPCODE ops[AVR_OP_MAX];
|
|
|
|
} AVRMEMdeep;
|
|
|
|
|
|
|
|
static int avrmem_deep_copy(AVRMEMdeep *d, AVRMEM *m) {
|
2022-07-22 22:50:22 +00:00
|
|
|
size_t len;
|
2022-07-07 17:32:19 +00:00
|
|
|
|
|
|
|
d->base = *m;
|
|
|
|
|
|
|
|
// zap all bytes beyond terminating nul of desc array
|
|
|
|
len = strlen(m->desc)+1;
|
|
|
|
if(len < sizeof m->desc)
|
|
|
|
memset(d->base.desc + len, 0, sizeof m->desc - len);
|
|
|
|
|
|
|
|
// zap address values
|
|
|
|
d->base.buf = NULL;
|
|
|
|
d->base.tags = NULL;
|
|
|
|
for(int i=0; i<AVR_OP_MAX; i++)
|
|
|
|
d->base.op[i] = NULL;
|
|
|
|
|
|
|
|
|
|
|
|
// copy over the SPI operations themselves
|
|
|
|
memset(d->base.op, 0, sizeof d->base.op);
|
|
|
|
memset(d->ops, 0, sizeof d->ops);
|
2022-07-22 22:50:22 +00:00
|
|
|
for(size_t i=0; i<sizeof d->ops/sizeof *d->ops; i++)
|
2022-07-07 17:32:19 +00:00
|
|
|
if(m->op[i])
|
|
|
|
d->ops[i] = *m->op[i];
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int memorycmp(AVRMEM *m1, AVRMEM *m2) {
|
|
|
|
AVRMEMdeep dm1, dm2;
|
|
|
|
|
|
|
|
if(!m1 && !m2)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if(!m1 || !m2)
|
|
|
|
return m1? -1: 1;
|
|
|
|
|
|
|
|
avrmem_deep_copy(&dm1, m1);
|
|
|
|
avrmem_deep_copy(&dm2, m2);
|
|
|
|
|
|
|
|
return memcmp(&dm1, &dm2, sizeof dm1);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
AVRPART base;
|
|
|
|
OPCODE ops[AVR_OP_MAX];
|
|
|
|
AVRMEMdeep mems[40];
|
|
|
|
} AVRPARTdeep;
|
|
|
|
|
|
|
|
static int avrpart_deep_copy(AVRPARTdeep *d, AVRPART *p) {
|
|
|
|
AVRMEM *m;
|
2022-07-22 22:50:22 +00:00
|
|
|
size_t len, di;
|
2022-07-07 17:32:19 +00:00
|
|
|
|
|
|
|
memset(d, 0, sizeof *d);
|
|
|
|
|
|
|
|
d->base = *p;
|
|
|
|
|
2022-07-19 13:38:34 +00:00
|
|
|
d->base.config_file = NULL;
|
2022-07-07 17:32:19 +00:00
|
|
|
d->base.lineno = 0;
|
|
|
|
|
|
|
|
// zap all bytes beyond terminating nul of desc, id and family_id array
|
|
|
|
len = strlen(p->desc);
|
|
|
|
if(len < sizeof p->desc)
|
|
|
|
memset(d->base.desc + len, 0, sizeof p->desc - len);
|
|
|
|
|
|
|
|
len = strlen(p->family_id);
|
|
|
|
if(len < sizeof p->family_id)
|
|
|
|
memset(d->base.family_id + len, 0, sizeof p->family_id - len);
|
|
|
|
|
|
|
|
len = strlen(p->id);
|
|
|
|
if(len < sizeof p->id)
|
|
|
|
memset(d->base.id + len, 0, sizeof p->id - len);
|
|
|
|
|
|
|
|
// zap address values
|
|
|
|
d->base.mem = NULL;
|
|
|
|
d->base.mem_alias = NULL;
|
|
|
|
for(int i=0; i<AVR_OP_MAX; i++)
|
|
|
|
d->base.op[i] = NULL;
|
|
|
|
|
|
|
|
// copy over the SPI operations
|
|
|
|
memset(d->base.op, 0, sizeof d->base.op);
|
|
|
|
memset(d->ops, 0, sizeof d->ops);
|
|
|
|
for(int i=0; i<AVR_OP_MAX; i++)
|
|
|
|
if(p->op[i])
|
|
|
|
d->ops[i] = *p->op[i];
|
|
|
|
|
|
|
|
// fill in all memories we got in defined order
|
|
|
|
di = 0;
|
2022-07-22 22:50:22 +00:00
|
|
|
for(size_t mi=0; mi < sizeof mem_order/sizeof *mem_order && mem_order[mi]; mi++) {
|
2022-07-07 17:32:19 +00:00
|
|
|
m = p->mem? avr_locate_mem(p, mem_order[mi]): NULL;
|
|
|
|
if(m) {
|
|
|
|
if(di >= sizeof d->mems/sizeof *d->mems) {
|
|
|
|
avrdude_message(MSG_INFO, "%s: ran out of mems[] space, increase size in AVRMEMdeep of developer_opts.c and recompile\n", progname);
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
avrmem_deep_copy(d->mems+di, m);
|
|
|
|
di++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return di;
|
|
|
|
}
|
|
|
|
|
|
|
|
static char txtchar(unsigned char in) {
|
|
|
|
in &= 0x7f;
|
|
|
|
return in == ' '? '_': in > ' ' && in < 0x7f? in: '.';
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void dev_raw_dump(unsigned char *p, int nbytes, const char *name, const char *sub, int idx) {
|
|
|
|
unsigned char *end = p+nbytes;
|
|
|
|
int n = ((end - p) + 15)/16;
|
|
|
|
|
|
|
|
for(int i=0; i<n; i++, p += 16) {
|
|
|
|
dev_info("%s\t%s\t%02x%04x: ", name, sub, idx, i*16);
|
|
|
|
for(int j=0; j<16; j++)
|
|
|
|
dev_info("%02x", p+i*16+j<end? p[i*16+j]: 0);
|
|
|
|
dev_info(" ");
|
|
|
|
for(int j=0; j<16; j++)
|
|
|
|
dev_info("%c", txtchar(p+i*16+j<end? p[i*16+j]: 0));
|
|
|
|
dev_info("\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void dev_part_raw(AVRPART *part) {
|
|
|
|
AVRPARTdeep dp;
|
|
|
|
int di = avrpart_deep_copy(&dp, part);
|
|
|
|
|
|
|
|
dev_raw_dump((unsigned char *) &dp.base, sizeof dp.base, part->desc, "part", 0);
|
|
|
|
dev_raw_dump((unsigned char *) &dp.ops, sizeof dp.ops, part->desc, "ops", 1);
|
|
|
|
|
|
|
|
for(int i=0; i<di; i++)
|
|
|
|
dev_raw_dump((unsigned char *) (dp.mems+i), sizeof dp.mems[i], part->desc, dp.mems[i].base.desc, i+2);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void dev_part_strct(AVRPART *p, bool tsv, AVRPART *base) {
|
2022-07-19 13:38:34 +00:00
|
|
|
dev_info("# %s %d\n", p->config_file, p->lineno);
|
2022-07-07 17:32:19 +00:00
|
|
|
|
|
|
|
if(!tsv)
|
|
|
|
dev_info("part\n");
|
|
|
|
|
2022-07-19 13:38:34 +00:00
|
|
|
_if_partout(strcmp, "\"%s\"", desc);
|
|
|
|
_if_partout(strcmp, "\"%s\"", id);
|
|
|
|
_if_partout(strcmp, "\"%s\"", family_id);
|
|
|
|
_if_partout(intcmp, "%d", hvupdi_variant);
|
|
|
|
_if_partout(intcmp, "0x%02x", stk500_devcode);
|
|
|
|
_if_partout(intcmp, "0x%02x", avr910_devcode);
|
|
|
|
_if_partout(intcmp, "%d", chip_erase_delay);
|
|
|
|
_if_partout(intcmp, "0x%02x", pagel);
|
|
|
|
_if_partout(intcmp, "0x%02x", bs2);
|
|
|
|
_if_n_partout_str(memcmp, sizeof p->signature, dev_sprintf("0x%02x 0x%02x 0x%02x", p->signature[0], p->signature[1], p->signature[2]), signature);
|
|
|
|
_if_partout(intcmp, "0x%04x", usbpid);
|
2022-07-07 17:32:19 +00:00
|
|
|
|
|
|
|
if(!base || base->reset_disposition != p->reset_disposition)
|
2022-07-19 13:38:34 +00:00
|
|
|
_partout_str(strdup(p->reset_disposition == RESET_DEDICATED? "dedicated": p->reset_disposition == RESET_IO? "io": "unknown"), reset);
|
2022-07-07 17:32:19 +00:00
|
|
|
|
2022-07-19 13:38:34 +00:00
|
|
|
_if_partout_str(intcmp, strdup(p->retry_pulse == PIN_AVR_RESET? "reset": p->retry_pulse == PIN_AVR_SCK? "sck": "unknown"), retry_pulse);
|
2022-07-07 17:32:19 +00:00
|
|
|
|
|
|
|
if(!base || base->flags != p->flags) {
|
|
|
|
if(tsv) {
|
2022-07-19 13:38:34 +00:00
|
|
|
_partout("0x%04x", flags);
|
2022-07-07 17:32:19 +00:00
|
|
|
} else {
|
2022-07-19 13:38:34 +00:00
|
|
|
_if_flagout(AVRPART_HAS_JTAG, has_jtag);
|
|
|
|
_if_flagout(AVRPART_HAS_DW, has_debugwire);
|
|
|
|
_if_flagout(AVRPART_HAS_PDI, has_pdi);
|
|
|
|
_if_flagout(AVRPART_HAS_UPDI, has_updi);
|
|
|
|
_if_flagout(AVRPART_HAS_TPI, has_tpi);
|
|
|
|
_if_flagout(AVRPART_IS_AT90S1200, is_at90s1200);
|
|
|
|
_if_flagout(AVRPART_AVR32, is_avr32);
|
|
|
|
_if_flagout(AVRPART_ALLOWFULLPAGEBITSTREAM, allowfullpagebitstream);
|
|
|
|
_if_flagout(AVRPART_ENABLEPAGEPROGRAMMING, enablepageprogramming);
|
|
|
|
_if_flagout(AVRPART_SERIALOK, serial);
|
2022-07-07 17:32:19 +00:00
|
|
|
|
|
|
|
if(!base || (base->flags & (AVRPART_PARALLELOK | AVRPART_PSEUDOPARALLEL)) != (p->flags & (AVRPART_PARALLELOK | AVRPART_PSEUDOPARALLEL))) {
|
|
|
|
int par = p->flags & (AVRPART_PARALLELOK | AVRPART_PSEUDOPARALLEL);
|
2022-07-19 13:38:34 +00:00
|
|
|
_partout_str(strdup(par == 0? "no": par == AVRPART_PSEUDOPARALLEL? "unknown": AVRPART_PARALLELOK? "yes": "pseudo"), parallel);
|
2022-07-07 17:32:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-19 13:38:34 +00:00
|
|
|
_if_partout(intcmp, "%d", timeout);
|
|
|
|
_if_partout(intcmp, "%d", stabdelay);
|
|
|
|
_if_partout(intcmp, "%d", cmdexedelay);
|
|
|
|
_if_partout(intcmp, "%d", synchloops);
|
|
|
|
_if_partout(intcmp, "%d", bytedelay);
|
|
|
|
_if_partout(intcmp, "%d", pollindex);
|
|
|
|
_if_partout(intcmp, "0x%02x", pollvalue);
|
|
|
|
_if_partout(intcmp, "%d", predelay);
|
|
|
|
_if_partout(intcmp, "%d", postdelay);
|
|
|
|
_if_partout(intcmp, "%d", pollmethod);
|
2022-07-07 17:32:19 +00:00
|
|
|
|
|
|
|
if(!base && p->ctl_stack_type != CTL_STACK_NONE)
|
|
|
|
dev_stack_out(tsv, p, dev_controlstack_name(p), p->controlstack, CTL_STACK_SIZE);
|
|
|
|
|
|
|
|
// @@@ may need to remove controlstack and set p->ctl_stack_type to CTL_STACK_NONE if base has controlstack?
|
|
|
|
if(base && (p->ctl_stack_type != base->ctl_stack_type || memcmp(base->controlstack, p->controlstack, sizeof base->controlstack)))
|
|
|
|
dev_stack_out(tsv, p, dev_controlstack_name(p), p->controlstack, CTL_STACK_SIZE);
|
|
|
|
|
|
|
|
if(!base || memcmp(base->flash_instr, p->flash_instr, sizeof base->flash_instr))
|
|
|
|
dev_stack_out(tsv, p, "flash_instr", p->flash_instr, FLASH_INSTR_SIZE);
|
|
|
|
|
|
|
|
if(!base || memcmp(base->eeprom_instr, p->eeprom_instr, sizeof base->eeprom_instr))
|
|
|
|
dev_stack_out(tsv, p, "eeprom_instr", p->eeprom_instr, EEPROM_INSTR_SIZE);
|
|
|
|
|
2022-07-19 13:38:34 +00:00
|
|
|
_if_partout(intcmp, "%d", hventerstabdelay);
|
|
|
|
_if_partout(intcmp, "%d", progmodedelay);
|
|
|
|
_if_partout(intcmp, "%d", latchcycles);
|
|
|
|
_if_partout(intcmp, "%d", togglevtg);
|
|
|
|
_if_partout(intcmp, "%d", poweroffdelay);
|
|
|
|
_if_partout(intcmp, "%d", resetdelayms);
|
|
|
|
_if_partout(intcmp, "%d", resetdelayus);
|
|
|
|
_if_partout(intcmp, "%d", hvleavestabdelay);
|
|
|
|
_if_partout(intcmp, "%d", resetdelay);
|
|
|
|
_if_partout(intcmp, "%d", chiperasepulsewidth);
|
|
|
|
_if_partout(intcmp, "%d", chiperasepolltimeout);
|
|
|
|
_if_partout(intcmp, "%d", chiperasetime);
|
|
|
|
_if_partout(intcmp, "%d", programfusepulsewidth);
|
|
|
|
_if_partout(intcmp, "%d", programfusepolltimeout);
|
|
|
|
_if_partout(intcmp, "%d", programlockpulsewidth);
|
|
|
|
_if_partout(intcmp, "%d", programlockpolltimeout);
|
|
|
|
_if_partout(intcmp, "%d", synchcycles);
|
|
|
|
_if_partout(intcmp, "%d", hvspcmdexedelay);
|
|
|
|
|
|
|
|
_if_partout(intcmp, "0x%02x", idr);
|
|
|
|
_if_partout(intcmp, "0x%02x", rampz);
|
|
|
|
_if_partout(intcmp, "0x%02x", spmcr);
|
|
|
|
_if_partout(intcmp, "0x%02x", eecr); // why is eecr an unsigned short?
|
|
|
|
_if_partout(intcmp, "0x%04x", mcu_base);
|
|
|
|
_if_partout(intcmp, "0x%04x", nvm_base);
|
|
|
|
_if_partout(intcmp, "0x%04x", ocd_base);
|
|
|
|
_if_partout(intcmp, "%d", ocdrev);
|
2022-07-07 17:32:19 +00:00
|
|
|
|
|
|
|
for(int i=0; i < AVR_OP_MAX; i++)
|
2022-07-19 22:44:58 +00:00
|
|
|
if(!base || opcodecmp(p->op[i], base->op[i], i))
|
|
|
|
dev_part_strct_entry(tsv, ".ptop", p->desc, "part", opcodename(i), opcode2str(p->op[i], i, !tsv));
|
2022-07-07 17:32:19 +00:00
|
|
|
|
2022-07-22 22:50:22 +00:00
|
|
|
for(size_t mi=0; mi < sizeof mem_order/sizeof *mem_order && mem_order[mi]; mi++) {
|
2022-07-07 17:32:19 +00:00
|
|
|
AVRMEM *m, *bm;
|
|
|
|
|
|
|
|
m = p->mem? avr_locate_mem(p, mem_order[mi]): NULL;
|
|
|
|
bm = base && base->mem? avr_locate_mem(base, mem_order[mi]): NULL;
|
|
|
|
|
|
|
|
if(!m && bm && !tsv)
|
|
|
|
dev_info("\n memory \"%s\" = NULL;\n", bm->desc);
|
|
|
|
|
|
|
|
if(!m)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if(base && !bm)
|
|
|
|
bm = avr_new_memtype();
|
|
|
|
|
|
|
|
if(!tsv) {
|
|
|
|
if(!memorycmp(bm, m)) // same memory bit for bit, no need to instantiate
|
|
|
|
continue;
|
|
|
|
|
|
|
|
dev_info("\n memory \"%s\"\n", m->desc);
|
|
|
|
}
|
|
|
|
|
2022-07-19 13:38:34 +00:00
|
|
|
_if_memout_yn(paged);
|
|
|
|
_if_memout(intcmp, m->size > 8192? "0x%x": "%d", size);
|
|
|
|
_if_memout(intcmp, "%d", page_size);
|
|
|
|
_if_memout(intcmp, "%d", num_pages); // why can AVRDUDE not compute this?
|
|
|
|
_if_memout(intcmp, "0x%x", offset);
|
|
|
|
_if_memout(intcmp, "%d", min_write_delay);
|
|
|
|
_if_memout(intcmp, "%d", max_write_delay);
|
|
|
|
_if_memout_yn(pwroff_after_write);
|
|
|
|
_if_n_memout_str(memcmp, 2, dev_sprintf("0x%02x 0x%02x", m->readback[0], m->readback[1]), readback);
|
|
|
|
_if_memout(intcmp, "%d", mode);
|
|
|
|
_if_memout(intcmp, "%d", delay);
|
|
|
|
_if_memout(intcmp, "%d", blocksize);
|
|
|
|
_if_memout(intcmp, "%d", readsize);
|
|
|
|
_if_memout(intcmp, "%d", pollindex);
|
2022-07-07 17:32:19 +00:00
|
|
|
|
|
|
|
for(int i=0; i < AVR_OP_MAX; i++)
|
2022-07-19 22:44:58 +00:00
|
|
|
if(!bm || opcodecmp(bm->op[i], m->op[i], i))
|
|
|
|
dev_part_strct_entry(tsv, ".ptmmop", p->desc, m->desc, opcodename(i), opcode2str(m->op[i], i, !tsv));
|
2022-07-07 17:32:19 +00:00
|
|
|
|
|
|
|
if(!tsv)
|
|
|
|
dev_info(" ;\n");
|
|
|
|
|
|
|
|
for(LNODEID lnm=lfirst(p->mem_alias); lnm; lnm=lnext(lnm)) {
|
|
|
|
AVRMEM_ALIAS *ma = ldata(lnm);
|
|
|
|
if(ma->aliased_mem && !strcmp(ma->aliased_mem->desc, m->desc)) {
|
|
|
|
if(tsv)
|
|
|
|
dev_info(".ptmm\t%s\t%s\talias\t%s\n", p->desc, ma->desc, m->desc);
|
|
|
|
else
|
|
|
|
dev_info("\n memory \"%s\"\n alias \"%s\";\n ;\n", ma->desc, m->desc);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!tsv)
|
|
|
|
dev_info(";\n");
|
2022-06-28 19:14:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-07-21 17:04:41 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Match STRING against the partname pattern PATTERN, returning 1 if it
|
|
|
|
* matches, 0 if not. NOTE: part_match() is a modified old copy of !fnmatch()
|
|
|
|
* from the GNU C Library (published under GLP v2). Used for portability.
|
|
|
|
*/
|
|
|
|
|
2022-07-22 22:50:22 +00:00
|
|
|
inline static int fold(int c) {
|
|
|
|
return (c >= 'A' && c <= 'Z')? c+('a'-'A'): c;
|
|
|
|
}
|
2022-07-21 17:04:41 +00:00
|
|
|
|
2022-07-21 17:36:04 +00:00
|
|
|
int part_match(const char *pattern, const char *string) {
|
2022-07-21 17:04:41 +00:00
|
|
|
unsigned char c;
|
|
|
|
const char *p = pattern, *n = string;
|
|
|
|
|
|
|
|
if(!*n) // AVRDUDE specialty: empty string never matches
|
|
|
|
return 0;
|
|
|
|
|
2022-07-22 22:50:22 +00:00
|
|
|
while((c = fold(*p++))) {
|
2022-07-21 17:04:41 +00:00
|
|
|
switch(c) {
|
|
|
|
case '?':
|
|
|
|
if(*n == 0)
|
|
|
|
return 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case '\\':
|
2022-07-22 22:50:22 +00:00
|
|
|
c = fold(*p++);
|
|
|
|
if(fold(*n) != c)
|
2022-07-21 17:04:41 +00:00
|
|
|
return 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case '*':
|
|
|
|
for(c = *p++; c == '?' || c == '*'; c = *p++)
|
|
|
|
if(c == '?' && *n++ == 0)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if(c == 0)
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
{
|
2022-07-22 22:50:22 +00:00
|
|
|
unsigned char c1 = fold(c == '\\'? *p : c); // This char
|
2022-07-21 17:04:41 +00:00
|
|
|
|
|
|
|
for(--p; *n; ++n) // Recursively check reminder of string for *
|
2022-07-22 22:50:22 +00:00
|
|
|
if((c == '[' || fold(*n) == c1) && part_match(p, n) == 1)
|
2022-07-21 17:04:41 +00:00
|
|
|
return 1;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
case '[':
|
|
|
|
{
|
|
|
|
int negate;
|
|
|
|
|
|
|
|
if(*n == 0)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
negate = (*p == '!' || *p == '^');
|
|
|
|
if(negate)
|
|
|
|
++p;
|
|
|
|
|
|
|
|
c = *p++;
|
|
|
|
for(;;) {
|
|
|
|
unsigned char cstart = c, cend = c;
|
|
|
|
|
|
|
|
if(c == '\\')
|
|
|
|
cstart = cend = *p++;
|
|
|
|
|
2022-07-22 22:50:22 +00:00
|
|
|
cstart = cend = fold(cstart);
|
2022-07-21 17:04:41 +00:00
|
|
|
|
|
|
|
if(c == 0) // [ (unterminated)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
c = *p++;
|
2022-07-22 22:50:22 +00:00
|
|
|
c = fold(c);
|
2022-07-21 17:04:41 +00:00
|
|
|
|
|
|
|
if(c == '-' && *p != ']') {
|
|
|
|
cend = *p++;
|
|
|
|
if(cend == '\\')
|
|
|
|
cend = *p++;
|
|
|
|
if(cend == 0)
|
|
|
|
return 0;
|
2022-07-22 22:50:22 +00:00
|
|
|
cend = fold(cend);
|
2022-07-21 17:04:41 +00:00
|
|
|
|
|
|
|
c = *p++;
|
|
|
|
}
|
|
|
|
|
2022-07-22 22:50:22 +00:00
|
|
|
if(fold(*n) >= cstart && fold(*n) <= cend)
|
2022-07-21 17:04:41 +00:00
|
|
|
goto matched;
|
|
|
|
|
|
|
|
if(c == ']')
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if(!negate)
|
|
|
|
return 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
matched:;
|
|
|
|
while(c != ']') { // Skip the rest of the [...] that already matched
|
|
|
|
|
|
|
|
if(c == 0) // [... (unterminated)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
c = *p++;
|
|
|
|
if(c == '\\') // XXX 1003.2d11 is unclear if this is right
|
|
|
|
++p;
|
|
|
|
}
|
|
|
|
if(negate)
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
2022-07-22 22:50:22 +00:00
|
|
|
if(c != fold(*n))
|
2022-07-21 17:04:41 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
++n;
|
|
|
|
}
|
|
|
|
|
|
|
|
return *n == 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-06-28 19:14:29 +00:00
|
|
|
// -p */[cdosw*]
|
|
|
|
void dev_output_part_defs(char *partdesc) {
|
2022-07-07 17:32:19 +00:00
|
|
|
bool cmdok, waits, opspi, descs, strct, cmpst, raw, all, tsv;
|
|
|
|
char *flags;
|
|
|
|
int nprinted;
|
|
|
|
AVRPART *nullpart = avr_new_part();
|
|
|
|
|
|
|
|
if((flags = strchr(partdesc, '/')))
|
|
|
|
*flags++ = 0;
|
|
|
|
|
|
|
|
if(!flags && !strcmp(partdesc, "*")) // treat -p * as if it was -p */*
|
|
|
|
flags = "*";
|
|
|
|
|
|
|
|
if(!*flags || !strchr("cdosSrw*t", *flags)) {
|
|
|
|
dev_info("%s: flags for developer option -p <wildcard>/<flags> not recognised\n", progname);
|
|
|
|
dev_info(
|
2022-07-24 22:38:51 +00:00
|
|
|
"Wildcard examples (these need protecting in the shell through quoting):\n"
|
2022-07-07 17:32:19 +00:00
|
|
|
" * all known parts\n"
|
|
|
|
" ATtiny10 just this part\n"
|
|
|
|
" *32[0-9] matches ATmega329, ATmega325 and ATmega328\n"
|
|
|
|
" *32? matches ATmega329, ATmega32A, ATmega325 and ATmega328\n"
|
|
|
|
"Flags (one or more of the characters below):\n"
|
2022-07-24 22:38:51 +00:00
|
|
|
" c check and report errors in address bits of SPI commands\n"
|
2022-07-07 17:32:19 +00:00
|
|
|
" d description of core part features\n"
|
|
|
|
" o opcodes for SPI programming parts and memories\n"
|
|
|
|
" S show entries of avrdude.conf parts with all values\n"
|
|
|
|
" s show entries of avrdude.conf parts with necessary values\n"
|
|
|
|
" r show entries of avrdude.conf parts as raw dump\n"
|
|
|
|
" w wd_... constants for ISP parts\n"
|
|
|
|
" * all of the above except s\n"
|
|
|
|
" t use tab separated values as much as possible\n"
|
2022-07-24 22:38:51 +00:00
|
|
|
"Examples:\n"
|
|
|
|
" $ avrdude -p ATmega328P/s\n"
|
|
|
|
" $ avrdude -p m328*/st | grep chip_erase_delay\n"
|
|
|
|
" avrdude -p*/r | sort\n"
|
|
|
|
"Notes:\n"
|
|
|
|
" -p * is the same as -p */*\n"
|
|
|
|
" This help message is printed using any unrecognised flag, eg, -p/h\n"
|
|
|
|
" Leaving no space after -p can be an OK substitute for quoting in shells\n"
|
|
|
|
" /s and /S outputs are designed to be used as input in avrdude.conf\n"
|
|
|
|
" Sorted /r output should stay invariant when rearranging avrdude.conf\n"
|
|
|
|
" The /c, /o and /w flags are less generic and may be removed sometime\n"
|
|
|
|
" These options are just to help development, so not further documented\n"
|
2022-07-07 17:32:19 +00:00
|
|
|
);
|
2022-06-28 19:14:29 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// redirect stderr to stdout
|
|
|
|
fflush(stderr); fflush(stdout); dup2(1, 2);
|
|
|
|
|
2022-07-07 17:32:19 +00:00
|
|
|
all = *flags == '*';
|
|
|
|
cmdok = all || !!strchr(flags, 'c');
|
|
|
|
descs = all || !!strchr(flags, 'd');
|
|
|
|
opspi = all || !!strchr(flags, 'o');
|
|
|
|
waits = all || !!strchr(flags, 'w');
|
|
|
|
strct = all || !!strchr(flags, 'S');
|
|
|
|
raw = all || !!strchr(flags, 'r');
|
|
|
|
cmpst = !!strchr(flags, 's');
|
|
|
|
tsv = !!strchr(flags, 't');
|
|
|
|
|
2022-06-28 19:14:29 +00:00
|
|
|
|
2022-07-07 17:32:19 +00:00
|
|
|
// go through all memories and add them to the memory order list
|
2022-06-28 19:14:29 +00:00
|
|
|
for(LNODEID ln1 = lfirst(part_list); ln1; ln1 = lnext(ln1)) {
|
|
|
|
AVRPART *p = ldata(ln1);
|
2022-07-07 17:32:19 +00:00
|
|
|
if(p->mem)
|
|
|
|
for(LNODEID lnm=lfirst(p->mem); lnm; lnm=lnext(lnm))
|
|
|
|
add_mem_order(((AVRMEM *) ldata(lnm))->desc);
|
|
|
|
|
|
|
|
// same for aliased memories (though probably not needed)
|
|
|
|
if(p->mem_alias)
|
|
|
|
for(LNODEID lnm=lfirst(p->mem_alias); lnm; lnm=lnext(lnm))
|
|
|
|
add_mem_order(((AVRMEM_ALIAS *) ldata(lnm))->desc);
|
|
|
|
}
|
2022-06-28 19:14:29 +00:00
|
|
|
|
2022-07-07 17:32:19 +00:00
|
|
|
nprinted = dev_nprinted;
|
|
|
|
for(LNODEID ln1 = lfirst(part_list); ln1; ln1 = lnext(ln1)) {
|
|
|
|
AVRPART *p = ldata(ln1);
|
|
|
|
int flashsize, flashoffset, flashpagesize, eepromsize , eepromoffset, eeprompagesize;
|
2022-06-28 19:14:29 +00:00
|
|
|
|
2022-07-07 17:32:19 +00:00
|
|
|
if(!descs || tsv)
|
|
|
|
if(dev_nprinted > nprinted) {
|
2022-06-28 19:14:29 +00:00
|
|
|
dev_info("\n");
|
2022-07-07 17:32:19 +00:00
|
|
|
nprinted = dev_nprinted;
|
2022-06-28 19:14:29 +00:00
|
|
|
}
|
|
|
|
|
2022-07-21 17:04:41 +00:00
|
|
|
if(!part_match(partdesc, p->desc) && !part_match(partdesc, p->id))
|
2022-07-07 17:32:19 +00:00
|
|
|
continue;
|
2022-06-28 19:14:29 +00:00
|
|
|
|
2022-07-07 17:32:19 +00:00
|
|
|
if(strct || cmpst)
|
|
|
|
dev_part_strct(p, tsv, cmpst? nullpart: NULL);
|
2022-06-28 19:14:29 +00:00
|
|
|
|
2022-07-07 17:32:19 +00:00
|
|
|
if(raw)
|
|
|
|
dev_part_raw(p);
|
2022-06-28 19:14:29 +00:00
|
|
|
|
|
|
|
// identify core flash and eeprom parameters
|
|
|
|
|
|
|
|
flashsize = flashoffset = flashpagesize = eepromsize = eepromoffset = eeprompagesize = 0;
|
|
|
|
if(p->mem) {
|
|
|
|
for(LNODEID lnm=lfirst(p->mem); lnm; lnm=lnext(lnm)) {
|
|
|
|
AVRMEM *m = ldata(lnm);
|
2022-07-22 22:50:22 +00:00
|
|
|
if(!flashsize && 0==strcmp(m->desc, "flash")) {
|
2022-06-28 19:14:29 +00:00
|
|
|
flashsize = m->size;
|
|
|
|
flashpagesize = m->page_size;
|
|
|
|
flashoffset = m->offset;
|
|
|
|
}
|
2022-07-22 22:50:22 +00:00
|
|
|
if(!eepromsize && 0==strcmp(m->desc, "eeprom")) {
|
2022-06-28 19:14:29 +00:00
|
|
|
eepromsize = m->size;
|
|
|
|
eepromoffset = m->offset;
|
|
|
|
eeprompagesize = m->page_size;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// "real" entries don't seem to have a space in their desc (a bit hackey)
|
2022-07-22 22:50:22 +00:00
|
|
|
if(flashsize && !strchr(p->desc, ' ')) {
|
2022-06-28 19:14:29 +00:00
|
|
|
int ok, nfuses;
|
|
|
|
AVRMEM *m;
|
|
|
|
OPCODE *oc;
|
|
|
|
|
|
|
|
ok = 2047;
|
|
|
|
nfuses = 0;
|
|
|
|
|
|
|
|
if(!p->op[AVR_OP_PGM_ENABLE])
|
|
|
|
ok &= ~DEV_SPI_EN_CE_SIG;
|
|
|
|
|
|
|
|
if(!p->op[AVR_OP_CHIP_ERASE])
|
|
|
|
ok &= ~DEV_SPI_EN_CE_SIG;
|
|
|
|
|
|
|
|
if((m = avr_locate_mem(p, "flash"))) {
|
|
|
|
if((oc = m->op[AVR_OP_LOAD_EXT_ADDR])) {
|
|
|
|
// @@@ to do: check whether address is put at lsb of third byte
|
|
|
|
} else
|
|
|
|
ok &= ~DEV_SPI_LOAD_EXT_ADDR;
|
|
|
|
|
|
|
|
if((oc = m->op[AVR_OP_READ_HI])) {
|
|
|
|
if(cmdok)
|
|
|
|
checkaddr(m->size>>1, 1, AVR_OP_READ_HI, oc, p, m);
|
|
|
|
} else
|
|
|
|
ok &= ~DEV_SPI_PROGMEM;
|
|
|
|
|
|
|
|
if((oc = m->op[AVR_OP_READ_LO])) {
|
|
|
|
if(cmdok)
|
|
|
|
checkaddr(m->size>>1, 1, AVR_OP_READ_LO, oc, p, m);
|
|
|
|
} else
|
|
|
|
ok &= ~DEV_SPI_PROGMEM;
|
|
|
|
|
|
|
|
if((oc = m->op[AVR_OP_WRITE_HI])) {
|
|
|
|
if(cmdok)
|
|
|
|
checkaddr(m->size>>1, 1, AVR_OP_WRITE_HI, oc, p, m);
|
|
|
|
} else
|
|
|
|
ok &= ~DEV_SPI_PROGMEM;
|
|
|
|
|
|
|
|
if((oc = m->op[AVR_OP_WRITE_LO])) {
|
|
|
|
if(cmdok)
|
|
|
|
checkaddr(m->size>>1, 1, AVR_OP_WRITE_LO, oc, p, m);
|
|
|
|
} else
|
|
|
|
ok &= ~DEV_SPI_PROGMEM;
|
|
|
|
|
|
|
|
if((oc = m->op[AVR_OP_LOADPAGE_HI])) {
|
|
|
|
if(cmdok)
|
|
|
|
checkaddr(m->page_size>>1, 1, AVR_OP_LOADPAGE_HI, oc, p, m);
|
|
|
|
} else
|
|
|
|
ok &= ~DEV_SPI_PROGMEM_PAGED;
|
|
|
|
|
|
|
|
if((oc = m->op[AVR_OP_LOADPAGE_LO])) {
|
|
|
|
if(cmdok)
|
|
|
|
checkaddr(m->page_size>>1, 1, AVR_OP_LOADPAGE_LO, oc, p, m);
|
|
|
|
} else
|
|
|
|
ok &= ~DEV_SPI_PROGMEM_PAGED;
|
|
|
|
|
|
|
|
if((oc = m->op[AVR_OP_WRITEPAGE])) {
|
|
|
|
if(cmdok)
|
|
|
|
checkaddr(m->size>>1, m->page_size>>1, AVR_OP_WRITEPAGE, oc, p, m);
|
|
|
|
} else
|
|
|
|
ok &= ~DEV_SPI_PROGMEM_PAGED;
|
|
|
|
} else
|
|
|
|
ok &= ~(DEV_SPI_PROGMEM_PAGED | DEV_SPI_PROGMEM);
|
|
|
|
|
|
|
|
if((m = avr_locate_mem(p, "eeprom"))) {
|
|
|
|
if((oc = m->op[AVR_OP_READ])) {
|
|
|
|
if(cmdok)
|
|
|
|
checkaddr(m->size, 1, AVR_OP_READ, oc, p, m);
|
|
|
|
} else
|
|
|
|
ok &= ~DEV_SPI_EEPROM;
|
|
|
|
|
|
|
|
if((oc = m->op[AVR_OP_WRITE])) {
|
|
|
|
if(cmdok)
|
|
|
|
checkaddr(m->size, 1, AVR_OP_WRITE, oc, p, m);
|
|
|
|
} else
|
|
|
|
ok &= ~DEV_SPI_EEPROM;
|
|
|
|
|
|
|
|
if((oc = m->op[AVR_OP_LOADPAGE_LO])) {
|
|
|
|
if(cmdok)
|
|
|
|
checkaddr(m->page_size, 1, AVR_OP_LOADPAGE_LO, oc, p, m);
|
|
|
|
} else
|
|
|
|
ok &= ~DEV_SPI_EEPROM_PAGED;
|
|
|
|
|
|
|
|
if((oc = m->op[AVR_OP_WRITEPAGE])) {
|
|
|
|
if(cmdok)
|
|
|
|
checkaddr(m->size, m->page_size, AVR_OP_WRITEPAGE, oc, p, m);
|
|
|
|
} else
|
|
|
|
ok &= ~DEV_SPI_EEPROM_PAGED;
|
|
|
|
} else
|
|
|
|
ok &= ~(DEV_SPI_EEPROM_PAGED | DEV_SPI_EEPROM);
|
|
|
|
|
|
|
|
if((m = avr_locate_mem(p, "signature")) && (oc = m->op[AVR_OP_READ])) {
|
|
|
|
if(cmdok)
|
|
|
|
checkaddr(m->size, 1, AVR_OP_READ, oc, p, m);
|
|
|
|
} else
|
|
|
|
ok &= ~DEV_SPI_EN_CE_SIG;
|
|
|
|
|
|
|
|
if((m = avr_locate_mem(p, "calibration")) && (oc = m->op[AVR_OP_READ])) {
|
|
|
|
if(cmdok)
|
|
|
|
checkaddr(m->size, 1, AVR_OP_READ, oc, p, m);
|
|
|
|
} else
|
|
|
|
ok &= ~DEV_SPI_CALIBRATION;
|
|
|
|
|
|
|
|
// actually, some AT90S... parts cannot read, only write lock bits :-0
|
|
|
|
if( ! ((m = avr_locate_mem(p, "lock")) && m->op[AVR_OP_WRITE]))
|
|
|
|
ok &= ~DEV_SPI_LOCK;
|
|
|
|
|
|
|
|
if(((m = avr_locate_mem(p, "fuse")) || (m = avr_locate_mem(p, "lfuse"))) && m->op[AVR_OP_READ] && m->op[AVR_OP_WRITE])
|
|
|
|
nfuses++;
|
|
|
|
else
|
|
|
|
ok &= ~DEV_SPI_LFUSE;
|
|
|
|
|
|
|
|
if((m = avr_locate_mem(p, "hfuse")) && m->op[AVR_OP_READ] && m->op[AVR_OP_WRITE])
|
|
|
|
nfuses++;
|
|
|
|
else
|
|
|
|
ok &= ~DEV_SPI_HFUSE;
|
|
|
|
|
|
|
|
if((m = avr_locate_mem(p, "efuse")) && m->op[AVR_OP_READ] && m->op[AVR_OP_WRITE])
|
|
|
|
nfuses++;
|
|
|
|
else
|
|
|
|
ok &= ~DEV_SPI_EFUSE;
|
|
|
|
|
|
|
|
if(descs) {
|
|
|
|
int len = 16-strlen(p->desc);
|
2022-07-07 17:32:19 +00:00
|
|
|
dev_info("%s '%s' =>%*s [0x%02X, 0x%02X, 0x%02X, 0x%08x, 0x%05x, 0x%03x, 0x%06x, 0x%04x, 0x%03x, %d, 0x%03x, 0x%04x, '%s'], # %s %d\n",
|
|
|
|
tsv || all? ".desc": " ",
|
2022-06-28 19:14:29 +00:00
|
|
|
p->desc, len > 0? len: 0, "",
|
|
|
|
p->signature[0], p->signature[1], p->signature[2],
|
|
|
|
flashoffset, flashsize, flashpagesize,
|
|
|
|
eepromoffset, eepromsize, eeprompagesize,
|
|
|
|
nfuses,
|
|
|
|
ok,
|
|
|
|
p->flags,
|
|
|
|
parttype(p),
|
|
|
|
p->config_file, p->lineno
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(opspi) {
|
|
|
|
printallopcodes(p, "part", p->op);
|
|
|
|
if(p->mem) {
|
|
|
|
for(LNODEID lnm=lfirst(p->mem); lnm; lnm=lnext(lnm)) {
|
|
|
|
AVRMEM *m = ldata(lnm);
|
|
|
|
if(m)
|
|
|
|
printallopcodes(p, m->desc, m->op);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// print wait delays for AVR family parts
|
|
|
|
if(waits) {
|
|
|
|
if(!(p->flags & (AVRPART_HAS_PDI | AVRPART_HAS_UPDI | AVRPART_HAS_TPI | AVRPART_AVR32)))
|
|
|
|
dev_info(".wd_chip_erase %.3f ms %s\n", p->chip_erase_delay/1000.0, p->desc);
|
|
|
|
if(p->mem) {
|
|
|
|
for(LNODEID lnm=lfirst(p->mem); lnm; lnm=lnext(lnm)) {
|
|
|
|
AVRMEM *m = ldata(lnm);
|
|
|
|
// write delays not needed for read-only calibration and signature memories
|
|
|
|
if(strcmp(m->desc, "calibration") && strcmp(m->desc, "signature")) {
|
|
|
|
if(!(p->flags & (AVRPART_HAS_PDI | AVRPART_HAS_UPDI | AVRPART_HAS_TPI | AVRPART_AVR32))) {
|
|
|
|
if(m->min_write_delay == m->max_write_delay)
|
|
|
|
dev_info(".wd_%s %.3f ms %s\n", m->desc, m->min_write_delay/1000.0, p->desc);
|
|
|
|
else {
|
|
|
|
dev_info(".wd_min_%s %.3f ms %s\n", m->desc, m->min_write_delay/1000.0, p->desc);
|
|
|
|
dev_info(".wd_max_%s %.3f ms %s\n", m->desc, m->max_write_delay/1000.0, p->desc);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|