2001-01-19 02:46:50 +00:00
|
|
|
/*
|
2003-02-08 04:17:25 +00:00
|
|
|
* avrdude - A Downloader/Uploader for AVR device programmers
|
2004-12-22 01:52:45 +00:00
|
|
|
* Copyright (C) 2000-2004 Brian S. Dean <bsd@bsdhome.com>
|
2001-01-19 02:46:50 +00:00
|
|
|
*
|
2003-02-06 19:08:33 +00:00
|
|
|
* 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.
|
2001-01-19 02:46:50 +00:00
|
|
|
*
|
2003-02-06 19:08:33 +00:00
|
|
|
* 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.
|
2001-01-19 02:46:50 +00:00
|
|
|
*
|
2003-02-06 19:08:33 +00:00
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program; if not, write to the Free Software
|
|
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
2001-01-19 02:46:50 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
/* $Id$ */
|
|
|
|
|
2003-02-14 20:34:03 +00:00
|
|
|
#include "ac_cfg.h"
|
|
|
|
|
2001-01-19 02:46:50 +00:00
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <unistd.h>
|
2001-10-13 03:12:52 +00:00
|
|
|
#include <string.h>
|
2003-11-19 18:11:59 +00:00
|
|
|
#include <sys/time.h>
|
|
|
|
#include <time.h>
|
2001-01-19 02:46:50 +00:00
|
|
|
|
|
|
|
|
|
|
|
#include "avr.h"
|
2001-11-21 02:46:55 +00:00
|
|
|
#include "lists.h"
|
2001-01-24 19:10:34 +00:00
|
|
|
#include "pindefs.h"
|
2001-01-19 02:46:50 +00:00
|
|
|
#include "ppi.h"
|
2005-09-21 00:20:32 +00:00
|
|
|
#include "safemode.h"
|
2001-01-19 02:46:50 +00:00
|
|
|
|
2002-01-12 01:26:09 +00:00
|
|
|
#define DEBUG 0
|
|
|
|
|
2001-10-14 23:17:26 +00:00
|
|
|
extern char * progname;
|
|
|
|
extern char progbuf[];
|
|
|
|
extern PROGRAMMER * pgm;
|
2001-01-19 02:46:50 +00:00
|
|
|
|
|
|
|
|
2002-08-01 02:06:48 +00:00
|
|
|
extern int do_cycles;
|
|
|
|
|
2001-01-19 02:46:50 +00:00
|
|
|
|
2003-03-23 23:22:50 +00:00
|
|
|
int avr_read_byte_default(PROGRAMMER * pgm, AVRPART * p, AVRMEM * mem,
|
|
|
|
unsigned long addr, unsigned char * value)
|
2001-01-19 02:46:50 +00:00
|
|
|
{
|
|
|
|
unsigned char cmd[4];
|
|
|
|
unsigned char res[4];
|
2001-11-21 02:46:55 +00:00
|
|
|
unsigned char data;
|
|
|
|
OPCODE * readop;
|
2001-01-19 02:46:50 +00:00
|
|
|
|
2002-11-30 14:09:12 +00:00
|
|
|
pgm->pgm_led(pgm, ON);
|
|
|
|
pgm->err_led(pgm, OFF);
|
2001-01-24 19:10:34 +00:00
|
|
|
|
2001-11-21 18:54:11 +00:00
|
|
|
/*
|
|
|
|
* figure out what opcode to use
|
|
|
|
*/
|
2001-11-21 02:46:55 +00:00
|
|
|
if (mem->op[AVR_OP_READ_LO]) {
|
|
|
|
if (addr & 0x00000001)
|
|
|
|
readop = mem->op[AVR_OP_READ_HI];
|
|
|
|
else
|
|
|
|
readop = mem->op[AVR_OP_READ_LO];
|
|
|
|
addr = addr / 2;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
readop = mem->op[AVR_OP_READ];
|
|
|
|
}
|
2001-01-22 01:59:47 +00:00
|
|
|
|
2001-11-21 02:46:55 +00:00
|
|
|
if (readop == NULL) {
|
2002-01-12 01:26:09 +00:00
|
|
|
#if DEBUG
|
2001-11-21 02:46:55 +00:00
|
|
|
fprintf(stderr,
|
|
|
|
"avr_read_byte(): operation not supported on memory type \"%s\"\n",
|
|
|
|
p->desc);
|
2002-01-12 01:26:09 +00:00
|
|
|
#endif
|
2001-11-21 02:46:55 +00:00
|
|
|
return -1;
|
2001-01-19 02:46:50 +00:00
|
|
|
}
|
|
|
|
|
2001-11-21 02:46:55 +00:00
|
|
|
memset(cmd, 0, sizeof(cmd));
|
2001-01-19 02:46:50 +00:00
|
|
|
|
2001-11-21 02:46:55 +00:00
|
|
|
avr_set_bits(readop, cmd);
|
|
|
|
avr_set_addr(readop, cmd, addr);
|
2002-11-30 14:09:12 +00:00
|
|
|
pgm->cmd(pgm, cmd, res);
|
2001-11-21 02:46:55 +00:00
|
|
|
data = 0;
|
|
|
|
avr_get_output(readop, res, &data);
|
2001-01-19 02:46:50 +00:00
|
|
|
|
2002-11-30 14:09:12 +00:00
|
|
|
pgm->pgm_led(pgm, OFF);
|
2001-01-24 19:10:34 +00:00
|
|
|
|
2001-11-21 02:46:55 +00:00
|
|
|
*value = data;
|
|
|
|
|
|
|
|
return 0;
|
2001-01-19 02:46:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2003-03-23 23:22:50 +00:00
|
|
|
/*
|
|
|
|
* read a byte of data from the indicated memory region
|
|
|
|
*/
|
|
|
|
int avr_read_byte(PROGRAMMER * pgm, AVRPART * p, AVRMEM * mem,
|
|
|
|
unsigned long addr, unsigned char * value)
|
|
|
|
{
|
2003-09-05 16:40:55 +00:00
|
|
|
int rc;
|
|
|
|
|
2003-03-23 23:22:50 +00:00
|
|
|
if (pgm->read_byte) {
|
2003-09-05 16:40:55 +00:00
|
|
|
rc = pgm->read_byte(pgm, p, mem, addr, value);
|
|
|
|
if (rc == 0) {
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
/* read_byte() method failed, try again with default. */
|
2003-03-23 23:22:50 +00:00
|
|
|
}
|
2003-09-05 16:40:55 +00:00
|
|
|
|
|
|
|
return avr_read_byte_default(pgm, p, mem, addr, value);
|
2003-03-23 23:22:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2003-05-22 02:33:17 +00:00
|
|
|
/*
|
|
|
|
* Return the number of "interesting" bytes in a memory buffer,
|
|
|
|
* "interesting" being defined as up to the last non-0xff data
|
|
|
|
* value. This is useful for determining where to stop when dealing
|
|
|
|
* with "flash" memory, since writing 0xff to flash is typically a
|
|
|
|
* no-op. Always return an even number since flash is word addressed.
|
|
|
|
*/
|
|
|
|
int avr_mem_hiaddr(AVRMEM * mem)
|
|
|
|
{
|
|
|
|
int i, n;
|
|
|
|
|
|
|
|
/* return the highest non-0xff address regardless of how much
|
|
|
|
memory was read */
|
|
|
|
for (i=mem->size-1; i>0; i--) {
|
|
|
|
if (mem->buf[i] != 0xff) {
|
|
|
|
n = i+1;
|
|
|
|
if (n & 0x01)
|
|
|
|
return n+1;
|
|
|
|
else
|
|
|
|
return n;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2001-01-19 02:46:50 +00:00
|
|
|
/*
|
2001-01-20 16:34:28 +00:00
|
|
|
* Read the entirety of the specified memory type into the
|
2001-11-19 17:44:24 +00:00
|
|
|
* corresponding buffer of the avrpart pointed to by 'p'. If size =
|
2001-11-21 02:46:55 +00:00
|
|
|
* 0, read the entire contents, otherwise, read 'size' bytes.
|
2001-01-20 16:34:28 +00:00
|
|
|
*
|
2001-11-21 18:54:11 +00:00
|
|
|
* Return the number of bytes read, or < 0 if an error occurs.
|
2001-11-21 02:46:55 +00:00
|
|
|
*/
|
2002-11-30 14:09:12 +00:00
|
|
|
int avr_read(PROGRAMMER * pgm, AVRPART * p, char * memtype, int size,
|
|
|
|
int verbose)
|
2001-01-19 02:46:50 +00:00
|
|
|
{
|
2001-01-22 01:59:47 +00:00
|
|
|
unsigned char rbyte;
|
2001-10-14 02:53:21 +00:00
|
|
|
unsigned long i;
|
2001-01-22 01:59:47 +00:00
|
|
|
unsigned char * buf;
|
2001-11-21 02:46:55 +00:00
|
|
|
AVRMEM * mem;
|
|
|
|
int rc;
|
2001-01-19 02:46:50 +00:00
|
|
|
|
2001-11-21 02:46:55 +00:00
|
|
|
mem = avr_locate_mem(p, memtype);
|
|
|
|
if (mem == NULL) {
|
|
|
|
fprintf(stderr, "No \"%s\" memory for part %s\n",
|
|
|
|
memtype, p->desc);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
buf = mem->buf;
|
2001-11-19 17:44:24 +00:00
|
|
|
if (size == 0) {
|
2001-11-21 02:46:55 +00:00
|
|
|
size = mem->size;
|
2001-11-19 17:44:24 +00:00
|
|
|
}
|
2001-01-19 02:46:50 +00:00
|
|
|
|
2003-05-22 02:33:17 +00:00
|
|
|
/*
|
|
|
|
* start with all 0xff
|
|
|
|
*/
|
|
|
|
memset(buf, 0xff, size);
|
|
|
|
|
2002-12-01 15:05:56 +00:00
|
|
|
if ((strcmp(mem->desc, "flash")==0) || (strcmp(mem->desc, "eeprom")==0)) {
|
|
|
|
if (pgm->paged_load != NULL) {
|
|
|
|
/*
|
|
|
|
* the programmer supports a paged mode read, perhaps more
|
|
|
|
* efficiently than we can read it directly, so use its routine
|
|
|
|
* instead
|
|
|
|
*/
|
|
|
|
if (mem->paged) {
|
2003-05-22 02:33:17 +00:00
|
|
|
rc = pgm->paged_load(pgm, p, mem, mem->page_size, size);
|
|
|
|
if (rc < 0)
|
|
|
|
return rc;
|
2002-12-01 15:05:56 +00:00
|
|
|
}
|
|
|
|
else {
|
2003-05-22 02:33:17 +00:00
|
|
|
rc = pgm->paged_load(pgm, p, mem, pgm->page_size, size);
|
|
|
|
if (rc < 0)
|
|
|
|
return rc;
|
2002-12-01 15:05:56 +00:00
|
|
|
}
|
2003-05-22 02:33:17 +00:00
|
|
|
if (strcasecmp(mem->desc, "flash") == 0)
|
|
|
|
return avr_mem_hiaddr(mem);
|
|
|
|
else
|
|
|
|
return rc;
|
2002-12-01 15:05:56 +00:00
|
|
|
}
|
2002-12-01 06:35:18 +00:00
|
|
|
}
|
|
|
|
|
2003-03-17 06:20:02 +00:00
|
|
|
if (strcmp(mem->desc, "signature") == 0) {
|
|
|
|
if (pgm->read_sig_bytes) {
|
|
|
|
return pgm->read_sig_bytes(pgm, p, mem);
|
|
|
|
}
|
|
|
|
}
|
2002-12-01 06:35:18 +00:00
|
|
|
|
2001-01-22 01:59:47 +00:00
|
|
|
for (i=0; i<size; i++) {
|
2002-11-30 14:09:12 +00:00
|
|
|
rc = avr_read_byte(pgm, p, mem, i, &rbyte);
|
2001-11-21 02:46:55 +00:00
|
|
|
if (rc != 0) {
|
|
|
|
fprintf(stderr, "avr_read(): error reading address 0x%04lx\n", i);
|
2002-01-12 01:26:09 +00:00
|
|
|
if (rc == -1)
|
|
|
|
fprintf(stderr,
|
|
|
|
" read operation not supported for memory \"%s\"\n",
|
|
|
|
memtype);
|
2001-11-21 02:46:55 +00:00
|
|
|
return -2;
|
|
|
|
}
|
2001-01-22 01:59:47 +00:00
|
|
|
buf[i] = rbyte;
|
2003-07-29 22:08:21 +00:00
|
|
|
report_progress(i, size, NULL);
|
2002-08-01 01:00:03 +00:00
|
|
|
}
|
2001-01-19 02:46:50 +00:00
|
|
|
|
2003-05-22 02:33:17 +00:00
|
|
|
if (strcasecmp(mem->desc, "flash") == 0)
|
|
|
|
return avr_mem_hiaddr(mem);
|
|
|
|
else
|
|
|
|
return i;
|
2001-01-19 02:46:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2001-10-14 02:53:21 +00:00
|
|
|
/*
|
2001-11-21 18:54:11 +00:00
|
|
|
* write a page data at the specified address
|
2001-10-14 02:53:21 +00:00
|
|
|
*/
|
2002-11-30 14:09:12 +00:00
|
|
|
int avr_write_page(PROGRAMMER * pgm, AVRPART * p, AVRMEM * mem,
|
2001-11-21 02:46:55 +00:00
|
|
|
unsigned long addr)
|
2001-10-14 02:53:21 +00:00
|
|
|
{
|
|
|
|
unsigned char cmd[4];
|
|
|
|
unsigned char res[4];
|
2001-11-21 02:46:55 +00:00
|
|
|
OPCODE * wp;
|
|
|
|
|
|
|
|
wp = mem->op[AVR_OP_WRITEPAGE];
|
|
|
|
if (wp == NULL) {
|
|
|
|
fprintf(stderr,
|
|
|
|
"avr_write_page(): memory \"%s\" not configured for page writes\n",
|
|
|
|
mem->desc);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2001-11-21 18:54:11 +00:00
|
|
|
/*
|
|
|
|
* if this memory is word-addressable, adjust the address
|
|
|
|
* accordingly
|
|
|
|
*/
|
2002-12-01 15:05:56 +00:00
|
|
|
if ((mem->op[AVR_OP_LOADPAGE_LO]) || (mem->op[AVR_OP_READ_LO]))
|
2001-11-21 02:46:55 +00:00
|
|
|
addr = addr / 2;
|
2001-10-14 02:53:21 +00:00
|
|
|
|
2002-11-30 14:09:12 +00:00
|
|
|
pgm->pgm_led(pgm, ON);
|
|
|
|
pgm->err_led(pgm, OFF);
|
2001-10-14 02:53:21 +00:00
|
|
|
|
2001-11-21 02:46:55 +00:00
|
|
|
memset(cmd, 0, sizeof(cmd));
|
2001-10-14 02:53:21 +00:00
|
|
|
|
2001-11-21 02:46:55 +00:00
|
|
|
avr_set_bits(wp, cmd);
|
|
|
|
avr_set_addr(wp, cmd, addr);
|
2002-11-30 14:09:12 +00:00
|
|
|
pgm->cmd(pgm, cmd, res);
|
2001-10-14 02:53:21 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* since we don't know what voltage the target AVR is powered by, be
|
2003-11-30 16:42:10 +00:00
|
|
|
* conservative and delay the max amount the spec says to wait
|
2001-10-14 02:53:21 +00:00
|
|
|
*/
|
2001-11-21 02:46:55 +00:00
|
|
|
usleep(mem->max_write_delay);
|
2001-10-14 02:53:21 +00:00
|
|
|
|
2002-11-30 14:09:12 +00:00
|
|
|
pgm->pgm_led(pgm, OFF);
|
2001-10-14 02:53:21 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2003-03-23 23:22:50 +00:00
|
|
|
int avr_write_byte_default(PROGRAMMER * pgm, AVRPART * p, AVRMEM * mem,
|
2001-10-14 23:17:26 +00:00
|
|
|
unsigned long addr, unsigned char data)
|
2001-01-19 02:46:50 +00:00
|
|
|
{
|
2001-01-22 01:59:47 +00:00
|
|
|
unsigned char cmd[4];
|
|
|
|
unsigned char res[4];
|
2001-01-19 02:46:50 +00:00
|
|
|
unsigned char r;
|
|
|
|
int ready;
|
|
|
|
int tries;
|
2003-11-19 18:11:59 +00:00
|
|
|
unsigned long start_time;
|
|
|
|
unsigned long prog_time;
|
2001-01-19 02:46:50 +00:00
|
|
|
unsigned char b;
|
2001-01-22 01:59:47 +00:00
|
|
|
unsigned short caddr;
|
2001-11-21 02:46:55 +00:00
|
|
|
OPCODE * writeop;
|
|
|
|
int rc;
|
2002-01-12 01:26:09 +00:00
|
|
|
int readok=0;
|
2003-11-19 18:11:59 +00:00
|
|
|
struct timeval tv;
|
2001-01-19 02:46:50 +00:00
|
|
|
|
2001-11-21 02:46:55 +00:00
|
|
|
if (!mem->paged) {
|
2001-10-14 02:53:21 +00:00
|
|
|
/*
|
|
|
|
* check to see if the write is necessary by reading the existing
|
|
|
|
* value and only write if we are changing the value; we can't
|
2001-10-16 23:32:30 +00:00
|
|
|
* use this optimization for paged addressing.
|
2001-10-14 02:53:21 +00:00
|
|
|
*/
|
2002-11-30 14:09:12 +00:00
|
|
|
rc = avr_read_byte(pgm, p, mem, addr, &b);
|
2002-01-12 01:26:09 +00:00
|
|
|
if (rc != 0) {
|
|
|
|
if (rc != -1) {
|
2002-02-14 02:48:07 +00:00
|
|
|
return -2;
|
2002-01-12 01:26:09 +00:00
|
|
|
}
|
|
|
|
/*
|
|
|
|
* the read operation is not support on this memory type
|
|
|
|
*/
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
readok = 1;
|
|
|
|
if (b == data) {
|
|
|
|
return 0;
|
|
|
|
}
|
2001-10-14 02:53:21 +00:00
|
|
|
}
|
|
|
|
}
|
2001-11-21 02:46:55 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* determine which memory opcode to use
|
|
|
|
*/
|
|
|
|
if (mem->op[AVR_OP_WRITE_LO]) {
|
|
|
|
if (addr & 0x01)
|
|
|
|
writeop = mem->op[AVR_OP_WRITE_HI];
|
|
|
|
else
|
|
|
|
writeop = mem->op[AVR_OP_WRITE_LO];
|
|
|
|
caddr = addr / 2;
|
|
|
|
}
|
|
|
|
else if (mem->op[AVR_OP_LOADPAGE_LO]) {
|
|
|
|
if (addr & 0x01)
|
|
|
|
writeop = mem->op[AVR_OP_LOADPAGE_HI];
|
|
|
|
else
|
|
|
|
writeop = mem->op[AVR_OP_LOADPAGE_LO];
|
|
|
|
caddr = addr / 2;
|
|
|
|
}
|
2001-10-14 02:53:21 +00:00
|
|
|
else {
|
2001-11-21 02:46:55 +00:00
|
|
|
writeop = mem->op[AVR_OP_WRITE];
|
|
|
|
caddr = addr;
|
2001-01-19 02:46:50 +00:00
|
|
|
}
|
|
|
|
|
2001-11-21 02:46:55 +00:00
|
|
|
if (writeop == NULL) {
|
2002-01-12 01:26:09 +00:00
|
|
|
#if DEBUG
|
2001-11-21 02:46:55 +00:00
|
|
|
fprintf(stderr,
|
2002-01-12 01:26:09 +00:00
|
|
|
"avr_write_byte(): write not supported for memory type \"%s\"\n",
|
2001-11-21 02:46:55 +00:00
|
|
|
mem->desc);
|
2002-01-12 01:26:09 +00:00
|
|
|
#endif
|
2001-11-21 02:46:55 +00:00
|
|
|
return -1;
|
|
|
|
}
|
2001-01-24 19:10:34 +00:00
|
|
|
|
2001-01-22 01:59:47 +00:00
|
|
|
|
2002-11-30 14:09:12 +00:00
|
|
|
pgm->pgm_led(pgm, ON);
|
|
|
|
pgm->err_led(pgm, OFF);
|
2001-01-19 02:46:50 +00:00
|
|
|
|
2001-11-21 02:46:55 +00:00
|
|
|
memset(cmd, 0, sizeof(cmd));
|
2001-01-19 02:46:50 +00:00
|
|
|
|
2001-11-21 02:46:55 +00:00
|
|
|
avr_set_bits(writeop, cmd);
|
|
|
|
avr_set_addr(writeop, cmd, caddr);
|
|
|
|
avr_set_input(writeop, cmd, data);
|
2002-11-30 14:09:12 +00:00
|
|
|
pgm->cmd(pgm, cmd, res);
|
2001-01-19 02:46:50 +00:00
|
|
|
|
2001-11-21 02:46:55 +00:00
|
|
|
if (mem->paged) {
|
2001-10-14 02:53:21 +00:00
|
|
|
/*
|
2002-07-27 20:55:01 +00:00
|
|
|
* in paged addressing, single bytes to be written to the memory
|
2001-10-14 02:53:21 +00:00
|
|
|
* page complete immediately, we only need to delay when we commit
|
2001-10-16 23:32:30 +00:00
|
|
|
* the whole page via the avr_write_page() routine.
|
2001-10-14 02:53:21 +00:00
|
|
|
*/
|
2002-11-30 14:09:12 +00:00
|
|
|
pgm->pgm_led(pgm, OFF);
|
2001-10-14 02:53:21 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2002-01-12 01:26:09 +00:00
|
|
|
if (readok == 0) {
|
|
|
|
/*
|
|
|
|
* read operation not supported for this memory type, just wait
|
|
|
|
* the max programming time and then return
|
|
|
|
*/
|
|
|
|
usleep(mem->max_write_delay); /* maximum write delay */
|
2002-11-30 14:09:12 +00:00
|
|
|
pgm->pgm_led(pgm, OFF);
|
2002-01-12 01:26:09 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2001-01-19 02:46:50 +00:00
|
|
|
tries = 0;
|
|
|
|
ready = 0;
|
|
|
|
while (!ready) {
|
2002-02-14 02:48:07 +00:00
|
|
|
|
2001-11-21 02:46:55 +00:00
|
|
|
if ((data == mem->readback[0]) ||
|
|
|
|
(data == mem->readback[1])) {
|
2001-01-19 02:46:50 +00:00
|
|
|
/*
|
|
|
|
* use an extra long delay when we happen to be writing values
|
|
|
|
* used for polled data read-back. In this case, polling
|
|
|
|
* doesn't work, and we need to delay the worst case write time
|
|
|
|
* specified for the chip.
|
|
|
|
*/
|
2001-11-21 02:46:55 +00:00
|
|
|
usleep(mem->max_write_delay);
|
2002-11-30 14:09:12 +00:00
|
|
|
rc = avr_read_byte(pgm, p, mem, addr, &r);
|
2001-11-21 02:46:55 +00:00
|
|
|
if (rc != 0) {
|
2002-11-30 14:09:12 +00:00
|
|
|
pgm->pgm_led(pgm, OFF);
|
|
|
|
pgm->err_led(pgm, OFF);
|
2002-02-14 02:48:07 +00:00
|
|
|
return -5;
|
2001-11-21 02:46:55 +00:00
|
|
|
}
|
2001-01-19 02:46:50 +00:00
|
|
|
}
|
2003-11-19 18:11:59 +00:00
|
|
|
else {
|
|
|
|
gettimeofday (&tv, NULL);
|
|
|
|
start_time = (tv.tv_sec * 1000000) + tv.tv_usec;
|
|
|
|
do {
|
|
|
|
/*
|
|
|
|
* Do polling, but timeout after max_write_delay.
|
|
|
|
*/
|
|
|
|
rc = avr_read_byte(pgm, p, mem, addr, &r);
|
|
|
|
if (rc != 0) {
|
|
|
|
pgm->pgm_led(pgm, OFF);
|
|
|
|
pgm->err_led(pgm, ON);
|
|
|
|
return -4;
|
|
|
|
}
|
|
|
|
gettimeofday (&tv, NULL);
|
|
|
|
prog_time = (tv.tv_sec * 1000000) + tv.tv_usec;
|
|
|
|
} while ((r != data) &&
|
|
|
|
((prog_time-start_time) < mem->max_write_delay));
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* At this point we either have a valid readback or the
|
|
|
|
* max_write_delay is expired.
|
|
|
|
*/
|
2002-01-12 01:26:09 +00:00
|
|
|
|
2001-10-14 02:53:21 +00:00
|
|
|
if (r == data) {
|
2001-01-19 02:46:50 +00:00
|
|
|
ready = 1;
|
|
|
|
}
|
2002-02-14 02:48:07 +00:00
|
|
|
else if (mem->pwroff_after_write) {
|
|
|
|
/*
|
|
|
|
* The device has been flagged as power-off after write to this
|
|
|
|
* memory type. The reason we don't just blindly follow the
|
|
|
|
* flag is that the power-off advice may only apply to some
|
|
|
|
* memory bits but not all. We only actually power-off the
|
|
|
|
* device if the data read back does not match what we wrote.
|
|
|
|
*/
|
2002-11-30 14:09:12 +00:00
|
|
|
pgm->pgm_led(pgm, OFF);
|
2002-02-14 02:48:07 +00:00
|
|
|
fprintf(stderr,
|
|
|
|
"%s: this device must be powered off and back on to continue\n",
|
|
|
|
progname);
|
|
|
|
if (pgm->pinno[PPI_AVR_VCC]) {
|
|
|
|
fprintf(stderr, "%s: attempting to do this now ...\n", progname);
|
2002-11-30 14:09:12 +00:00
|
|
|
pgm->powerdown(pgm);
|
2002-02-14 02:48:07 +00:00
|
|
|
usleep(250000);
|
2002-11-30 14:09:12 +00:00
|
|
|
rc = pgm->initialize(pgm, p);
|
2002-02-14 02:48:07 +00:00
|
|
|
if (rc < 0) {
|
|
|
|
fprintf(stderr, "%s: initialization failed, rc=%d\n", progname, rc);
|
|
|
|
fprintf(stderr,
|
|
|
|
"%s: can't re-initialize device after programming the "
|
|
|
|
"%s bits\n", progname, mem->desc);
|
|
|
|
fprintf(stderr,
|
|
|
|
"%s: you must manually power-down the device and restart\n"
|
2003-02-06 05:13:32 +00:00
|
|
|
"%s: %s to continue.\n",
|
|
|
|
progname, progname, progname);
|
2002-02-14 02:48:07 +00:00
|
|
|
return -3;
|
|
|
|
}
|
|
|
|
|
|
|
|
fprintf(stderr, "%s: device was successfully re-initialized\n",
|
|
|
|
progname);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2001-01-19 02:46:50 +00:00
|
|
|
tries++;
|
2001-10-14 02:53:21 +00:00
|
|
|
if (!ready && tries > 5) {
|
2001-01-19 02:46:50 +00:00
|
|
|
/*
|
2002-02-14 02:48:07 +00:00
|
|
|
* we wrote the data, but after waiting for what should have
|
|
|
|
* been plenty of time, the memory cell still doesn't match what
|
|
|
|
* we wrote. Indicate a write error.
|
2001-01-19 02:46:50 +00:00
|
|
|
*/
|
2002-11-30 14:09:12 +00:00
|
|
|
pgm->pgm_led(pgm, OFF);
|
|
|
|
pgm->err_led(pgm, ON);
|
2002-01-12 01:26:09 +00:00
|
|
|
|
2002-02-14 02:48:07 +00:00
|
|
|
return -6;
|
2001-01-19 02:46:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2002-11-30 14:09:12 +00:00
|
|
|
pgm->pgm_led(pgm, OFF);
|
2001-01-19 02:46:50 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2003-03-23 23:22:50 +00:00
|
|
|
/*
|
|
|
|
* write a byte of data at the specified address
|
|
|
|
*/
|
|
|
|
int avr_write_byte(PROGRAMMER * pgm, AVRPART * p, AVRMEM * mem,
|
|
|
|
unsigned long addr, unsigned char data)
|
|
|
|
{
|
2005-09-21 00:20:32 +00:00
|
|
|
|
|
|
|
unsigned char safemode_lfuse;
|
|
|
|
unsigned char safemode_hfuse;
|
|
|
|
unsigned char safemode_efuse;
|
|
|
|
unsigned char safemode_fuse;
|
|
|
|
|
|
|
|
/* If we write the fuses, then we need to tell safemode that they *should* change */
|
|
|
|
safemode_memfuses(0, &safemode_lfuse, &safemode_hfuse, &safemode_efuse, &safemode_fuse);
|
|
|
|
|
|
|
|
if (strcmp(mem->desc, "fuse")==0) {
|
|
|
|
safemode_fuse = data;
|
|
|
|
}
|
|
|
|
if (strcmp(mem->desc, "lfuse")==0) {
|
|
|
|
safemode_lfuse = data;
|
|
|
|
}
|
|
|
|
if (strcmp(mem->desc, "hfuse")==0) {
|
|
|
|
safemode_hfuse = data;
|
|
|
|
}
|
|
|
|
if (strcmp(mem->desc, "efuse")==0) {
|
|
|
|
safemode_efuse = data;
|
|
|
|
}
|
|
|
|
|
|
|
|
safemode_memfuses(1, &safemode_lfuse, &safemode_hfuse, &safemode_efuse, &safemode_fuse);
|
|
|
|
|
2003-09-05 16:40:55 +00:00
|
|
|
int rc;
|
|
|
|
|
2003-03-23 23:22:50 +00:00
|
|
|
if (pgm->write_byte) {
|
2003-09-05 16:40:55 +00:00
|
|
|
rc = pgm->write_byte(pgm, p, mem, addr, data);
|
|
|
|
if (rc == 0) {
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
/* write_byte() method failed, try again with default. */
|
2003-03-23 23:22:50 +00:00
|
|
|
}
|
2003-09-05 16:40:55 +00:00
|
|
|
|
|
|
|
return avr_write_byte_default(pgm, p, mem, addr, data);
|
2003-03-23 23:22:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2001-01-19 02:46:50 +00:00
|
|
|
/*
|
2001-11-21 18:54:11 +00:00
|
|
|
* Write the whole memory region of the specified memory from the
|
|
|
|
* corresponding buffer of the avrpart pointed to by 'p'. Write up to
|
|
|
|
* 'size' bytes from the buffer. Data is only written if the new data
|
|
|
|
* value is different from the existing data value. Data beyond
|
|
|
|
* 'size' bytes is not affected.
|
2001-01-20 16:34:28 +00:00
|
|
|
*
|
|
|
|
* Return the number of bytes written, or -1 if an error occurs.
|
2001-01-19 02:46:50 +00:00
|
|
|
*/
|
2002-11-30 14:09:12 +00:00
|
|
|
int avr_write(PROGRAMMER * pgm, AVRPART * p, char * memtype, int size,
|
|
|
|
int verbose)
|
2001-01-19 02:46:50 +00:00
|
|
|
{
|
2001-01-22 01:59:47 +00:00
|
|
|
int rc;
|
|
|
|
int wsize;
|
2001-10-14 02:53:21 +00:00
|
|
|
unsigned long i;
|
2001-01-22 01:59:47 +00:00
|
|
|
unsigned char data;
|
2001-02-08 01:05:05 +00:00
|
|
|
int werror;
|
2001-11-21 02:46:55 +00:00
|
|
|
AVRMEM * m;
|
|
|
|
|
|
|
|
m = avr_locate_mem(p, memtype);
|
|
|
|
if (m == NULL) {
|
|
|
|
fprintf(stderr, "No \"%s\" memory for part %s\n",
|
|
|
|
memtype, p->desc);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2002-11-30 14:09:12 +00:00
|
|
|
pgm->err_led(pgm, OFF);
|
2001-01-24 19:10:34 +00:00
|
|
|
|
2002-08-01 01:00:03 +00:00
|
|
|
werror = 0;
|
2001-02-08 01:05:05 +00:00
|
|
|
|
2001-11-21 02:46:55 +00:00
|
|
|
wsize = m->size;
|
2001-01-22 01:59:47 +00:00
|
|
|
if (size < wsize) {
|
|
|
|
wsize = size;
|
2001-01-19 02:46:50 +00:00
|
|
|
}
|
2001-01-22 01:59:47 +00:00
|
|
|
else if (size > wsize) {
|
2001-01-20 16:34:28 +00:00
|
|
|
fprintf(stderr,
|
2003-03-05 04:30:20 +00:00
|
|
|
"%s: WARNING: %d bytes requested, but memory region is only %d"
|
|
|
|
"bytes\n"
|
2001-01-20 16:34:28 +00:00
|
|
|
"%sOnly %d bytes will actually be written\n",
|
2001-01-22 01:59:47 +00:00
|
|
|
progname, size, wsize,
|
|
|
|
progbuf, wsize);
|
2001-01-20 16:34:28 +00:00
|
|
|
}
|
|
|
|
|
2003-03-05 04:30:20 +00:00
|
|
|
if ((strcmp(m->desc, "flash")==0) || (strcmp(m->desc, "eeprom")==0)) {
|
|
|
|
if (pgm->paged_write != NULL) {
|
|
|
|
/*
|
|
|
|
* the programmer supports a paged mode write, perhaps more
|
|
|
|
* efficiently than we can read it directly, so use its routine
|
|
|
|
* instead
|
|
|
|
*/
|
|
|
|
return pgm->paged_write(pgm, p, m, m->page_size, size);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2003-03-24 07:09:16 +00:00
|
|
|
if (pgm->write_setup) {
|
|
|
|
pgm->write_setup(pgm, p, m);
|
|
|
|
}
|
|
|
|
|
2001-01-22 01:59:47 +00:00
|
|
|
for (i=0; i<wsize; i++) {
|
2001-11-21 02:46:55 +00:00
|
|
|
data = m->buf[i];
|
2003-07-29 22:08:21 +00:00
|
|
|
report_progress(i, wsize, NULL);
|
|
|
|
|
2002-11-30 14:09:12 +00:00
|
|
|
rc = avr_write_byte(pgm, p, m, i, data);
|
2001-01-19 02:46:50 +00:00
|
|
|
if (rc) {
|
|
|
|
fprintf(stderr, " ***failed; ");
|
|
|
|
fprintf(stderr, "\n");
|
2002-11-30 14:09:12 +00:00
|
|
|
pgm->err_led(pgm, ON);
|
2001-02-08 01:05:05 +00:00
|
|
|
werror = 1;
|
|
|
|
}
|
2001-10-14 02:53:21 +00:00
|
|
|
|
2001-11-21 02:46:55 +00:00
|
|
|
if (m->paged) {
|
2001-11-21 18:54:11 +00:00
|
|
|
/*
|
|
|
|
* check to see if it is time to flush the page with a page
|
|
|
|
* write
|
|
|
|
*/
|
2001-11-21 02:46:55 +00:00
|
|
|
if (((i % m->page_size) == m->page_size-1) ||
|
2001-10-14 02:53:21 +00:00
|
|
|
(i == wsize-1)) {
|
2002-11-30 14:09:12 +00:00
|
|
|
rc = avr_write_page(pgm, p, m, i);
|
2001-10-14 02:53:21 +00:00
|
|
|
if (rc) {
|
|
|
|
fprintf(stderr,
|
2001-11-21 02:46:55 +00:00
|
|
|
" *** page %ld (addresses 0x%04lx - 0x%04lx) failed "
|
|
|
|
"to write\n",
|
|
|
|
i % m->page_size,
|
|
|
|
i - m->page_size + 1, i);
|
2001-10-14 02:53:21 +00:00
|
|
|
fprintf(stderr, "\n");
|
2002-11-30 14:09:12 +00:00
|
|
|
pgm->err_led(pgm, ON);
|
2001-10-14 02:53:21 +00:00
|
|
|
werror = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2001-02-08 01:05:05 +00:00
|
|
|
if (werror) {
|
|
|
|
/*
|
|
|
|
* make sure the error led stay on if there was a previous write
|
2003-11-30 16:42:10 +00:00
|
|
|
* error, otherwise it gets cleared in avr_write_byte()
|
2001-02-08 01:05:05 +00:00
|
|
|
*/
|
2002-11-30 14:09:12 +00:00
|
|
|
pgm->err_led(pgm, ON);
|
2001-01-22 01:59:47 +00:00
|
|
|
}
|
2001-01-19 02:46:50 +00:00
|
|
|
}
|
2005-09-21 00:20:32 +00:00
|
|
|
|
2001-01-22 01:59:47 +00:00
|
|
|
return i;
|
2001-01-19 02:46:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* read the AVR device's signature bytes
|
|
|
|
*/
|
2002-11-30 14:09:12 +00:00
|
|
|
int avr_signature(PROGRAMMER * pgm, AVRPART * p)
|
2001-01-19 02:46:50 +00:00
|
|
|
{
|
2001-11-21 02:46:55 +00:00
|
|
|
int rc;
|
2001-01-19 02:46:50 +00:00
|
|
|
|
2003-07-29 22:08:21 +00:00
|
|
|
report_progress (0,1,"Reading");
|
2002-11-30 14:09:12 +00:00
|
|
|
rc = avr_read(pgm, p, "signature", 0, 0);
|
2001-11-21 02:46:55 +00:00
|
|
|
if (rc < 0) {
|
2003-11-30 16:42:10 +00:00
|
|
|
fprintf(stderr,
|
2001-11-21 02:46:55 +00:00
|
|
|
"%s: error reading signature data for part \"%s\", rc=%d\n",
|
|
|
|
progname, p->desc, rc);
|
|
|
|
return -1;
|
2001-01-19 02:46:50 +00:00
|
|
|
}
|
2003-07-29 22:08:21 +00:00
|
|
|
report_progress (1,1,NULL);
|
2001-01-19 02:46:50 +00:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2001-01-20 16:34:28 +00:00
|
|
|
/*
|
2001-01-26 17:22:40 +00:00
|
|
|
* Verify the memory buffer of p with that of v. The byte range of v,
|
|
|
|
* may be a subset of p. The byte range of p should cover the whole
|
|
|
|
* chip's memory size.
|
|
|
|
*
|
2003-11-30 16:42:10 +00:00
|
|
|
* Return the number of bytes verified, or -1 if they don't match.
|
2001-01-20 16:34:28 +00:00
|
|
|
*/
|
2001-11-21 02:46:55 +00:00
|
|
|
int avr_verify(AVRPART * p, AVRPART * v, char * memtype, int size)
|
2001-01-19 02:46:50 +00:00
|
|
|
{
|
|
|
|
int i;
|
|
|
|
unsigned char * buf1, * buf2;
|
2001-01-20 16:34:28 +00:00
|
|
|
int vsize;
|
2001-11-21 02:46:55 +00:00
|
|
|
AVRMEM * a, * b;
|
|
|
|
|
|
|
|
a = avr_locate_mem(p, memtype);
|
|
|
|
if (a == NULL) {
|
|
|
|
fprintf(stderr,
|
|
|
|
"avr_verify(): memory type \"%s\" not defined for part %s\n",
|
|
|
|
memtype, p->desc);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
b = avr_locate_mem(v, memtype);
|
|
|
|
if (b == NULL) {
|
|
|
|
fprintf(stderr,
|
|
|
|
"avr_verify(): memory type \"%s\" not defined for part %s\n",
|
|
|
|
memtype, v->desc);
|
|
|
|
return -1;
|
|
|
|
}
|
2001-01-19 02:46:50 +00:00
|
|
|
|
2001-11-21 02:46:55 +00:00
|
|
|
buf1 = a->buf;
|
|
|
|
buf2 = b->buf;
|
|
|
|
vsize = a->size;
|
2001-01-19 02:46:50 +00:00
|
|
|
|
2001-01-20 16:34:28 +00:00
|
|
|
if (vsize < size) {
|
|
|
|
fprintf(stderr,
|
|
|
|
"%s: WARNING: requested verification for %d bytes\n"
|
|
|
|
"%s%s memory region only contains %d bytes\n"
|
|
|
|
"%sOnly %d bytes will be verified.\n",
|
|
|
|
progname, size,
|
2001-11-21 02:46:55 +00:00
|
|
|
progbuf, memtype, vsize,
|
2001-01-20 16:34:28 +00:00
|
|
|
progbuf, vsize);
|
|
|
|
size = vsize;
|
|
|
|
}
|
|
|
|
|
2001-01-19 02:46:50 +00:00
|
|
|
for (i=0; i<size; i++) {
|
|
|
|
if (buf1[i] != buf2[i]) {
|
|
|
|
fprintf(stderr,
|
2002-02-14 02:48:07 +00:00
|
|
|
"%s: verification error, first mismatch at byte 0x%04x\n"
|
2001-01-19 02:46:50 +00:00
|
|
|
"%s0x%02x != 0x%02x\n",
|
|
|
|
progname, i,
|
|
|
|
progbuf, buf1[i], buf2[i]);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2001-01-20 16:34:28 +00:00
|
|
|
return size;
|
2001-01-19 02:46:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2002-11-30 14:09:12 +00:00
|
|
|
int avr_get_cycle_count(PROGRAMMER * pgm, AVRPART * p, int * cycles)
|
2002-08-01 01:00:03 +00:00
|
|
|
{
|
|
|
|
AVRMEM * a;
|
2004-01-03 18:04:54 +00:00
|
|
|
unsigned int cycle_count = 0;
|
|
|
|
unsigned char v1;
|
2002-08-01 01:00:03 +00:00
|
|
|
int rc;
|
2004-01-03 18:04:54 +00:00
|
|
|
int i;
|
2002-08-01 01:00:03 +00:00
|
|
|
|
|
|
|
a = avr_locate_mem(p, "eeprom");
|
|
|
|
if (a == NULL) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2004-01-03 18:04:54 +00:00
|
|
|
for (i=4; i>0; i--) {
|
|
|
|
rc = avr_read_byte(pgm, p, a, a->size-i, &v1);
|
2002-08-01 01:00:03 +00:00
|
|
|
if (rc < 0) {
|
|
|
|
fprintf(stderr, "%s: WARNING: can't read memory for cycle count, rc=%d\n",
|
|
|
|
progname, rc);
|
|
|
|
return -1;
|
|
|
|
}
|
2004-01-03 18:04:54 +00:00
|
|
|
cycle_count = (cycle_count << 8) | v1;
|
2002-10-11 19:36:56 +00:00
|
|
|
}
|
|
|
|
|
2004-01-03 18:04:54 +00:00
|
|
|
/*
|
|
|
|
* If the EEPROM is erased, the cycle count reads 0xffffffff.
|
|
|
|
* In this case we return a cycle_count of zero.
|
|
|
|
* So, the calling function don't have to care about whether or not
|
|
|
|
* the cycle count was initialized.
|
|
|
|
*/
|
|
|
|
if (cycle_count == 0xffffffff) {
|
|
|
|
cycle_count = 0;
|
2002-11-01 14:40:23 +00:00
|
|
|
}
|
|
|
|
|
2004-01-03 18:04:54 +00:00
|
|
|
*cycles = (int) cycle_count;
|
2002-11-06 02:19:57 +00:00
|
|
|
|
|
|
|
return 0;
|
2002-08-01 01:00:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2002-11-30 14:09:12 +00:00
|
|
|
int avr_put_cycle_count(PROGRAMMER * pgm, AVRPART * p, int cycles)
|
2002-08-01 01:00:03 +00:00
|
|
|
{
|
|
|
|
AVRMEM * a;
|
2004-01-03 18:04:54 +00:00
|
|
|
unsigned char v1;
|
2002-08-01 01:00:03 +00:00
|
|
|
int rc;
|
2004-01-03 18:04:54 +00:00
|
|
|
int i;
|
2002-08-01 01:00:03 +00:00
|
|
|
|
|
|
|
a = avr_locate_mem(p, "eeprom");
|
|
|
|
if (a == NULL) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2004-01-03 18:04:54 +00:00
|
|
|
for (i=1; i<=4; i++) {
|
|
|
|
v1 = cycles & 0xff;
|
|
|
|
cycles = cycles >> 8;
|
2002-08-01 01:00:03 +00:00
|
|
|
|
2004-01-03 18:04:54 +00:00
|
|
|
rc = avr_write_byte(pgm, p, a, a->size-i, v1);
|
|
|
|
if (rc < 0) {
|
|
|
|
fprintf(stderr, "%s: WARNING: can't write memory for cycle count, rc=%d\n",
|
|
|
|
progname, rc);
|
|
|
|
return -1;
|
|
|
|
}
|
2002-10-11 19:36:56 +00:00
|
|
|
}
|
2004-01-03 18:04:54 +00:00
|
|
|
|
|
|
|
return 0;
|
2002-10-11 19:36:56 +00:00
|
|
|
}
|
2004-01-03 18:04:54 +00:00
|
|
|
|
|
|
|
int avr_chip_erase(PROGRAMMER * pgm, AVRPART * p)
|
|
|
|
{
|
|
|
|
int cycles;
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
if (do_cycles) {
|
|
|
|
rc = avr_get_cycle_count(pgm, p, &cycles);
|
|
|
|
/*
|
|
|
|
* Don't update the cycle counter, if read failed
|
|
|
|
*/
|
|
|
|
if(rc != 0) {
|
|
|
|
do_cycles = 0;
|
|
|
|
}
|
2002-08-01 01:00:03 +00:00
|
|
|
}
|
2004-01-03 18:04:54 +00:00
|
|
|
|
|
|
|
rc = pgm->chip_erase(pgm, p);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Don't update the cycle counter, if erase failed
|
|
|
|
*/
|
|
|
|
if (do_cycles && (rc == 0)) {
|
|
|
|
cycles++;
|
|
|
|
fprintf(stderr, "%s: erase-rewrite cycle count is now %d\n",
|
|
|
|
progname, cycles);
|
|
|
|
avr_put_cycle_count(pgm, p, cycles);
|
2002-08-01 01:00:03 +00:00
|
|
|
}
|
|
|
|
|
2004-01-03 18:04:54 +00:00
|
|
|
return rc;
|
2002-08-01 01:00:03 +00:00
|
|
|
}
|