2002-11-30 14:09:12 +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) 2002-2004 Brian S. Dean <bsd@bsdhome.com>
|
main.c, pgm.c, pgm.h: Add setup and teardown hooks to the programmer
definition. If present, call the setup hook immediately after finding
the respective programmer object, and schedule the teardown hook to be
called upon exit. This allows the programmer implementation to
dynamically allocate private programmer data.
avr910.c, butterfly.c, jtagmkI.c, jtagmkII.c, stk500v2.c, usbasp.c,
usbtiny.c: Convert static programmer data into dynamically allocated
data.
git-svn-id: svn://svn.savannah.nongnu.org/avrdude/trunk/avrdude@764 81a1dc3b-b13d-400b-aceb-764788c761c2
2007-11-07 20:36:12 +00:00
|
|
|
* Copyright 2007 Joerg Wunsch <j@uriah.heep.sax.de>
|
2002-11-30 14:09:12 +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.
|
2002-11-30 14:09:12 +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.
|
2002-11-30 14:09:12 +00:00
|
|
|
*
|
2003-02-06 19:08:33 +00:00
|
|
|
* You should have received a copy of the GNU General Public License
|
2012-11-20 14:03:50 +00:00
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
2002-11-30 14:09:12 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
/* $Id$ */
|
|
|
|
|
2003-02-14 20:34:03 +00:00
|
|
|
#include "ac_cfg.h"
|
|
|
|
|
2002-11-30 14:09:12 +00:00
|
|
|
#include <stdio.h>
|
2003-02-11 21:58:07 +00:00
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
2002-11-30 14:09:12 +00:00
|
|
|
|
2007-01-24 21:07:54 +00:00
|
|
|
#include "avrdude.h"
|
2014-05-19 10:01:59 +00:00
|
|
|
#include "libavrdude.h"
|
2002-11-30 14:09:12 +00:00
|
|
|
|
2004-01-29 13:23:59 +00:00
|
|
|
static int pgm_default_2 (struct programmer_t *, AVRPART *);
|
2006-11-20 15:04:09 +00:00
|
|
|
static int pgm_default_3 (struct programmer_t * pgm, AVRPART * p, AVRMEM * mem,
|
|
|
|
unsigned long addr, unsigned char * value);
|
2004-01-29 13:23:59 +00:00
|
|
|
static void pgm_default_4 (struct programmer_t *);
|
2006-11-20 15:04:09 +00:00
|
|
|
static int pgm_default_5 (struct programmer_t * pgm, AVRPART * p, AVRMEM * mem,
|
|
|
|
unsigned long addr, unsigned char data);
|
2007-01-30 13:41:54 +00:00
|
|
|
static void pgm_default_6 (struct programmer_t *, const char *);
|
2004-01-29 13:23:59 +00:00
|
|
|
|
|
|
|
|
|
|
|
static int pgm_default_open (struct programmer_t *pgm, char * name)
|
|
|
|
{
|
2014-06-13 20:07:40 +00:00
|
|
|
avrdude_message(MSG_INFO, "\n%s: Fatal error: Programmer does not support open()",
|
2004-01-29 13:23:59 +00:00
|
|
|
progname);
|
2014-05-16 15:52:25 +00:00
|
|
|
return -1;
|
2004-01-29 13:23:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static int pgm_default_led (struct programmer_t * pgm, int value)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* If programmer has no LEDs, just do nothing.
|
|
|
|
*/
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void pgm_default_powerup_powerdown (struct programmer_t * pgm)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* If programmer does not support powerup/down, just do nothing.
|
|
|
|
*/
|
|
|
|
}
|
2002-11-30 14:09:12 +00:00
|
|
|
|
|
|
|
|
|
|
|
PROGRAMMER * pgm_new(void)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
PROGRAMMER * pgm;
|
2022-08-07 13:05:54 +00:00
|
|
|
char *nulp = cache_string("");
|
2002-11-30 14:09:12 +00:00
|
|
|
|
|
|
|
pgm = (PROGRAMMER *)malloc(sizeof(*pgm));
|
|
|
|
if (pgm == NULL) {
|
2014-06-13 20:07:40 +00:00
|
|
|
avrdude_message(MSG_INFO, "%s: out of memory allocating programmer structure\n",
|
2002-11-30 14:09:12 +00:00
|
|
|
progname);
|
2014-06-17 20:08:28 +00:00
|
|
|
return NULL;
|
2002-11-30 14:09:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
memset(pgm, 0, sizeof(*pgm));
|
|
|
|
|
|
|
|
pgm->id = lcreat(NULL, 0);
|
2014-02-27 13:06:03 +00:00
|
|
|
pgm->usbpid = lcreat(NULL, 0);
|
2002-11-30 14:09:12 +00:00
|
|
|
pgm->desc[0] = 0;
|
|
|
|
pgm->type[0] = 0;
|
2022-08-07 13:05:54 +00:00
|
|
|
pgm->parent_id = nulp;
|
|
|
|
pgm->config_file = nulp;
|
2003-02-22 16:45:13 +00:00
|
|
|
pgm->lineno = 0;
|
2004-01-03 18:36:44 +00:00
|
|
|
pgm->baudrate = 0;
|
2012-01-22 12:31:54 +00:00
|
|
|
pgm->initpgm = NULL;
|
2022-07-07 10:23:05 +00:00
|
|
|
pgm->hvupdi_support = lcreat(NULL, 0);
|
2002-11-30 14:09:12 +00:00
|
|
|
|
2022-08-07 13:05:54 +00:00
|
|
|
pgm->usbdev = nulp;
|
|
|
|
pgm->usbsn = nulp;
|
|
|
|
pgm->usbvendor = nulp;
|
|
|
|
pgm->usbproduct = nulp;
|
|
|
|
|
2013-05-05 13:35:35 +00:00
|
|
|
for (i=0; i<N_PINS; i++) {
|
2002-11-30 14:09:12 +00:00
|
|
|
pgm->pinno[i] = 0;
|
2013-05-05 13:35:35 +00:00
|
|
|
pin_clear_all(&(pgm->pin[i]));
|
|
|
|
}
|
2002-11-30 14:09:12 +00:00
|
|
|
|
2002-12-01 06:35:18 +00:00
|
|
|
/*
|
|
|
|
* mandatory functions - these are called without checking to see
|
|
|
|
* whether they are assigned or not
|
|
|
|
*/
|
2002-11-30 14:09:12 +00:00
|
|
|
pgm->initialize = pgm_default_2;
|
|
|
|
pgm->display = pgm_default_6;
|
|
|
|
pgm->enable = pgm_default_4;
|
|
|
|
pgm->disable = pgm_default_4;
|
2004-01-29 13:23:59 +00:00
|
|
|
pgm->powerup = pgm_default_powerup_powerdown;
|
|
|
|
pgm->powerdown = pgm_default_powerup_powerdown;
|
2002-11-30 14:09:12 +00:00
|
|
|
pgm->program_enable = pgm_default_2;
|
|
|
|
pgm->chip_erase = pgm_default_2;
|
2004-01-29 13:23:59 +00:00
|
|
|
pgm->open = pgm_default_open;
|
2002-11-30 14:09:12 +00:00
|
|
|
pgm->close = pgm_default_4;
|
2006-11-20 15:04:09 +00:00
|
|
|
pgm->read_byte = pgm_default_3;
|
|
|
|
pgm->write_byte = pgm_default_5;
|
2002-11-30 14:09:12 +00:00
|
|
|
|
2004-01-29 13:23:59 +00:00
|
|
|
/*
|
|
|
|
* predefined functions - these functions have a valid default
|
|
|
|
* implementation. Hence, they don't need to be defined in
|
|
|
|
* the programmer.
|
|
|
|
*/
|
|
|
|
pgm->rdy_led = pgm_default_led;
|
|
|
|
pgm->err_led = pgm_default_led;
|
|
|
|
pgm->pgm_led = pgm_default_led;
|
|
|
|
pgm->vfy_led = pgm_default_led;
|
|
|
|
|
2002-12-01 06:35:18 +00:00
|
|
|
/*
|
|
|
|
* optional functions - these are checked to make sure they are
|
|
|
|
* assigned before they are called
|
|
|
|
*/
|
2006-11-20 15:04:09 +00:00
|
|
|
pgm->cmd = NULL;
|
2011-08-23 21:03:36 +00:00
|
|
|
pgm->cmd_tpi = NULL;
|
2009-02-17 15:31:27 +00:00
|
|
|
pgm->spi = NULL;
|
2002-12-01 06:35:18 +00:00
|
|
|
pgm->paged_write = NULL;
|
|
|
|
pgm->paged_load = NULL;
|
2003-03-24 07:09:16 +00:00
|
|
|
pgm->write_setup = NULL;
|
2003-03-17 06:20:02 +00:00
|
|
|
pgm->read_sig_bytes = NULL;
|
2003-07-24 21:26:28 +00:00
|
|
|
pgm->set_vtarget = NULL;
|
|
|
|
pgm->set_varef = NULL;
|
|
|
|
pgm->set_fosc = NULL;
|
2006-10-09 14:34:24 +00:00
|
|
|
pgm->perform_osccal = NULL;
|
2007-11-06 19:42:16 +00:00
|
|
|
pgm->parseextparams = NULL;
|
main.c, pgm.c, pgm.h: Add setup and teardown hooks to the programmer
definition. If present, call the setup hook immediately after finding
the respective programmer object, and schedule the teardown hook to be
called upon exit. This allows the programmer implementation to
dynamically allocate private programmer data.
avr910.c, butterfly.c, jtagmkI.c, jtagmkII.c, stk500v2.c, usbasp.c,
usbtiny.c: Convert static programmer data into dynamically allocated
data.
git-svn-id: svn://svn.savannah.nongnu.org/avrdude/trunk/avrdude@764 81a1dc3b-b13d-400b-aceb-764788c761c2
2007-11-07 20:36:12 +00:00
|
|
|
pgm->setup = NULL;
|
|
|
|
pgm->teardown = NULL;
|
2002-12-01 06:35:18 +00:00
|
|
|
|
2002-11-30 14:09:12 +00:00
|
|
|
return pgm;
|
|
|
|
}
|
|
|
|
|
2012-01-17 20:56:37 +00:00
|
|
|
void pgm_free(PROGRAMMER * const p)
|
|
|
|
{
|
2014-03-12 21:20:32 +00:00
|
|
|
ldestroy_cb(p->id, free);
|
|
|
|
ldestroy_cb(p->usbpid, free);
|
2012-01-17 20:56:37 +00:00
|
|
|
p->id = NULL;
|
2014-04-14 21:41:43 +00:00
|
|
|
p->usbpid = NULL;
|
2022-08-07 13:05:54 +00:00
|
|
|
/* do not free p->parent_id, p->config_file, p->usbdev, p->usbsn, p->usbvendor or p-> usbproduct */
|
2022-07-26 22:43:56 +00:00
|
|
|
/* p->cookie is freed by pgm_teardown */
|
2012-01-17 20:56:37 +00:00
|
|
|
free(p);
|
|
|
|
}
|
2002-11-30 14:09:12 +00:00
|
|
|
|
2013-12-15 12:57:13 +00:00
|
|
|
PROGRAMMER * pgm_dup(const PROGRAMMER * const src)
|
2012-01-22 12:31:54 +00:00
|
|
|
{
|
|
|
|
PROGRAMMER * pgm;
|
2014-04-14 21:41:43 +00:00
|
|
|
LNODEID ln;
|
2012-01-22 12:31:54 +00:00
|
|
|
|
|
|
|
pgm = (PROGRAMMER *)malloc(sizeof(*pgm));
|
|
|
|
if (pgm == NULL) {
|
2014-06-13 20:07:40 +00:00
|
|
|
avrdude_message(MSG_INFO, "%s: out of memory allocating programmer structure\n",
|
2012-01-22 12:31:54 +00:00
|
|
|
progname);
|
2014-06-17 20:08:28 +00:00
|
|
|
return NULL;
|
2012-01-22 12:31:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
memcpy(pgm, src, sizeof(*pgm));
|
|
|
|
|
|
|
|
pgm->id = lcreat(NULL, 0);
|
2014-04-14 21:41:43 +00:00
|
|
|
pgm->usbpid = lcreat(NULL, 0);
|
|
|
|
for (ln = lfirst(src->usbpid); ln; ln = lnext(ln)) {
|
|
|
|
int *ip = malloc(sizeof(int));
|
|
|
|
if (ip == NULL) {
|
2014-06-13 20:07:40 +00:00
|
|
|
avrdude_message(MSG_INFO, "%s: out of memory allocating programmer structure\n",
|
2014-04-14 21:41:43 +00:00
|
|
|
progname);
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
*ip = *(int *) ldata(ln);
|
|
|
|
ladd(pgm->usbpid, ip);
|
|
|
|
}
|
2012-01-22 12:31:54 +00:00
|
|
|
|
|
|
|
return pgm;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2004-01-29 13:23:59 +00:00
|
|
|
static void pgm_default(void)
|
2002-11-30 14:09:12 +00:00
|
|
|
{
|
2014-06-13 20:07:40 +00:00
|
|
|
avrdude_message(MSG_INFO, "%s: programmer operation not supported\n", progname);
|
2002-11-30 14:09:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2004-01-29 13:23:59 +00:00
|
|
|
static int pgm_default_2 (struct programmer_t * pgm, AVRPART * p)
|
2002-11-30 14:09:12 +00:00
|
|
|
{
|
|
|
|
pgm_default();
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2006-11-20 15:04:09 +00:00
|
|
|
static int pgm_default_3 (struct programmer_t * pgm, AVRPART * p, AVRMEM * mem,
|
|
|
|
unsigned long addr, unsigned char * value)
|
|
|
|
{
|
|
|
|
pgm_default();
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2004-01-29 13:23:59 +00:00
|
|
|
static void pgm_default_4 (struct programmer_t * pgm)
|
2002-11-30 14:09:12 +00:00
|
|
|
{
|
|
|
|
pgm_default();
|
|
|
|
}
|
|
|
|
|
2006-11-20 15:04:09 +00:00
|
|
|
static int pgm_default_5 (struct programmer_t * pgm, AVRPART * p, AVRMEM * mem,
|
|
|
|
unsigned long addr, unsigned char data)
|
2002-11-30 14:09:12 +00:00
|
|
|
{
|
|
|
|
pgm_default();
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2007-01-30 13:41:54 +00:00
|
|
|
static void pgm_default_6 (struct programmer_t * pgm, const char * p)
|
2002-11-30 14:09:12 +00:00
|
|
|
{
|
|
|
|
pgm_default();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-01-30 13:41:54 +00:00
|
|
|
void programmer_display(PROGRAMMER * pgm, const char * p)
|
2007-01-24 22:43:46 +00:00
|
|
|
{
|
2014-06-13 20:07:40 +00:00
|
|
|
avrdude_message(MSG_INFO, "%sProgrammer Type : %s\n", p, pgm->type);
|
|
|
|
avrdude_message(MSG_INFO, "%sDescription : %s\n", p, pgm->desc);
|
2007-01-24 22:43:46 +00:00
|
|
|
|
|
|
|
pgm->display(pgm, p);
|
|
|
|
}
|
|
|
|
|
2012-02-01 22:26:58 +00:00
|
|
|
|
|
|
|
void pgm_display_generic_mask(PROGRAMMER * pgm, const char * p, unsigned int show)
|
|
|
|
{
|
|
|
|
if(show & (1<<PPI_AVR_VCC))
|
2014-06-13 20:07:40 +00:00
|
|
|
avrdude_message(MSG_INFO, "%s VCC = %s\n", p, pins_to_str(&pgm->pin[PPI_AVR_VCC]));
|
2012-02-01 22:26:58 +00:00
|
|
|
if(show & (1<<PPI_AVR_BUFF))
|
2014-06-13 20:07:40 +00:00
|
|
|
avrdude_message(MSG_INFO, "%s BUFF = %s\n", p, pins_to_str(&pgm->pin[PPI_AVR_BUFF]));
|
2012-02-01 22:26:58 +00:00
|
|
|
if(show & (1<<PIN_AVR_RESET))
|
2014-06-13 20:07:40 +00:00
|
|
|
avrdude_message(MSG_INFO, "%s RESET = %s\n", p, pins_to_str(&pgm->pin[PIN_AVR_RESET]));
|
2012-02-01 22:26:58 +00:00
|
|
|
if(show & (1<<PIN_AVR_SCK))
|
2014-06-13 20:07:40 +00:00
|
|
|
avrdude_message(MSG_INFO, "%s SCK = %s\n", p, pins_to_str(&pgm->pin[PIN_AVR_SCK]));
|
2012-02-01 22:26:58 +00:00
|
|
|
if(show & (1<<PIN_AVR_MOSI))
|
2014-06-13 20:07:40 +00:00
|
|
|
avrdude_message(MSG_INFO, "%s MOSI = %s\n", p, pins_to_str(&pgm->pin[PIN_AVR_MOSI]));
|
2012-02-01 22:26:58 +00:00
|
|
|
if(show & (1<<PIN_AVR_MISO))
|
2014-06-13 20:07:40 +00:00
|
|
|
avrdude_message(MSG_INFO, "%s MISO = %s\n", p, pins_to_str(&pgm->pin[PIN_AVR_MISO]));
|
2012-02-01 22:26:58 +00:00
|
|
|
if(show & (1<<PIN_LED_ERR))
|
2014-06-13 20:07:40 +00:00
|
|
|
avrdude_message(MSG_INFO, "%s ERR LED = %s\n", p, pins_to_str(&pgm->pin[PIN_LED_ERR]));
|
2012-02-01 22:26:58 +00:00
|
|
|
if(show & (1<<PIN_LED_RDY))
|
2014-06-13 20:07:40 +00:00
|
|
|
avrdude_message(MSG_INFO, "%s RDY LED = %s\n", p, pins_to_str(&pgm->pin[PIN_LED_RDY]));
|
2012-02-01 22:26:58 +00:00
|
|
|
if(show & (1<<PIN_LED_PGM))
|
2014-06-13 20:07:40 +00:00
|
|
|
avrdude_message(MSG_INFO, "%s PGM LED = %s\n", p, pins_to_str(&pgm->pin[PIN_LED_PGM]));
|
2012-02-01 22:26:58 +00:00
|
|
|
if(show & (1<<PIN_LED_VFY))
|
2014-06-13 20:07:40 +00:00
|
|
|
avrdude_message(MSG_INFO, "%s VFY LED = %s\n", p, pins_to_str(&pgm->pin[PIN_LED_VFY]));
|
2012-02-01 22:26:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void pgm_display_generic(PROGRAMMER * pgm, const char * p)
|
|
|
|
{
|
|
|
|
pgm_display_generic_mask(pgm, p, SHOW_ALL_PINS);
|
|
|
|
}
|
|
|
|
|
2007-01-30 13:41:54 +00:00
|
|
|
PROGRAMMER * locate_programmer(LISTID programmers, const char * configid)
|
2007-01-24 22:43:46 +00:00
|
|
|
{
|
|
|
|
LNODEID ln1, ln2;
|
|
|
|
PROGRAMMER * p = NULL;
|
2007-01-30 13:41:54 +00:00
|
|
|
const char * id;
|
2007-01-24 22:43:46 +00:00
|
|
|
int found;
|
|
|
|
|
|
|
|
found = 0;
|
|
|
|
|
|
|
|
for (ln1=lfirst(programmers); ln1 && !found; ln1=lnext(ln1)) {
|
|
|
|
p = ldata(ln1);
|
|
|
|
for (ln2=lfirst(p->id); ln2 && !found; ln2=lnext(ln2)) {
|
|
|
|
id = ldata(ln2);
|
|
|
|
if (strcasecmp(configid, id) == 0)
|
|
|
|
found = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (found)
|
|
|
|
return p;
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2007-01-29 20:41:47 +00:00
|
|
|
/*
|
|
|
|
* Iterate over the list of programmers given as "programmers", and
|
|
|
|
* call the callback function cb for each entry found. cb is being
|
|
|
|
* passed the following arguments:
|
|
|
|
* . the name of the programmer (for -c)
|
|
|
|
* . the descriptive text given in the config file
|
|
|
|
* . the name of the config file this programmer has been defined in
|
|
|
|
* . the line number of the config file this programmer has been defined at
|
|
|
|
* . the "cookie" passed into walk_programmers() (opaque client data)
|
|
|
|
*/
|
|
|
|
void walk_programmers(LISTID programmers, walk_programmers_cb cb, void *cookie)
|
2007-01-24 22:43:46 +00:00
|
|
|
{
|
|
|
|
LNODEID ln1;
|
2011-12-16 20:44:07 +00:00
|
|
|
LNODEID ln2;
|
2007-01-24 22:43:46 +00:00
|
|
|
PROGRAMMER * p;
|
|
|
|
|
2007-01-29 20:41:47 +00:00
|
|
|
for (ln1 = lfirst(programmers); ln1; ln1 = lnext(ln1)) {
|
2007-01-24 22:43:46 +00:00
|
|
|
p = ldata(ln1);
|
2011-12-16 20:44:07 +00:00
|
|
|
for (ln2=lfirst(p->id); ln2; ln2=lnext(ln2)) {
|
|
|
|
cb(ldata(ln2), p->desc, p->config_file, p->lineno, cookie);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Compare function to sort the list of programmers
|
|
|
|
*/
|
|
|
|
static int sort_programmer_compare(PROGRAMMER * p1,PROGRAMMER * p2)
|
|
|
|
{
|
|
|
|
char* id1;
|
|
|
|
char* id2;
|
|
|
|
if(p1 == NULL || p2 == NULL) {
|
|
|
|
return 0;
|
2007-01-24 22:43:46 +00:00
|
|
|
}
|
2011-12-16 20:44:07 +00:00
|
|
|
id1 = ldata(lfirst(p1->id));
|
|
|
|
id2 = ldata(lfirst(p2->id));
|
|
|
|
return strncasecmp(id1,id2,AVR_IDLEN);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Sort the list of programmers given as "programmers"
|
|
|
|
*/
|
|
|
|
void sort_programmers(LISTID programmers)
|
|
|
|
{
|
|
|
|
lsort(programmers,(int (*)(void*, void*)) sort_programmer_compare);
|
2007-01-24 22:43:46 +00:00
|
|
|
}
|
2007-01-29 20:41:47 +00:00
|
|
|
|