2000-03-27 08:39:14 +00:00
|
|
|
/* tc-avr.c -- Assembler code for the ATMEL AVR
|
|
|
|
|
2008-02-14 13:04:29 +00:00
|
|
|
Copyright 1999, 2000, 2001, 2002, 2004, 2005, 2006, 2007, 2008
|
2005-02-23 12:28:06 +00:00
|
|
|
Free Software Foundation, Inc.
|
2000-03-27 08:39:14 +00:00
|
|
|
Contributed by Denis Chertykov <denisc@overta.ru>
|
|
|
|
|
|
|
|
This file is part of GAS, the GNU Assembler.
|
|
|
|
|
|
|
|
GAS is free software; you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
2007-07-03 11:01:12 +00:00
|
|
|
the Free Software Foundation; either version 3, or (at your option)
|
2000-03-27 08:39:14 +00:00
|
|
|
any later version.
|
|
|
|
|
|
|
|
GAS 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 GAS; see the file COPYING. If not, write to
|
2005-05-05 09:13:19 +00:00
|
|
|
the Free Software Foundation, 51 Franklin Street - Fifth Floor,
|
|
|
|
Boston, MA 02110-1301, USA. */
|
2000-03-27 08:39:14 +00:00
|
|
|
|
|
|
|
#include "as.h"
|
2001-09-19 05:33:36 +00:00
|
|
|
#include "safe-ctype.h"
|
2000-03-27 08:39:14 +00:00
|
|
|
#include "subsegs.h"
|
|
|
|
|
2000-06-07 17:42:44 +00:00
|
|
|
struct avr_opcodes_s
|
|
|
|
{
|
2005-10-12 10:56:46 +00:00
|
|
|
char * name;
|
|
|
|
char * constraints;
|
|
|
|
int insn_size; /* In words. */
|
|
|
|
int isa;
|
|
|
|
unsigned int bin_opcode;
|
2000-06-07 17:42:44 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
#define AVR_INSN(NAME, CONSTR, OPCODE, SIZE, ISA, BIN) \
|
|
|
|
{#NAME, CONSTR, SIZE, ISA, BIN},
|
|
|
|
|
|
|
|
struct avr_opcodes_s avr_opcodes[] =
|
|
|
|
{
|
|
|
|
#include "opcode/avr.h"
|
|
|
|
{NULL, NULL, 0, 0, 0}
|
|
|
|
};
|
|
|
|
|
2000-03-27 08:39:14 +00:00
|
|
|
const char comment_chars[] = ";";
|
|
|
|
const char line_comment_chars[] = "#";
|
|
|
|
const char line_separator_chars[] = "$";
|
|
|
|
|
|
|
|
const char *md_shortopts = "m:";
|
|
|
|
struct mcu_type_s
|
|
|
|
{
|
|
|
|
char *name;
|
|
|
|
int isa;
|
|
|
|
int mach;
|
|
|
|
};
|
|
|
|
|
2001-11-10 09:35:53 +00:00
|
|
|
/* XXX - devices that don't seem to exist (renamed, replaced with larger
|
|
|
|
ones, or planned but never produced), left here for compatibility.
|
|
|
|
TODO: hide them in show_mcu_list output? */
|
|
|
|
|
2000-03-27 08:39:14 +00:00
|
|
|
static struct mcu_type_s mcu_types[] =
|
|
|
|
{
|
2006-05-24 07:36:12 +00:00
|
|
|
{"avr1", AVR_ISA_TINY1, bfd_mach_avr1},
|
|
|
|
{"avr2", AVR_ISA_TINY2, bfd_mach_avr2},
|
2008-01-16 17:59:07 +00:00
|
|
|
{"avr3", AVR_ISA_AVR3, bfd_mach_avr3},
|
2006-05-24 07:36:12 +00:00
|
|
|
{"avr4", AVR_ISA_M8, bfd_mach_avr4},
|
|
|
|
{"avr5", AVR_ISA_ALL, bfd_mach_avr5},
|
|
|
|
{"avr6", AVR_ISA_ALL, bfd_mach_avr6},
|
|
|
|
{"at90s1200", AVR_ISA_1200, bfd_mach_avr1},
|
|
|
|
{"attiny11", AVR_ISA_TINY1, bfd_mach_avr1},
|
|
|
|
{"attiny12", AVR_ISA_TINY1, bfd_mach_avr1},
|
|
|
|
{"attiny15", AVR_ISA_TINY1, bfd_mach_avr1},
|
|
|
|
{"attiny28", AVR_ISA_TINY1, bfd_mach_avr1},
|
|
|
|
{"at90s2313", AVR_ISA_2xxx, bfd_mach_avr2},
|
|
|
|
{"at90s2323", AVR_ISA_2xxx, bfd_mach_avr2},
|
|
|
|
{"at90s2333", AVR_ISA_2xxx, bfd_mach_avr2}, /* XXX -> 4433 */
|
|
|
|
{"at90s2343", AVR_ISA_2xxx, bfd_mach_avr2},
|
|
|
|
{"attiny22", AVR_ISA_2xxx, bfd_mach_avr2}, /* XXX -> 2343 */
|
2008-02-14 13:04:29 +00:00
|
|
|
{"attiny26", AVR_ISA_2xxe, bfd_mach_avr2},
|
2006-05-24 07:36:12 +00:00
|
|
|
{"at90s4433", AVR_ISA_2xxx, bfd_mach_avr2},
|
|
|
|
{"at90s4414", AVR_ISA_2xxx, bfd_mach_avr2}, /* XXX -> 8515 */
|
|
|
|
{"at90s4434", AVR_ISA_2xxx, bfd_mach_avr2}, /* XXX -> 8535 */
|
|
|
|
{"at90s8515", AVR_ISA_2xxx, bfd_mach_avr2},
|
|
|
|
{"at90s8535", AVR_ISA_2xxx, bfd_mach_avr2},
|
|
|
|
{"at90c8534", AVR_ISA_2xxx, bfd_mach_avr2},
|
2008-01-23 17:36:23 +00:00
|
|
|
{"at86rf401", AVR_ISA_RF401, bfd_mach_avr2},
|
2006-05-24 07:36:12 +00:00
|
|
|
{"attiny13", AVR_ISA_TINY2, bfd_mach_avr2},
|
2008-06-25 16:19:11 +00:00
|
|
|
{"attiny13a", AVR_ISA_TINY2, bfd_mach_avr2},
|
2006-05-24 07:36:12 +00:00
|
|
|
{"attiny2313", AVR_ISA_TINY2, bfd_mach_avr2},
|
|
|
|
{"attiny261", AVR_ISA_TINY2, bfd_mach_avr2},
|
|
|
|
{"attiny461", AVR_ISA_TINY2, bfd_mach_avr2},
|
|
|
|
{"attiny861", AVR_ISA_TINY2, bfd_mach_avr2},
|
|
|
|
{"attiny24", AVR_ISA_TINY2, bfd_mach_avr2},
|
|
|
|
{"attiny44", AVR_ISA_TINY2, bfd_mach_avr2},
|
|
|
|
{"attiny84", AVR_ISA_TINY2, bfd_mach_avr2},
|
|
|
|
{"attiny25", AVR_ISA_TINY2, bfd_mach_avr2},
|
|
|
|
{"attiny45", AVR_ISA_TINY2, bfd_mach_avr2},
|
|
|
|
{"attiny85", AVR_ISA_TINY2, bfd_mach_avr2},
|
2007-10-08 10:33:27 +00:00
|
|
|
{"attiny43u", AVR_ISA_TINY2, bfd_mach_avr2},
|
|
|
|
{"attiny48", AVR_ISA_TINY2, bfd_mach_avr2},
|
2007-11-07 17:59:05 +00:00
|
|
|
{"attiny88", AVR_ISA_TINY2, bfd_mach_avr2},
|
2006-05-24 07:36:12 +00:00
|
|
|
{"atmega103", AVR_ISA_M103, bfd_mach_avr3},
|
|
|
|
{"at43usb320", AVR_ISA_M103, bfd_mach_avr3},
|
|
|
|
{"at43usb355", AVR_ISA_M603, bfd_mach_avr3},
|
|
|
|
{"at76c711", AVR_ISA_M603, bfd_mach_avr3},
|
2008-01-16 17:59:07 +00:00
|
|
|
{"at90usb82", AVR_ISA_USB162, bfd_mach_avr3},
|
|
|
|
{"at90usb162", AVR_ISA_USB162, bfd_mach_avr3},
|
2008-03-28 21:51:38 +00:00
|
|
|
{"attiny167", AVR_ISA_TINY3, bfd_mach_avr3},
|
2006-05-24 07:36:12 +00:00
|
|
|
{"atmega48", AVR_ISA_PWMx, bfd_mach_avr4},
|
2007-11-07 17:24:59 +00:00
|
|
|
{"atmega48p", AVR_ISA_PWMx, bfd_mach_avr4},
|
2006-05-24 07:36:12 +00:00
|
|
|
{"atmega8", AVR_ISA_M8, bfd_mach_avr4},
|
|
|
|
{"atmega88", AVR_ISA_PWMx, bfd_mach_avr4},
|
2007-11-07 17:24:59 +00:00
|
|
|
{"atmega88p", AVR_ISA_PWMx, bfd_mach_avr4},
|
2006-05-24 07:36:12 +00:00
|
|
|
{"atmega8515", AVR_ISA_M8, bfd_mach_avr4},
|
|
|
|
{"atmega8535", AVR_ISA_M8, bfd_mach_avr4},
|
2007-04-26 17:18:23 +00:00
|
|
|
{"atmega8hva", AVR_ISA_PWMx, bfd_mach_avr4},
|
2007-04-02 18:42:36 +00:00
|
|
|
{"at90pwm1", AVR_ISA_PWMx, bfd_mach_avr4},
|
2006-05-24 07:36:12 +00:00
|
|
|
{"at90pwm2", AVR_ISA_PWMx, bfd_mach_avr4},
|
2007-10-12 16:28:03 +00:00
|
|
|
{"at90pwm2b", AVR_ISA_PWMx, bfd_mach_avr4},
|
2006-05-24 07:36:12 +00:00
|
|
|
{"at90pwm3", AVR_ISA_PWMx, bfd_mach_avr4},
|
2007-10-12 16:28:03 +00:00
|
|
|
{"at90pwm3b", AVR_ISA_PWMx, bfd_mach_avr4},
|
2006-05-24 07:36:12 +00:00
|
|
|
{"atmega16", AVR_ISA_M323, bfd_mach_avr5},
|
|
|
|
{"atmega161", AVR_ISA_M161, bfd_mach_avr5},
|
|
|
|
{"atmega162", AVR_ISA_M323, bfd_mach_avr5},
|
|
|
|
{"atmega163", AVR_ISA_M161, bfd_mach_avr5},
|
2006-06-19 16:58:29 +00:00
|
|
|
{"atmega164p", AVR_ISA_M323, bfd_mach_avr5},
|
2006-05-24 07:36:12 +00:00
|
|
|
{"atmega165", AVR_ISA_M323, bfd_mach_avr5},
|
2006-06-19 16:58:29 +00:00
|
|
|
{"atmega165p", AVR_ISA_M323, bfd_mach_avr5},
|
2006-05-24 07:36:12 +00:00
|
|
|
{"atmega168", AVR_ISA_M323, bfd_mach_avr5},
|
2007-11-07 17:24:59 +00:00
|
|
|
{"atmega168p", AVR_ISA_M323, bfd_mach_avr5},
|
2006-05-24 07:36:12 +00:00
|
|
|
{"atmega169", AVR_ISA_M323, bfd_mach_avr5},
|
2006-06-19 16:58:29 +00:00
|
|
|
{"atmega169p", AVR_ISA_M323, bfd_mach_avr5},
|
2006-05-24 07:36:12 +00:00
|
|
|
{"atmega32", AVR_ISA_M323, bfd_mach_avr5},
|
|
|
|
{"atmega323", AVR_ISA_M323, bfd_mach_avr5},
|
2006-06-19 16:58:29 +00:00
|
|
|
{"atmega324p", AVR_ISA_M323, bfd_mach_avr5},
|
2006-05-24 07:36:12 +00:00
|
|
|
{"atmega325", AVR_ISA_M323, bfd_mach_avr5},
|
2007-04-02 18:42:36 +00:00
|
|
|
{"atmega325p", AVR_ISA_M323, bfd_mach_avr5},
|
2007-11-07 17:24:59 +00:00
|
|
|
{"atmega328p", AVR_ISA_M323, bfd_mach_avr5},
|
2006-05-24 07:36:12 +00:00
|
|
|
{"atmega329", AVR_ISA_M323, bfd_mach_avr5},
|
2007-04-02 18:42:36 +00:00
|
|
|
{"atmega329p", AVR_ISA_M323, bfd_mach_avr5},
|
2006-05-24 07:36:12 +00:00
|
|
|
{"atmega3250", AVR_ISA_M323, bfd_mach_avr5},
|
2007-04-02 18:42:36 +00:00
|
|
|
{"atmega3250p",AVR_ISA_M323, bfd_mach_avr5},
|
2006-05-24 07:36:12 +00:00
|
|
|
{"atmega3290", AVR_ISA_M323, bfd_mach_avr5},
|
2007-04-02 18:42:36 +00:00
|
|
|
{"atmega3290p",AVR_ISA_M323, bfd_mach_avr5},
|
2006-05-24 07:36:12 +00:00
|
|
|
{"atmega406", AVR_ISA_M323, bfd_mach_avr5},
|
|
|
|
{"atmega64", AVR_ISA_M323, bfd_mach_avr5},
|
|
|
|
{"atmega640", AVR_ISA_M323, bfd_mach_avr5},
|
|
|
|
{"atmega644", AVR_ISA_M323, bfd_mach_avr5},
|
2006-06-19 16:58:29 +00:00
|
|
|
{"atmega644p", AVR_ISA_M323, bfd_mach_avr5},
|
2006-05-24 07:36:12 +00:00
|
|
|
{"atmega128", AVR_ISA_M128, bfd_mach_avr5},
|
|
|
|
{"atmega1280", AVR_ISA_M128, bfd_mach_avr5},
|
|
|
|
{"atmega1281", AVR_ISA_M128, bfd_mach_avr5},
|
2007-11-16 17:25:28 +00:00
|
|
|
{"atmega1284p",AVR_ISA_M128, bfd_mach_avr5},
|
2006-05-24 07:36:12 +00:00
|
|
|
{"atmega645", AVR_ISA_M323, bfd_mach_avr5},
|
|
|
|
{"atmega649", AVR_ISA_M323, bfd_mach_avr5},
|
|
|
|
{"atmega6450", AVR_ISA_M323, bfd_mach_avr5},
|
|
|
|
{"atmega6490", AVR_ISA_M323, bfd_mach_avr5},
|
2007-04-26 17:18:23 +00:00
|
|
|
{"atmega16hva",AVR_ISA_M323, bfd_mach_avr5},
|
2006-05-24 07:36:12 +00:00
|
|
|
{"at90can32" , AVR_ISA_M323, bfd_mach_avr5},
|
|
|
|
{"at90can64" , AVR_ISA_M323, bfd_mach_avr5},
|
|
|
|
{"at90can128", AVR_ISA_M128, bfd_mach_avr5},
|
2007-10-08 10:39:17 +00:00
|
|
|
{"at90pwm216", AVR_ISA_M323, bfd_mach_avr5},
|
|
|
|
{"at90pwm316", AVR_ISA_M323, bfd_mach_avr5},
|
2008-03-28 19:24:52 +00:00
|
|
|
{"atmega32c1", AVR_ISA_M323, bfd_mach_avr5},
|
2008-03-27 14:52:35 +00:00
|
|
|
{"atmega32m1", AVR_ISA_M323, bfd_mach_avr5},
|
2008-03-28 21:04:22 +00:00
|
|
|
{"atmega32u4", AVR_ISA_M323, bfd_mach_avr5},
|
Add support for attiny261, attiny461, attiny861, attiny25, attiny45,
attiny85, attiny24, attiny44, attiny84, at90pwm2, at90pwm3, atmega164,
atmega324, atmega644, atmega329, atmega3290, atmega649, atmega6490,
atmega406, atmega640, atmega1280, atmega1281, at90can32, at90can64,
at90usb646, at90usb647, at90usb1286 and at90usb1287.
Move atmega48 and atmega88 from AVR_ISA_M8 to AVR_ISA_PWMx.
2006-04-07 15:18:08 +00:00
|
|
|
{"at90usb646", AVR_ISA_M323, bfd_mach_avr5},
|
|
|
|
{"at90usb647", AVR_ISA_M323, bfd_mach_avr5},
|
|
|
|
{"at90usb1286",AVR_ISA_M128, bfd_mach_avr5},
|
|
|
|
{"at90usb1287",AVR_ISA_M128, bfd_mach_avr5},
|
2006-05-24 07:36:12 +00:00
|
|
|
{"at94k", AVR_ISA_94K, bfd_mach_avr5},
|
|
|
|
{"atmega2560", AVR_ISA_ALL, bfd_mach_avr6},
|
|
|
|
{"atmega2561", AVR_ISA_ALL, bfd_mach_avr6},
|
2000-03-27 08:39:14 +00:00
|
|
|
{NULL, 0, 0}
|
|
|
|
};
|
|
|
|
|
|
|
|
/* Current MCU type. */
|
2005-10-12 10:56:46 +00:00
|
|
|
static struct mcu_type_s default_mcu = {"avr2", AVR_ISA_2xxx,bfd_mach_avr2};
|
|
|
|
static struct mcu_type_s * avr_mcu = & default_mcu;
|
2000-03-27 08:39:14 +00:00
|
|
|
|
2000-07-03 22:25:33 +00:00
|
|
|
/* AVR target-specific switches. */
|
|
|
|
struct avr_opt_s
|
|
|
|
{
|
2005-10-12 10:56:46 +00:00
|
|
|
int all_opcodes; /* -mall-opcodes: accept all known AVR opcodes. */
|
|
|
|
int no_skip_bug; /* -mno-skip-bug: no warnings for skipping 2-word insns. */
|
|
|
|
int no_wrap; /* -mno-wrap: reject rjmp/rcall with 8K wrap-around. */
|
2000-07-03 22:25:33 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
static struct avr_opt_s avr_opt = { 0, 0, 0 };
|
|
|
|
|
2000-03-27 08:39:14 +00:00
|
|
|
const char EXP_CHARS[] = "eE";
|
|
|
|
const char FLT_CHARS[] = "dD";
|
2005-10-12 10:56:46 +00:00
|
|
|
|
|
|
|
static void avr_set_arch (int);
|
2000-03-27 08:39:14 +00:00
|
|
|
|
|
|
|
/* The target specific pseudo-ops which we support. */
|
|
|
|
const pseudo_typeS md_pseudo_table[] =
|
|
|
|
{
|
|
|
|
{"arch", avr_set_arch, 0},
|
|
|
|
{ NULL, NULL, 0}
|
|
|
|
};
|
|
|
|
|
|
|
|
#define LDI_IMMEDIATE(x) (((x) & 0xf) | (((x) << 4) & 0xf00))
|
|
|
|
|
2005-10-12 10:56:46 +00:00
|
|
|
#define EXP_MOD_NAME(i) exp_mod[i].name
|
|
|
|
#define EXP_MOD_RELOC(i) exp_mod[i].reloc
|
|
|
|
#define EXP_MOD_NEG_RELOC(i) exp_mod[i].neg_reloc
|
|
|
|
#define HAVE_PM_P(i) exp_mod[i].have_pm
|
2000-03-27 08:39:14 +00:00
|
|
|
|
|
|
|
struct exp_mod_s
|
|
|
|
{
|
2005-10-12 10:56:46 +00:00
|
|
|
char * name;
|
|
|
|
bfd_reloc_code_real_type reloc;
|
|
|
|
bfd_reloc_code_real_type neg_reloc;
|
|
|
|
int have_pm;
|
2000-03-27 08:39:14 +00:00
|
|
|
};
|
|
|
|
|
2000-07-28 00:42:18 +00:00
|
|
|
static struct exp_mod_s exp_mod[] =
|
|
|
|
{
|
2000-03-27 08:39:14 +00:00
|
|
|
{"hh8", BFD_RELOC_AVR_HH8_LDI, BFD_RELOC_AVR_HH8_LDI_NEG, 1},
|
|
|
|
{"pm_hh8", BFD_RELOC_AVR_HH8_LDI_PM, BFD_RELOC_AVR_HH8_LDI_PM_NEG, 0},
|
|
|
|
{"hi8", BFD_RELOC_AVR_HI8_LDI, BFD_RELOC_AVR_HI8_LDI_NEG, 1},
|
|
|
|
{"pm_hi8", BFD_RELOC_AVR_HI8_LDI_PM, BFD_RELOC_AVR_HI8_LDI_PM_NEG, 0},
|
|
|
|
{"lo8", BFD_RELOC_AVR_LO8_LDI, BFD_RELOC_AVR_LO8_LDI_NEG, 1},
|
|
|
|
{"pm_lo8", BFD_RELOC_AVR_LO8_LDI_PM, BFD_RELOC_AVR_LO8_LDI_PM_NEG, 0},
|
2006-03-03 15:25:31 +00:00
|
|
|
{"hlo8", BFD_RELOC_AVR_HH8_LDI, BFD_RELOC_AVR_HH8_LDI_NEG, 0},
|
|
|
|
{"hhi8", BFD_RELOC_AVR_MS8_LDI, BFD_RELOC_AVR_MS8_LDI_NEG, 0},
|
2000-03-27 08:39:14 +00:00
|
|
|
};
|
|
|
|
|
2006-01-11 17:39:50 +00:00
|
|
|
/* A union used to store indicies into the exp_mod[] array
|
|
|
|
in a hash table which expects void * data types. */
|
|
|
|
typedef union
|
|
|
|
{
|
|
|
|
void * ptr;
|
|
|
|
int index;
|
|
|
|
} mod_index;
|
|
|
|
|
2000-03-27 08:39:14 +00:00
|
|
|
/* Opcode hash table. */
|
|
|
|
static struct hash_control *avr_hash;
|
|
|
|
|
|
|
|
/* Reloc modifiers hash control (hh8,hi8,lo8,pm_xx). */
|
|
|
|
static struct hash_control *avr_mod_hash;
|
|
|
|
|
2000-07-03 22:25:33 +00:00
|
|
|
#define OPTION_MMCU 'm'
|
2005-10-12 10:56:46 +00:00
|
|
|
enum options
|
|
|
|
{
|
|
|
|
OPTION_ALL_OPCODES = OPTION_MD_BASE + 1,
|
|
|
|
OPTION_NO_SKIP_BUG,
|
|
|
|
OPTION_NO_WRAP
|
|
|
|
};
|
2000-03-27 08:39:14 +00:00
|
|
|
|
2000-07-28 00:42:18 +00:00
|
|
|
struct option md_longopts[] =
|
|
|
|
{
|
2000-07-03 22:25:33 +00:00
|
|
|
{ "mmcu", required_argument, NULL, OPTION_MMCU },
|
|
|
|
{ "mall-opcodes", no_argument, NULL, OPTION_ALL_OPCODES },
|
|
|
|
{ "mno-skip-bug", no_argument, NULL, OPTION_NO_SKIP_BUG },
|
|
|
|
{ "mno-wrap", no_argument, NULL, OPTION_NO_WRAP },
|
|
|
|
{ NULL, no_argument, NULL, 0 }
|
2000-03-27 08:39:14 +00:00
|
|
|
};
|
|
|
|
|
2000-07-28 00:42:18 +00:00
|
|
|
size_t md_longopts_size = sizeof (md_longopts);
|
2000-07-03 22:25:33 +00:00
|
|
|
|
|
|
|
/* Display nicely formatted list of known MCU names. */
|
2000-07-28 00:42:18 +00:00
|
|
|
|
2000-07-03 22:25:33 +00:00
|
|
|
static void
|
2005-10-12 10:56:46 +00:00
|
|
|
show_mcu_list (FILE *stream)
|
2000-07-03 22:25:33 +00:00
|
|
|
{
|
|
|
|
int i, x;
|
|
|
|
|
|
|
|
fprintf (stream, _("Known MCU names:"));
|
|
|
|
x = 1000;
|
2000-09-15 01:06:52 +00:00
|
|
|
|
2000-07-03 22:25:33 +00:00
|
|
|
for (i = 0; mcu_types[i].name; i++)
|
|
|
|
{
|
|
|
|
int len = strlen (mcu_types[i].name);
|
2000-09-15 01:06:52 +00:00
|
|
|
|
2000-07-03 22:25:33 +00:00
|
|
|
x += len + 1;
|
2000-09-15 01:06:52 +00:00
|
|
|
|
2000-07-03 22:25:33 +00:00
|
|
|
if (x < 75)
|
2000-07-28 00:42:18 +00:00
|
|
|
fprintf (stream, " %s", mcu_types[i].name);
|
2000-07-03 22:25:33 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
fprintf (stream, "\n %s", mcu_types[i].name);
|
|
|
|
x = len + 2;
|
|
|
|
}
|
|
|
|
}
|
2000-09-15 01:06:52 +00:00
|
|
|
|
2000-07-28 00:42:18 +00:00
|
|
|
fprintf (stream, "\n");
|
2000-07-03 22:25:33 +00:00
|
|
|
}
|
|
|
|
|
2000-03-27 08:39:14 +00:00
|
|
|
static inline char *
|
2005-10-12 10:56:46 +00:00
|
|
|
skip_space (char *s)
|
2000-03-27 08:39:14 +00:00
|
|
|
{
|
|
|
|
while (*s == ' ' || *s == '\t')
|
|
|
|
++s;
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Extract one word from FROM and copy it to TO. */
|
2000-07-28 00:42:18 +00:00
|
|
|
|
2000-03-27 08:39:14 +00:00
|
|
|
static char *
|
|
|
|
extract_word (char *from, char *to, int limit)
|
|
|
|
{
|
|
|
|
char *op_start;
|
|
|
|
char *op_end;
|
|
|
|
int size = 0;
|
|
|
|
|
|
|
|
/* Drop leading whitespace. */
|
|
|
|
from = skip_space (from);
|
|
|
|
*to = 0;
|
2000-07-28 00:42:18 +00:00
|
|
|
|
2000-03-27 08:39:14 +00:00
|
|
|
/* Find the op code end. */
|
2000-07-28 00:42:18 +00:00
|
|
|
for (op_start = op_end = from; *op_end != 0 && is_part_of_name (*op_end);)
|
2000-03-27 08:39:14 +00:00
|
|
|
{
|
|
|
|
to[size++] = *op_end++;
|
|
|
|
if (size + 1 >= limit)
|
|
|
|
break;
|
|
|
|
}
|
2000-09-15 01:06:52 +00:00
|
|
|
|
2000-03-27 08:39:14 +00:00
|
|
|
to[size] = 0;
|
|
|
|
return op_end;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
2005-10-12 10:56:46 +00:00
|
|
|
md_estimate_size_before_relax (fragS *fragp ATTRIBUTE_UNUSED,
|
|
|
|
asection *seg ATTRIBUTE_UNUSED)
|
2000-03-27 08:39:14 +00:00
|
|
|
{
|
|
|
|
abort ();
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2005-10-12 10:56:46 +00:00
|
|
|
md_show_usage (FILE *stream)
|
2000-03-27 08:39:14 +00:00
|
|
|
{
|
2000-07-03 22:25:33 +00:00
|
|
|
fprintf (stream,
|
|
|
|
_("AVR options:\n"
|
2000-03-27 08:39:14 +00:00
|
|
|
" -mmcu=[avr-name] select microcontroller variant\n"
|
|
|
|
" [avr-name] can be:\n"
|
2000-06-27 01:45:30 +00:00
|
|
|
" avr1 - AT90S1200, ATtiny1x, ATtiny28\n"
|
|
|
|
" avr2 - AT90S2xxx, AT90S4xxx, AT90S8xxx, ATtiny22\n"
|
2007-10-31 18:11:28 +00:00
|
|
|
" avr3 - ATmega103\n"
|
|
|
|
" avr4 - ATmega8, ATmega88\n"
|
2000-06-27 01:45:30 +00:00
|
|
|
" avr5 - ATmega161, ATmega163, ATmega32, AT94K\n"
|
2000-03-27 08:39:14 +00:00
|
|
|
" or immediate microcontroller name.\n"));
|
2000-07-03 22:25:33 +00:00
|
|
|
fprintf (stream,
|
|
|
|
_(" -mall-opcodes accept all AVR opcodes, even if not supported by MCU\n"
|
|
|
|
" -mno-skip-bug disable warnings for skipping two-word instructions\n"
|
|
|
|
" (default for avr4, avr5)\n"
|
|
|
|
" -mno-wrap reject rjmp/rcall instructions with 8K wrap-around\n"
|
|
|
|
" (default for avr3, avr5)\n"));
|
|
|
|
show_mcu_list (stream);
|
2000-03-27 08:39:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2005-10-12 10:56:46 +00:00
|
|
|
avr_set_arch (int dummy ATTRIBUTE_UNUSED)
|
2000-03-27 08:39:14 +00:00
|
|
|
{
|
2005-10-12 10:56:46 +00:00
|
|
|
char str[20];
|
2000-09-15 01:06:52 +00:00
|
|
|
|
2000-03-27 08:39:14 +00:00
|
|
|
input_line_pointer = extract_word (input_line_pointer, str, 20);
|
2000-07-03 22:25:33 +00:00
|
|
|
md_parse_option (OPTION_MMCU, str);
|
2000-03-27 08:39:14 +00:00
|
|
|
bfd_set_arch_mach (stdoutput, TARGET_ARCH, avr_mcu->mach);
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
2005-10-12 10:56:46 +00:00
|
|
|
md_parse_option (int c, char *arg)
|
2000-03-27 08:39:14 +00:00
|
|
|
{
|
2000-07-03 22:25:33 +00:00
|
|
|
switch (c)
|
2000-03-27 08:39:14 +00:00
|
|
|
{
|
2000-07-03 22:25:33 +00:00
|
|
|
case OPTION_MMCU:
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
char *s = alloca (strlen (arg) + 1);
|
2000-03-27 08:39:14 +00:00
|
|
|
|
2000-07-03 22:25:33 +00:00
|
|
|
{
|
|
|
|
char *t = s;
|
|
|
|
char *arg1 = arg;
|
|
|
|
|
|
|
|
do
|
2001-09-19 05:33:36 +00:00
|
|
|
*t = TOLOWER (*arg1++);
|
2000-07-03 22:25:33 +00:00
|
|
|
while (*t++);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; mcu_types[i].name; ++i)
|
|
|
|
if (strcmp (mcu_types[i].name, s) == 0)
|
|
|
|
break;
|
2000-03-27 08:39:14 +00:00
|
|
|
|
2000-07-03 22:25:33 +00:00
|
|
|
if (!mcu_types[i].name)
|
|
|
|
{
|
|
|
|
show_mcu_list (stderr);
|
|
|
|
as_fatal (_("unknown MCU: %s\n"), arg);
|
|
|
|
}
|
2000-06-27 01:45:30 +00:00
|
|
|
|
2000-07-03 22:25:33 +00:00
|
|
|
/* It is OK to redefine mcu type within the same avr[1-5] bfd machine
|
|
|
|
type - this for allows passing -mmcu=... via gcc ASM_SPEC as well
|
|
|
|
as .arch ... in the asm output at the same time. */
|
|
|
|
if (avr_mcu == &default_mcu || avr_mcu->mach == mcu_types[i].mach)
|
|
|
|
avr_mcu = &mcu_types[i];
|
|
|
|
else
|
|
|
|
as_fatal (_("redefinition of mcu type `%s' to `%s'"),
|
|
|
|
avr_mcu->name, mcu_types[i].name);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
case OPTION_ALL_OPCODES:
|
|
|
|
avr_opt.all_opcodes = 1;
|
|
|
|
return 1;
|
|
|
|
case OPTION_NO_SKIP_BUG:
|
|
|
|
avr_opt.no_skip_bug = 1;
|
|
|
|
return 1;
|
|
|
|
case OPTION_NO_WRAP:
|
|
|
|
avr_opt.no_wrap = 1;
|
2000-03-27 08:39:14 +00:00
|
|
|
return 1;
|
|
|
|
}
|
2000-09-15 01:06:52 +00:00
|
|
|
|
2000-03-27 08:39:14 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
symbolS *
|
2005-10-12 10:56:46 +00:00
|
|
|
md_undefined_symbol (char *name ATTRIBUTE_UNUSED)
|
2000-03-27 08:39:14 +00:00
|
|
|
{
|
2005-10-12 10:56:46 +00:00
|
|
|
return NULL;
|
2000-03-27 08:39:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
char *
|
2005-10-12 10:56:46 +00:00
|
|
|
md_atof (int type, char *litP, int *sizeP)
|
2000-03-27 08:39:14 +00:00
|
|
|
{
|
2007-10-17 16:45:56 +00:00
|
|
|
return ieee_md_atof (type, litP, sizeP, FALSE);
|
2000-03-27 08:39:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2005-10-12 10:56:46 +00:00
|
|
|
md_convert_frag (bfd *abfd ATTRIBUTE_UNUSED,
|
|
|
|
asection *sec ATTRIBUTE_UNUSED,
|
|
|
|
fragS *fragP ATTRIBUTE_UNUSED)
|
2000-03-27 08:39:14 +00:00
|
|
|
{
|
|
|
|
abort ();
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2005-10-12 10:56:46 +00:00
|
|
|
md_begin (void)
|
2000-03-27 08:39:14 +00:00
|
|
|
{
|
2000-05-01 11:14:05 +00:00
|
|
|
unsigned int i;
|
2000-03-27 08:39:14 +00:00
|
|
|
struct avr_opcodes_s *opcode;
|
2005-10-12 10:56:46 +00:00
|
|
|
|
2000-07-28 00:42:18 +00:00
|
|
|
avr_hash = hash_new ();
|
2000-03-27 08:39:14 +00:00
|
|
|
|
|
|
|
/* Insert unique names into hash table. This hash table then provides a
|
|
|
|
quick index to the first opcode with a particular name in the opcode
|
|
|
|
table. */
|
|
|
|
for (opcode = avr_opcodes; opcode->name; opcode++)
|
|
|
|
hash_insert (avr_hash, opcode->name, (char *) opcode);
|
|
|
|
|
|
|
|
avr_mod_hash = hash_new ();
|
|
|
|
|
2005-10-12 10:56:46 +00:00
|
|
|
for (i = 0; i < ARRAY_SIZE (exp_mod); ++i)
|
2006-01-11 17:39:50 +00:00
|
|
|
{
|
|
|
|
mod_index m;
|
|
|
|
|
|
|
|
m.index = i + 10;
|
|
|
|
hash_insert (avr_mod_hash, EXP_MOD_NAME (i), m.ptr);
|
|
|
|
}
|
2000-07-28 00:42:18 +00:00
|
|
|
|
2000-03-27 08:39:14 +00:00
|
|
|
bfd_set_arch_mach (stdoutput, TARGET_ARCH, avr_mcu->mach);
|
|
|
|
}
|
|
|
|
|
2000-05-01 11:14:05 +00:00
|
|
|
/* Resolve STR as a constant expression and return the result.
|
2000-07-28 00:42:18 +00:00
|
|
|
If result greater than MAX then error. */
|
2000-05-01 11:14:05 +00:00
|
|
|
|
|
|
|
static unsigned int
|
2005-10-12 10:56:46 +00:00
|
|
|
avr_get_constant (char *str, int max)
|
2000-05-01 11:14:05 +00:00
|
|
|
{
|
|
|
|
expressionS ex;
|
2005-10-12 10:56:46 +00:00
|
|
|
|
2000-05-01 11:14:05 +00:00
|
|
|
str = skip_space (str);
|
|
|
|
input_line_pointer = str;
|
2005-10-12 10:56:46 +00:00
|
|
|
expression (& ex);
|
2000-05-01 11:14:05 +00:00
|
|
|
|
|
|
|
if (ex.X_op != O_constant)
|
|
|
|
as_bad (_("constant value required"));
|
|
|
|
|
|
|
|
if (ex.X_add_number > max || ex.X_add_number < 0)
|
2007-10-03 14:35:06 +00:00
|
|
|
as_bad (_("number must be positive and less than %d"), max + 1);
|
2000-09-15 01:06:52 +00:00
|
|
|
|
2000-05-01 11:14:05 +00:00
|
|
|
return ex.X_add_number;
|
|
|
|
}
|
|
|
|
|
2005-10-12 10:56:46 +00:00
|
|
|
/* Parse for ldd/std offset. */
|
2000-05-01 11:14:05 +00:00
|
|
|
|
2005-10-12 10:56:46 +00:00
|
|
|
static void
|
|
|
|
avr_offset_expression (expressionS *exp)
|
2000-03-27 08:39:14 +00:00
|
|
|
{
|
2005-10-12 10:56:46 +00:00
|
|
|
char *str = input_line_pointer;
|
|
|
|
char *tmp;
|
|
|
|
char op[8];
|
2000-03-27 08:39:14 +00:00
|
|
|
|
2005-10-12 10:56:46 +00:00
|
|
|
tmp = str;
|
|
|
|
str = extract_word (str, op, sizeof (op));
|
|
|
|
|
|
|
|
input_line_pointer = tmp;
|
|
|
|
expression (exp);
|
|
|
|
|
|
|
|
/* Warn about expressions that fail to use lo8 (). */
|
|
|
|
if (exp->X_op == O_constant)
|
2000-03-27 08:39:14 +00:00
|
|
|
{
|
2005-10-12 10:56:46 +00:00
|
|
|
int x = exp->X_add_number;
|
2006-05-24 07:36:12 +00:00
|
|
|
|
2005-10-12 10:56:46 +00:00
|
|
|
if (x < -255 || x > 255)
|
|
|
|
as_warn (_("constant out of 8-bit range: %d"), x);
|
|
|
|
}
|
|
|
|
}
|
2000-03-27 08:39:14 +00:00
|
|
|
|
2005-10-12 10:56:46 +00:00
|
|
|
/* Parse ordinary expression. */
|
2000-03-27 08:39:14 +00:00
|
|
|
|
2005-10-12 10:56:46 +00:00
|
|
|
static char *
|
|
|
|
parse_exp (char *s, expressionS *op)
|
|
|
|
{
|
|
|
|
input_line_pointer = s;
|
|
|
|
expression (op);
|
|
|
|
if (op->X_op == O_absent)
|
|
|
|
as_bad (_("missing operand"));
|
|
|
|
return input_line_pointer;
|
|
|
|
}
|
2000-09-15 01:06:52 +00:00
|
|
|
|
2005-10-12 10:56:46 +00:00
|
|
|
/* Parse special expressions (needed for LDI command):
|
|
|
|
xx8 (address)
|
|
|
|
xx8 (-address)
|
|
|
|
pm_xx8 (address)
|
|
|
|
pm_xx8 (-address)
|
|
|
|
where xx is: hh, hi, lo. */
|
2000-03-27 08:39:14 +00:00
|
|
|
|
2005-10-12 10:56:46 +00:00
|
|
|
static bfd_reloc_code_real_type
|
|
|
|
avr_ldi_expression (expressionS *exp)
|
|
|
|
{
|
|
|
|
char *str = input_line_pointer;
|
|
|
|
char *tmp;
|
|
|
|
char op[8];
|
|
|
|
int mod;
|
2006-05-24 07:36:12 +00:00
|
|
|
int linker_stubs_should_be_generated = 0;
|
|
|
|
|
2005-10-12 10:56:46 +00:00
|
|
|
tmp = str;
|
2000-03-27 08:39:14 +00:00
|
|
|
|
2005-10-12 10:56:46 +00:00
|
|
|
str = extract_word (str, op, sizeof (op));
|
2000-03-27 08:39:14 +00:00
|
|
|
|
2005-10-12 10:56:46 +00:00
|
|
|
if (op[0])
|
|
|
|
{
|
2006-01-11 17:39:50 +00:00
|
|
|
mod_index m;
|
2006-05-24 07:36:12 +00:00
|
|
|
|
2006-01-11 17:39:50 +00:00
|
|
|
m.ptr = hash_find (avr_mod_hash, op);
|
|
|
|
mod = m.index;
|
2000-09-15 01:06:52 +00:00
|
|
|
|
2005-10-12 10:56:46 +00:00
|
|
|
if (mod)
|
|
|
|
{
|
|
|
|
int closes = 0;
|
2000-05-01 08:48:32 +00:00
|
|
|
|
2005-10-12 10:56:46 +00:00
|
|
|
mod -= 10;
|
|
|
|
str = skip_space (str);
|
2000-07-03 22:25:33 +00:00
|
|
|
|
2005-10-12 10:56:46 +00:00
|
|
|
if (*str == '(')
|
|
|
|
{
|
2006-05-24 07:36:12 +00:00
|
|
|
bfd_reloc_code_real_type reloc_to_return;
|
2005-10-12 10:56:46 +00:00
|
|
|
int neg_p = 0;
|
2000-07-03 22:25:33 +00:00
|
|
|
|
2005-10-12 10:56:46 +00:00
|
|
|
++str;
|
2000-07-03 22:25:33 +00:00
|
|
|
|
2005-10-12 10:56:46 +00:00
|
|
|
if (strncmp ("pm(", str, 3) == 0
|
2006-05-24 07:36:12 +00:00
|
|
|
|| strncmp ("gs(",str,3) == 0
|
|
|
|
|| strncmp ("-(gs(",str,5) == 0
|
2005-10-12 10:56:46 +00:00
|
|
|
|| strncmp ("-(pm(", str, 5) == 0)
|
|
|
|
{
|
|
|
|
if (HAVE_PM_P (mod))
|
|
|
|
{
|
|
|
|
++mod;
|
|
|
|
++closes;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
as_bad (_("illegal expression"));
|
2000-05-01 08:48:32 +00:00
|
|
|
|
2006-05-24 07:36:12 +00:00
|
|
|
if (str[0] == 'g' || str[2] == 'g')
|
|
|
|
linker_stubs_should_be_generated = 1;
|
|
|
|
|
2005-10-12 10:56:46 +00:00
|
|
|
if (*str == '-')
|
|
|
|
{
|
|
|
|
neg_p = 1;
|
|
|
|
++closes;
|
|
|
|
str += 5;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
str += 3;
|
|
|
|
}
|
2000-03-27 08:39:14 +00:00
|
|
|
|
2005-10-12 10:56:46 +00:00
|
|
|
if (*str == '-' && *(str + 1) == '(')
|
|
|
|
{
|
|
|
|
neg_p ^= 1;
|
|
|
|
++closes;
|
|
|
|
str += 2;
|
|
|
|
}
|
2004-12-22 14:25:42 +00:00
|
|
|
|
2005-10-12 10:56:46 +00:00
|
|
|
input_line_pointer = str;
|
|
|
|
expression (exp);
|
2004-12-22 14:25:42 +00:00
|
|
|
|
2005-10-12 10:56:46 +00:00
|
|
|
do
|
|
|
|
{
|
|
|
|
if (*input_line_pointer != ')')
|
|
|
|
{
|
|
|
|
as_bad (_("`)' required"));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
input_line_pointer++;
|
|
|
|
}
|
|
|
|
while (closes--);
|
|
|
|
|
2006-05-24 07:36:12 +00:00
|
|
|
reloc_to_return =
|
|
|
|
neg_p ? EXP_MOD_NEG_RELOC (mod) : EXP_MOD_RELOC (mod);
|
|
|
|
if (linker_stubs_should_be_generated)
|
|
|
|
{
|
|
|
|
switch (reloc_to_return)
|
|
|
|
{
|
|
|
|
case BFD_RELOC_AVR_LO8_LDI_PM:
|
|
|
|
reloc_to_return = BFD_RELOC_AVR_LO8_LDI_GS;
|
|
|
|
break;
|
|
|
|
case BFD_RELOC_AVR_HI8_LDI_PM:
|
|
|
|
reloc_to_return = BFD_RELOC_AVR_HI8_LDI_GS;
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
2008-05-30 14:20:27 +00:00
|
|
|
/* PR 5523: Do not generate a warning here,
|
|
|
|
legitimate code can trigger this case. */
|
|
|
|
break;
|
2006-05-24 07:36:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return reloc_to_return;
|
2005-10-12 10:56:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2004-12-22 14:25:42 +00:00
|
|
|
|
|
|
|
input_line_pointer = tmp;
|
|
|
|
expression (exp);
|
|
|
|
|
|
|
|
/* Warn about expressions that fail to use lo8 (). */
|
|
|
|
if (exp->X_op == O_constant)
|
|
|
|
{
|
|
|
|
int x = exp->X_add_number;
|
2005-10-12 10:56:46 +00:00
|
|
|
|
2004-12-22 14:25:42 +00:00
|
|
|
if (x < -255 || x > 255)
|
|
|
|
as_warn (_("constant out of 8-bit range: %d"), x);
|
|
|
|
}
|
2005-10-12 10:56:46 +00:00
|
|
|
|
|
|
|
return BFD_RELOC_AVR_LDI;
|
2004-12-22 14:25:42 +00:00
|
|
|
}
|
|
|
|
|
2000-05-01 11:14:05 +00:00
|
|
|
/* Parse one instruction operand.
|
2000-07-28 00:42:18 +00:00
|
|
|
Return operand bitmask. Also fixups can be generated. */
|
|
|
|
|
2000-03-27 08:39:14 +00:00
|
|
|
static unsigned int
|
2005-10-12 10:56:46 +00:00
|
|
|
avr_operand (struct avr_opcodes_s *opcode,
|
|
|
|
int where,
|
|
|
|
char *op,
|
|
|
|
char **line)
|
2000-03-27 08:39:14 +00:00
|
|
|
{
|
|
|
|
expressionS op_expr;
|
2000-05-01 11:14:05 +00:00
|
|
|
unsigned int op_mask = 0;
|
|
|
|
char *str = skip_space (*line);
|
2000-03-27 08:39:14 +00:00
|
|
|
|
|
|
|
switch (*op)
|
|
|
|
{
|
|
|
|
/* Any register operand. */
|
|
|
|
case 'w':
|
|
|
|
case 'd':
|
|
|
|
case 'r':
|
2000-05-01 08:48:32 +00:00
|
|
|
case 'a':
|
|
|
|
case 'v':
|
2000-07-28 00:42:18 +00:00
|
|
|
if (*str == 'r' || *str == 'R')
|
|
|
|
{
|
|
|
|
char r_name[20];
|
2000-09-15 01:06:52 +00:00
|
|
|
|
2000-07-28 00:42:18 +00:00
|
|
|
str = extract_word (str, r_name, sizeof (r_name));
|
2000-08-06 14:03:58 +00:00
|
|
|
op_mask = 0xff;
|
2001-09-19 05:33:36 +00:00
|
|
|
if (ISDIGIT (r_name[1]))
|
2000-07-28 00:42:18 +00:00
|
|
|
{
|
|
|
|
if (r_name[2] == '\0')
|
|
|
|
op_mask = r_name[1] - '0';
|
|
|
|
else if (r_name[1] != '0'
|
2001-09-19 05:33:36 +00:00
|
|
|
&& ISDIGIT (r_name[2])
|
2000-07-28 00:42:18 +00:00
|
|
|
&& r_name[3] == '\0')
|
|
|
|
op_mask = (r_name[1] - '0') * 10 + r_name[2] - '0';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
op_mask = avr_get_constant (str, 31);
|
|
|
|
str = input_line_pointer;
|
|
|
|
}
|
2000-09-15 01:06:52 +00:00
|
|
|
|
2000-07-28 00:42:18 +00:00
|
|
|
if (op_mask <= 31)
|
|
|
|
{
|
|
|
|
switch (*op)
|
|
|
|
{
|
|
|
|
case 'a':
|
|
|
|
if (op_mask < 16 || op_mask > 23)
|
|
|
|
as_bad (_("register r16-r23 required"));
|
|
|
|
op_mask -= 16;
|
|
|
|
break;
|
2000-09-15 01:06:52 +00:00
|
|
|
|
2000-07-28 00:42:18 +00:00
|
|
|
case 'd':
|
|
|
|
if (op_mask < 16)
|
|
|
|
as_bad (_("register number above 15 required"));
|
|
|
|
op_mask -= 16;
|
|
|
|
break;
|
2000-09-15 01:06:52 +00:00
|
|
|
|
2000-07-28 00:42:18 +00:00
|
|
|
case 'v':
|
|
|
|
if (op_mask & 1)
|
|
|
|
as_bad (_("even register number required"));
|
|
|
|
op_mask >>= 1;
|
|
|
|
break;
|
2000-09-15 01:06:52 +00:00
|
|
|
|
2000-07-28 00:42:18 +00:00
|
|
|
case 'w':
|
2000-08-06 14:03:58 +00:00
|
|
|
if ((op_mask & 1) || op_mask < 24)
|
2000-07-28 00:42:18 +00:00
|
|
|
as_bad (_("register r24, r26, r28 or r30 required"));
|
2000-08-06 14:03:58 +00:00
|
|
|
op_mask = (op_mask - 24) >> 1;
|
2000-07-28 00:42:18 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
as_bad (_("register name or number from 0 to 31 required"));
|
2000-03-27 08:39:14 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 'e':
|
|
|
|
{
|
|
|
|
char c;
|
2000-09-15 01:06:52 +00:00
|
|
|
|
2000-03-27 08:39:14 +00:00
|
|
|
if (*str == '-')
|
|
|
|
{
|
2000-07-28 00:42:18 +00:00
|
|
|
str = skip_space (str + 1);
|
2000-03-27 08:39:14 +00:00
|
|
|
op_mask = 0x1002;
|
|
|
|
}
|
2001-09-19 05:33:36 +00:00
|
|
|
c = TOLOWER (*str);
|
2000-03-27 08:39:14 +00:00
|
|
|
if (c == 'x')
|
|
|
|
op_mask |= 0x100c;
|
|
|
|
else if (c == 'y')
|
|
|
|
op_mask |= 0x8;
|
|
|
|
else if (c != 'z')
|
2000-07-03 22:25:33 +00:00
|
|
|
as_bad (_("pointer register (X, Y or Z) required"));
|
2000-03-27 08:39:14 +00:00
|
|
|
|
2000-07-28 00:42:18 +00:00
|
|
|
str = skip_space (str + 1);
|
2000-03-27 08:39:14 +00:00
|
|
|
if (*str == '+')
|
|
|
|
{
|
|
|
|
++str;
|
|
|
|
if (op_mask & 2)
|
2000-07-03 22:25:33 +00:00
|
|
|
as_bad (_("cannot both predecrement and postincrement"));
|
2000-03-27 08:39:14 +00:00
|
|
|
op_mask |= 0x1001;
|
|
|
|
}
|
2000-06-07 18:56:15 +00:00
|
|
|
|
2000-06-07 17:42:44 +00:00
|
|
|
/* avr1 can do "ld r,Z" and "st Z,r" but no other pointer
|
2000-06-07 18:56:15 +00:00
|
|
|
registers, no predecrement, no postincrement. */
|
2000-07-03 22:25:33 +00:00
|
|
|
if (!avr_opt.all_opcodes && (op_mask & 0x100F)
|
|
|
|
&& !(avr_mcu->isa & AVR_ISA_SRAM))
|
|
|
|
as_bad (_("addressing mode not supported"));
|
2000-03-27 08:39:14 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2000-05-01 08:48:32 +00:00
|
|
|
case 'z':
|
2000-07-28 00:42:18 +00:00
|
|
|
if (*str == '-')
|
|
|
|
as_bad (_("can't predecrement"));
|
2000-09-15 01:06:52 +00:00
|
|
|
|
2000-07-28 00:42:18 +00:00
|
|
|
if (! (*str == 'z' || *str == 'Z'))
|
|
|
|
as_bad (_("pointer register Z required"));
|
2000-09-15 01:06:52 +00:00
|
|
|
|
2000-07-28 00:42:18 +00:00
|
|
|
str = skip_space (str + 1);
|
|
|
|
|
|
|
|
if (*str == '+')
|
|
|
|
{
|
|
|
|
++str;
|
|
|
|
op_mask |= 1;
|
|
|
|
}
|
2008-02-14 13:04:29 +00:00
|
|
|
|
|
|
|
/* attiny26 can do "lpm" and "lpm r,Z" but not "lpm r,Z+". */
|
|
|
|
if (!avr_opt.all_opcodes
|
|
|
|
&& (op_mask & 0x0001)
|
|
|
|
&& !(avr_mcu->isa & AVR_ISA_MOVW))
|
|
|
|
as_bad (_("postincrement not supported"));
|
2000-05-01 08:48:32 +00:00
|
|
|
break;
|
|
|
|
|
2000-03-27 08:39:14 +00:00
|
|
|
case 'b':
|
|
|
|
{
|
2001-09-19 05:33:36 +00:00
|
|
|
char c = TOLOWER (*str++);
|
2000-09-15 01:06:52 +00:00
|
|
|
|
2000-03-27 08:39:14 +00:00
|
|
|
if (c == 'y')
|
|
|
|
op_mask |= 0x8;
|
|
|
|
else if (c != 'z')
|
2000-07-03 22:25:33 +00:00
|
|
|
as_bad (_("pointer register (Y or Z) required"));
|
2000-03-27 08:39:14 +00:00
|
|
|
str = skip_space (str);
|
|
|
|
if (*str++ == '+')
|
|
|
|
{
|
2004-12-22 14:25:42 +00:00
|
|
|
input_line_pointer = str;
|
|
|
|
avr_offset_expression (& op_expr);
|
2000-03-27 08:39:14 +00:00
|
|
|
str = input_line_pointer;
|
2004-12-22 14:25:42 +00:00
|
|
|
fix_new_exp (frag_now, where, 3,
|
|
|
|
&op_expr, FALSE, BFD_RELOC_AVR_6);
|
2000-03-27 08:39:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'h':
|
2000-07-28 00:42:18 +00:00
|
|
|
str = parse_exp (str, &op_expr);
|
|
|
|
fix_new_exp (frag_now, where, opcode->insn_size * 2,
|
2002-11-30 08:39:46 +00:00
|
|
|
&op_expr, FALSE, BFD_RELOC_AVR_CALL);
|
2000-03-27 08:39:14 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 'L':
|
2000-07-28 00:42:18 +00:00
|
|
|
str = parse_exp (str, &op_expr);
|
|
|
|
fix_new_exp (frag_now, where, opcode->insn_size * 2,
|
2002-11-30 08:39:46 +00:00
|
|
|
&op_expr, TRUE, BFD_RELOC_AVR_13_PCREL);
|
2000-03-27 08:39:14 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 'l':
|
2000-07-28 00:42:18 +00:00
|
|
|
str = parse_exp (str, &op_expr);
|
|
|
|
fix_new_exp (frag_now, where, opcode->insn_size * 2,
|
2002-11-30 08:39:46 +00:00
|
|
|
&op_expr, TRUE, BFD_RELOC_AVR_7_PCREL);
|
2000-03-27 08:39:14 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 'i':
|
2000-07-28 00:42:18 +00:00
|
|
|
str = parse_exp (str, &op_expr);
|
|
|
|
fix_new_exp (frag_now, where + 2, opcode->insn_size * 2,
|
2002-11-30 08:39:46 +00:00
|
|
|
&op_expr, FALSE, BFD_RELOC_16);
|
2000-03-27 08:39:14 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 'M':
|
|
|
|
{
|
|
|
|
bfd_reloc_code_real_type r_type;
|
2000-09-15 01:06:52 +00:00
|
|
|
|
2000-07-28 00:42:18 +00:00
|
|
|
input_line_pointer = str;
|
|
|
|
r_type = avr_ldi_expression (&op_expr);
|
|
|
|
str = input_line_pointer;
|
2000-03-27 08:39:14 +00:00
|
|
|
fix_new_exp (frag_now, where, 3,
|
2002-11-30 08:39:46 +00:00
|
|
|
&op_expr, FALSE, r_type);
|
2000-03-27 08:39:14 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'n':
|
|
|
|
{
|
|
|
|
unsigned int x;
|
2000-09-15 01:06:52 +00:00
|
|
|
|
2000-03-27 08:39:14 +00:00
|
|
|
x = ~avr_get_constant (str, 255);
|
|
|
|
str = input_line_pointer;
|
|
|
|
op_mask |= (x & 0xf) | ((x << 4) & 0xf00);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'K':
|
2004-12-22 14:25:42 +00:00
|
|
|
input_line_pointer = str;
|
|
|
|
avr_offset_expression (& op_expr);
|
|
|
|
str = input_line_pointer;
|
|
|
|
fix_new_exp (frag_now, where, 3,
|
|
|
|
& op_expr, FALSE, BFD_RELOC_AVR_6_ADIW);
|
2000-03-27 08:39:14 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 'S':
|
|
|
|
case 's':
|
|
|
|
{
|
|
|
|
unsigned int x;
|
2000-09-15 01:06:52 +00:00
|
|
|
|
2000-03-27 08:39:14 +00:00
|
|
|
x = avr_get_constant (str, 7);
|
|
|
|
str = input_line_pointer;
|
|
|
|
if (*op == 'S')
|
|
|
|
x <<= 4;
|
|
|
|
op_mask |= x;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'P':
|
|
|
|
{
|
|
|
|
unsigned int x;
|
2000-09-15 01:06:52 +00:00
|
|
|
|
2000-03-27 08:39:14 +00:00
|
|
|
x = avr_get_constant (str, 63);
|
|
|
|
str = input_line_pointer;
|
|
|
|
op_mask |= (x & 0xf) | ((x & 0x30) << 5);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'p':
|
|
|
|
{
|
|
|
|
unsigned int x;
|
2000-09-15 01:06:52 +00:00
|
|
|
|
2000-03-27 08:39:14 +00:00
|
|
|
x = avr_get_constant (str, 31);
|
|
|
|
str = input_line_pointer;
|
|
|
|
op_mask |= x << 3;
|
|
|
|
}
|
|
|
|
break;
|
2000-09-15 01:06:52 +00:00
|
|
|
|
2000-06-07 17:42:44 +00:00
|
|
|
case '?':
|
|
|
|
break;
|
2000-09-15 01:06:52 +00:00
|
|
|
|
2000-03-27 08:39:14 +00:00
|
|
|
default:
|
2000-07-03 22:25:33 +00:00
|
|
|
as_bad (_("unknown constraint `%c'"), *op);
|
2000-03-27 08:39:14 +00:00
|
|
|
}
|
2000-09-15 01:06:52 +00:00
|
|
|
|
2000-03-27 08:39:14 +00:00
|
|
|
*line = str;
|
|
|
|
return op_mask;
|
|
|
|
}
|
|
|
|
|
2005-10-12 10:56:46 +00:00
|
|
|
/* Parse instruction operands.
|
|
|
|
Return binary opcode. */
|
|
|
|
|
|
|
|
static unsigned int
|
|
|
|
avr_operands (struct avr_opcodes_s *opcode, char **line)
|
|
|
|
{
|
|
|
|
char *op = opcode->constraints;
|
|
|
|
unsigned int bin = opcode->bin_opcode;
|
|
|
|
char *frag = frag_more (opcode->insn_size * 2);
|
|
|
|
char *str = *line;
|
|
|
|
int where = frag - frag_now->fr_literal;
|
|
|
|
static unsigned int prev = 0; /* Previous opcode. */
|
|
|
|
|
|
|
|
/* Opcode have operands. */
|
|
|
|
if (*op)
|
|
|
|
{
|
|
|
|
unsigned int reg1 = 0;
|
|
|
|
unsigned int reg2 = 0;
|
|
|
|
int reg1_present = 0;
|
|
|
|
int reg2_present = 0;
|
|
|
|
|
|
|
|
/* Parse first operand. */
|
|
|
|
if (REGISTER_P (*op))
|
|
|
|
reg1_present = 1;
|
|
|
|
reg1 = avr_operand (opcode, where, op, &str);
|
|
|
|
++op;
|
|
|
|
|
|
|
|
/* Parse second operand. */
|
|
|
|
if (*op)
|
|
|
|
{
|
|
|
|
if (*op == ',')
|
|
|
|
++op;
|
|
|
|
|
|
|
|
if (*op == '=')
|
|
|
|
{
|
|
|
|
reg2 = reg1;
|
|
|
|
reg2_present = 1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (REGISTER_P (*op))
|
|
|
|
reg2_present = 1;
|
|
|
|
|
|
|
|
str = skip_space (str);
|
|
|
|
if (*str++ != ',')
|
|
|
|
as_bad (_("`,' required"));
|
|
|
|
str = skip_space (str);
|
|
|
|
|
|
|
|
reg2 = avr_operand (opcode, where, op, &str);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (reg1_present && reg2_present)
|
|
|
|
reg2 = (reg2 & 0xf) | ((reg2 << 5) & 0x200);
|
|
|
|
else if (reg2_present)
|
|
|
|
reg2 <<= 4;
|
|
|
|
}
|
|
|
|
if (reg1_present)
|
|
|
|
reg1 <<= 4;
|
|
|
|
bin |= reg1 | reg2;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Detect undefined combinations (like ld r31,Z+). */
|
|
|
|
if (!avr_opt.all_opcodes && AVR_UNDEF_P (bin))
|
|
|
|
as_warn (_("undefined combination of operands"));
|
|
|
|
|
|
|
|
if (opcode->insn_size == 2)
|
|
|
|
{
|
|
|
|
/* Warn if the previous opcode was cpse/sbic/sbis/sbrc/sbrs
|
|
|
|
(AVR core bug, fixed in the newer devices). */
|
|
|
|
if (!(avr_opt.no_skip_bug ||
|
|
|
|
(avr_mcu->isa & (AVR_ISA_MUL | AVR_ISA_MOVW)))
|
|
|
|
&& AVR_SKIP_P (prev))
|
|
|
|
as_warn (_("skipping two-word instruction"));
|
|
|
|
|
|
|
|
bfd_putl32 ((bfd_vma) bin, frag);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
bfd_putl16 ((bfd_vma) bin, frag);
|
|
|
|
|
|
|
|
prev = bin;
|
|
|
|
*line = str;
|
|
|
|
return bin;
|
|
|
|
}
|
|
|
|
|
2000-03-27 08:39:14 +00:00
|
|
|
/* GAS will call this function for each section at the end of the assembly,
|
|
|
|
to permit the CPU backend to adjust the alignment of a section. */
|
2000-07-28 00:42:18 +00:00
|
|
|
|
2000-03-27 08:39:14 +00:00
|
|
|
valueT
|
2005-10-12 10:56:46 +00:00
|
|
|
md_section_align (asection *seg, valueT addr)
|
2000-03-27 08:39:14 +00:00
|
|
|
{
|
|
|
|
int align = bfd_get_section_alignment (stdoutput, seg);
|
|
|
|
return ((addr + (1 << align) - 1) & (-1 << align));
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If you define this macro, it should return the offset between the
|
|
|
|
address of a PC relative fixup and the position from which the PC
|
|
|
|
relative adjustment should be made. On many processors, the base
|
|
|
|
of a PC relative instruction is the next instruction, so this
|
|
|
|
macro would return the length of an instruction. */
|
2000-07-28 00:42:18 +00:00
|
|
|
|
2000-03-27 08:39:14 +00:00
|
|
|
long
|
2005-10-12 10:56:46 +00:00
|
|
|
md_pcrel_from_section (fixS *fixp, segT sec)
|
2000-03-27 08:39:14 +00:00
|
|
|
{
|
2000-07-28 00:42:18 +00:00
|
|
|
if (fixp->fx_addsy != (symbolS *) NULL
|
2000-03-27 08:39:14 +00:00
|
|
|
&& (!S_IS_DEFINED (fixp->fx_addsy)
|
|
|
|
|| (S_GET_SEGMENT (fixp->fx_addsy) != sec)))
|
|
|
|
return 0;
|
2000-09-15 01:06:52 +00:00
|
|
|
|
2000-03-27 08:39:14 +00:00
|
|
|
return fixp->fx_frag->fr_address + fixp->fx_where;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* GAS will call this for each fixup. It should store the correct
|
2000-07-28 00:42:18 +00:00
|
|
|
value in the object file. */
|
|
|
|
|
2001-11-15 21:29:00 +00:00
|
|
|
void
|
2005-10-12 10:56:46 +00:00
|
|
|
md_apply_fix (fixS *fixP, valueT * valP, segT seg)
|
2000-03-27 08:39:14 +00:00
|
|
|
{
|
|
|
|
unsigned char *where;
|
|
|
|
unsigned long insn;
|
2002-09-05 00:01:18 +00:00
|
|
|
long value = *valP;
|
2000-03-27 08:39:14 +00:00
|
|
|
|
2001-11-15 21:29:00 +00:00
|
|
|
if (fixP->fx_addsy == (symbolS *) NULL)
|
|
|
|
fixP->fx_done = 1;
|
|
|
|
|
2002-09-27 04:38:47 +00:00
|
|
|
else if (fixP->fx_pcrel)
|
|
|
|
{
|
|
|
|
segT s = S_GET_SEGMENT (fixP->fx_addsy);
|
|
|
|
|
|
|
|
if (s == seg || s == absolute_section)
|
|
|
|
{
|
|
|
|
value += S_GET_VALUE (fixP->fx_addsy);
|
|
|
|
fixP->fx_done = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2002-09-05 00:01:18 +00:00
|
|
|
/* We don't actually support subtracting a symbol. */
|
|
|
|
if (fixP->fx_subsy != (symbolS *) NULL)
|
|
|
|
as_bad_where (fixP->fx_file, fixP->fx_line, _("expression too complex"));
|
2000-09-15 01:06:52 +00:00
|
|
|
|
2001-11-15 21:29:00 +00:00
|
|
|
switch (fixP->fx_r_type)
|
2000-03-27 08:39:14 +00:00
|
|
|
{
|
|
|
|
default:
|
2001-11-15 21:29:00 +00:00
|
|
|
fixP->fx_no_overflow = 1;
|
2000-03-27 08:39:14 +00:00
|
|
|
break;
|
|
|
|
case BFD_RELOC_AVR_7_PCREL:
|
|
|
|
case BFD_RELOC_AVR_13_PCREL:
|
|
|
|
case BFD_RELOC_32:
|
|
|
|
case BFD_RELOC_16:
|
|
|
|
case BFD_RELOC_AVR_CALL:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2001-11-15 21:29:00 +00:00
|
|
|
if (fixP->fx_done)
|
2000-03-27 08:39:14 +00:00
|
|
|
{
|
|
|
|
/* Fetch the instruction, insert the fully resolved operand
|
|
|
|
value, and stuff the instruction back again. */
|
2005-02-23 12:28:06 +00:00
|
|
|
where = (unsigned char *) fixP->fx_frag->fr_literal + fixP->fx_where;
|
2000-03-27 08:39:14 +00:00
|
|
|
insn = bfd_getl16 (where);
|
|
|
|
|
2001-11-15 21:29:00 +00:00
|
|
|
switch (fixP->fx_r_type)
|
2000-03-27 08:39:14 +00:00
|
|
|
{
|
|
|
|
case BFD_RELOC_AVR_7_PCREL:
|
|
|
|
if (value & 1)
|
2001-11-15 21:29:00 +00:00
|
|
|
as_bad_where (fixP->fx_file, fixP->fx_line,
|
2000-03-27 08:39:14 +00:00
|
|
|
_("odd address operand: %ld"), value);
|
2000-09-15 01:06:52 +00:00
|
|
|
|
2000-03-27 08:39:14 +00:00
|
|
|
/* Instruction addresses are always right-shifted by 1. */
|
|
|
|
value >>= 1;
|
|
|
|
--value; /* Correct PC. */
|
2000-09-15 01:06:52 +00:00
|
|
|
|
2000-03-27 08:39:14 +00:00
|
|
|
if (value < -64 || value > 63)
|
2001-11-15 21:29:00 +00:00
|
|
|
as_bad_where (fixP->fx_file, fixP->fx_line,
|
2000-03-27 08:39:14 +00:00
|
|
|
_("operand out of range: %ld"), value);
|
|
|
|
value = (value << 3) & 0x3f8;
|
|
|
|
bfd_putl16 ((bfd_vma) (value | insn), where);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case BFD_RELOC_AVR_13_PCREL:
|
|
|
|
if (value & 1)
|
2001-11-15 21:29:00 +00:00
|
|
|
as_bad_where (fixP->fx_file, fixP->fx_line,
|
2000-03-27 08:39:14 +00:00
|
|
|
_("odd address operand: %ld"), value);
|
2000-09-15 01:06:52 +00:00
|
|
|
|
2000-03-27 08:39:14 +00:00
|
|
|
/* Instruction addresses are always right-shifted by 1. */
|
|
|
|
value >>= 1;
|
|
|
|
--value; /* Correct PC. */
|
|
|
|
|
|
|
|
if (value < -2048 || value > 2047)
|
|
|
|
{
|
2000-06-27 01:45:30 +00:00
|
|
|
/* No wrap for devices with >8K of program memory. */
|
2000-07-03 22:25:33 +00:00
|
|
|
if ((avr_mcu->isa & AVR_ISA_MEGA) || avr_opt.no_wrap)
|
2001-11-15 21:29:00 +00:00
|
|
|
as_bad_where (fixP->fx_file, fixP->fx_line,
|
2000-03-27 08:39:14 +00:00
|
|
|
_("operand out of range: %ld"), value);
|
|
|
|
}
|
|
|
|
|
|
|
|
value &= 0xfff;
|
|
|
|
bfd_putl16 ((bfd_vma) (value | insn), where);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case BFD_RELOC_32:
|
|
|
|
bfd_putl16 ((bfd_vma) value, where);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case BFD_RELOC_16:
|
|
|
|
bfd_putl16 ((bfd_vma) value, where);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case BFD_RELOC_AVR_16_PM:
|
2000-07-28 00:42:18 +00:00
|
|
|
bfd_putl16 ((bfd_vma) (value >> 1), where);
|
2000-03-27 08:39:14 +00:00
|
|
|
break;
|
|
|
|
|
2004-12-22 14:25:42 +00:00
|
|
|
case BFD_RELOC_AVR_LDI:
|
|
|
|
if (value > 255)
|
|
|
|
as_bad_where (fixP->fx_file, fixP->fx_line,
|
|
|
|
_("operand out of range: %ld"), value);
|
|
|
|
bfd_putl16 ((bfd_vma) insn | LDI_IMMEDIATE (value), where);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case BFD_RELOC_AVR_6:
|
|
|
|
if ((value > 63) || (value < 0))
|
|
|
|
as_bad_where (fixP->fx_file, fixP->fx_line,
|
|
|
|
_("operand out of range: %ld"), value);
|
|
|
|
bfd_putl16 ((bfd_vma) insn | ((value & 7) | ((value & (3 << 3)) << 7) | ((value & (1 << 5)) << 8)), where);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case BFD_RELOC_AVR_6_ADIW:
|
|
|
|
if ((value > 63) || (value < 0))
|
|
|
|
as_bad_where (fixP->fx_file, fixP->fx_line,
|
|
|
|
_("operand out of range: %ld"), value);
|
|
|
|
bfd_putl16 ((bfd_vma) insn | (value & 0xf) | ((value & 0x30) << 2), where);
|
|
|
|
break;
|
|
|
|
|
2000-03-27 08:39:14 +00:00
|
|
|
case BFD_RELOC_AVR_LO8_LDI:
|
|
|
|
bfd_putl16 ((bfd_vma) insn | LDI_IMMEDIATE (value), where);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case BFD_RELOC_AVR_HI8_LDI:
|
|
|
|
bfd_putl16 ((bfd_vma) insn | LDI_IMMEDIATE (value >> 8), where);
|
|
|
|
break;
|
|
|
|
|
2006-03-03 15:25:31 +00:00
|
|
|
case BFD_RELOC_AVR_MS8_LDI:
|
2000-03-27 08:39:14 +00:00
|
|
|
bfd_putl16 ((bfd_vma) insn | LDI_IMMEDIATE (value >> 24), where);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case BFD_RELOC_AVR_HH8_LDI:
|
|
|
|
bfd_putl16 ((bfd_vma) insn | LDI_IMMEDIATE (value >> 16), where);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case BFD_RELOC_AVR_LO8_LDI_NEG:
|
|
|
|
bfd_putl16 ((bfd_vma) insn | LDI_IMMEDIATE (-value), where);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case BFD_RELOC_AVR_HI8_LDI_NEG:
|
|
|
|
bfd_putl16 ((bfd_vma) insn | LDI_IMMEDIATE (-value >> 8), where);
|
|
|
|
break;
|
|
|
|
|
2006-03-03 15:25:31 +00:00
|
|
|
case BFD_RELOC_AVR_MS8_LDI_NEG:
|
2000-03-27 08:39:14 +00:00
|
|
|
bfd_putl16 ((bfd_vma) insn | LDI_IMMEDIATE (-value >> 24), where);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case BFD_RELOC_AVR_HH8_LDI_NEG:
|
|
|
|
bfd_putl16 ((bfd_vma) insn | LDI_IMMEDIATE (-value >> 16), where);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case BFD_RELOC_AVR_LO8_LDI_PM:
|
|
|
|
bfd_putl16 ((bfd_vma) insn | LDI_IMMEDIATE (value >> 1), where);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case BFD_RELOC_AVR_HI8_LDI_PM:
|
|
|
|
bfd_putl16 ((bfd_vma) insn | LDI_IMMEDIATE (value >> 9), where);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case BFD_RELOC_AVR_HH8_LDI_PM:
|
|
|
|
bfd_putl16 ((bfd_vma) insn | LDI_IMMEDIATE (value >> 17), where);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case BFD_RELOC_AVR_LO8_LDI_PM_NEG:
|
|
|
|
bfd_putl16 ((bfd_vma) insn | LDI_IMMEDIATE (-value >> 1), where);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case BFD_RELOC_AVR_HI8_LDI_PM_NEG:
|
|
|
|
bfd_putl16 ((bfd_vma) insn | LDI_IMMEDIATE (-value >> 9), where);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case BFD_RELOC_AVR_HH8_LDI_PM_NEG:
|
|
|
|
bfd_putl16 ((bfd_vma) insn | LDI_IMMEDIATE (-value >> 17), where);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case BFD_RELOC_AVR_CALL:
|
|
|
|
{
|
|
|
|
unsigned long x;
|
2000-09-15 01:06:52 +00:00
|
|
|
|
2000-03-27 08:39:14 +00:00
|
|
|
x = bfd_getl16 (where);
|
|
|
|
if (value & 1)
|
2001-11-15 21:29:00 +00:00
|
|
|
as_bad_where (fixP->fx_file, fixP->fx_line,
|
2000-03-27 08:39:14 +00:00
|
|
|
_("odd address operand: %ld"), value);
|
|
|
|
value >>= 1;
|
|
|
|
x |= ((value & 0x10000) | ((value << 3) & 0x1f00000)) >> 16;
|
|
|
|
bfd_putl16 ((bfd_vma) x, where);
|
2000-07-28 00:42:18 +00:00
|
|
|
bfd_putl16 ((bfd_vma) (value & 0xffff), where + 2);
|
2000-03-27 08:39:14 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
2000-07-28 00:42:18 +00:00
|
|
|
as_fatal (_("line %d: unknown relocation type: 0x%x"),
|
2001-11-15 21:29:00 +00:00
|
|
|
fixP->fx_line, fixP->fx_r_type);
|
2000-03-27 08:39:14 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2001-11-15 21:29:00 +00:00
|
|
|
switch (fixP->fx_r_type)
|
2000-03-27 08:39:14 +00:00
|
|
|
{
|
|
|
|
case -BFD_RELOC_AVR_HI8_LDI_NEG:
|
|
|
|
case -BFD_RELOC_AVR_HI8_LDI:
|
|
|
|
case -BFD_RELOC_AVR_LO8_LDI_NEG:
|
|
|
|
case -BFD_RELOC_AVR_LO8_LDI:
|
2001-11-15 21:29:00 +00:00
|
|
|
as_bad_where (fixP->fx_file, fixP->fx_line,
|
2000-03-27 08:39:14 +00:00
|
|
|
_("only constant expression allowed"));
|
2001-11-15 21:29:00 +00:00
|
|
|
fixP->fx_done = 1;
|
2000-03-27 08:39:14 +00:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
* README-vms: Delete.
* config-gas.com: Delete.
* makefile.vms: Delete.
* vmsconf.sh: Delete.
* config/atof-tahoe.c: Delete.
* config/m88k-opcode.h: Delete.
* config/obj-bout.c: Delete.
* config/obj-bout.h: Delete.
* config/obj-hp300.c: Delete.
* config/obj-hp300.h: Delete.
* config/tc-a29k.c: Delete.
* config/tc-a29k.h: Delete.
* config/tc-h8500.c: Delete.
* config/tc-h8500.h: Delete.
* config/tc-m88k.c: Delete.
* config/tc-m88k.h: Delete.
* config/tc-tahoe.c: Delete.
* config/tc-tahoe.h: Delete.
* config/tc-tic80.c: Delete.
* config/tc-tic80.h: Delete.
* config/tc-w65.c: Delete.
* config/tc-w65.h: Delete.
* config/te-aux.h: Delete.
* config/te-delt88.h: Delete.
* config/te-delta.h: Delete.
* config/te-dpx2.h: Delete.
* config/te-hp300.h: Delete.
* config/te-ic960.h: Delete.
* config/vms-a-conf.h: Delete.
* doc/c-a29k.texi: Delete.
* doc/c-h8500.texi: Delete.
* doc/c-m88k.texi: Delete.
* README: Remove obsolete examples, and list of supported targets.
* Makefile.am: Remove a29k, h8500, m88k, tahoe, tic80, w65,
bout and hp300 support.
(DEP_FLAGS): Don't define BFD_ASSEMBLER.
* configure.in: Remove --enable-bfd-assembler, need_bfd,
primary_bfd_gas.
* configure.tgt: Remove a29k, h8300-coff, h8500-*, i960 non-elf,
m68k non bfd, m88k, or32-coff, tic80-*, vax non-bfd, w65k-*, *-nindy.
* as.c: Remove all non-BFD_ASSEMBLER code, support for above targets.
* as.h: Likewise.
* dw2gencfi.c: Likewise.
* dwarf2dbg.c: Likewise.
* ehopt.c: Likewise.
* input-file.c: Likewise.
* listing.c: Likewise.
* literal.c: Likewise.
* messages.c: Likewise.
* obj.h: Likewise.
* output-file.c: Likewise.
* read.c: Likewise.
* stabs.c: Likewise.
* struc-symbol.h: Likewise.
* subsegs.c: Likewise.
* subsegs.h: Likewise.
* symbols.c: Likewise.
* symbols.h: Likewise.
* tc.h: Likewise.
* write.c: Likewise.
* write.h: Likewise.
* config/aout_gnu.h: Likewise.
* config/obj-aout.c: Likewise.
* config/obj-aout.h: Likewise.
* config/obj-coff.c: Likewise.
* config/obj-coff.h: Likewise.
* config/obj-evax.h: Likewise.
* config/obj-ieee.h: Likewise.
* config/tc-arm.c: Likewise.
* config/tc-arm.h: Likewise.
* config/tc-avr.c: Likewise.
* config/tc-avr.h: Likewise.
* config/tc-crx.h: Likewise.
* config/tc-d10v.h: Likewise.
* config/tc-d30v.h: Likewise.
* config/tc-dlx.h: Likewise.
* config/tc-fr30.h: Likewise.
* config/tc-frv.h: Likewise.
* config/tc-h8300.c: Likewise.
* config/tc-h8300.h: Likewise.
* config/tc-hppa.h: Likewise.
* config/tc-i370.h: Likewise.
* config/tc-i386.c: Likewise.
* config/tc-i386.h: Likewise.
* config/tc-i860.h: Likewise.
* config/tc-i960.c: Likewise.
* config/tc-i960.h: Likewise.
* config/tc-ip2k.h: Likewise.
* config/tc-iq2000.h: Likewise.
* config/tc-m32c.h: Likewise.
* config/tc-m32r.h: Likewise.
* config/tc-m68hc11.h: Likewise.
* config/tc-m68k.c: Likewise.
* config/tc-m68k.h: Likewise.
* config/tc-maxq.c: Likewise.
* config/tc-maxq.h: Likewise.
* config/tc-mcore.c: Likewise.
* config/tc-mcore.h: Likewise.
* config/tc-mn10200.h: Likewise.
* config/tc-mn10300.c: Likewise.
* config/tc-mn10300.h: Likewise.
* config/tc-ms1.h: Likewise.
* config/tc-msp430.c: Likewise.
* config/tc-msp430.h: Likewise.
* config/tc-ns32k.c: Likewise.
* config/tc-ns32k.h: Likewise.
* config/tc-openrisc.h: Likewise.
* config/tc-or32.c: Likewise.
* config/tc-or32.h: Likewise.
* config/tc-ppc.c: Likewise.
* config/tc-ppc.h: Likewise.
* config/tc-s390.h: Likewise.
* config/tc-sh.c: Likewise.
* config/tc-sh.h: Likewise.
* config/tc-sparc.c: Likewise.
* config/tc-tic30.c: Likewise.
* config/tc-tic30.h: Likewise.
* config/tc-tic4x.c: Likewise.
* config/tc-tic4x.h: Likewise.
* config/tc-tic54x.c: Likewise.
* config/tc-tic54x.h: Likewise.
* config/tc-v850.h: Likewise.
* config/tc-vax.c: Likewise.
* config/tc-vax.h: Likewise.
* config/tc-xstormy16.h: Likewise.
* config/tc-xtensa.h: Likewise.
* config/tc-z8k.c: Likewise.
* config/tc-z8k.h: Likewise.
* config/vms-a-conf.h
* doc/Makefile.am: Likewise.
* doc/all.texi: Likewise.
* doc/as.texinfo: Likewise.
* doc/Makefile.in: Regenerate.
* Makefile.in: Regenerate.
* configure: Regenerate.
* config.in: Regenerate.
* po/POTFILES.in: Regenerate.
2005-08-11 01:25:29 +00:00
|
|
|
/* GAS will call this to generate a reloc, passing the resulting reloc
|
|
|
|
to `bfd_install_relocation'. This currently works poorly, as
|
|
|
|
`bfd_install_relocation' often does the wrong thing, and instances of
|
|
|
|
`tc_gen_reloc' have been written to work around the problems, which
|
|
|
|
in turns makes it difficult to fix `bfd_install_relocation'. */
|
2000-03-27 08:39:14 +00:00
|
|
|
|
|
|
|
/* If while processing a fixup, a reloc really needs to be created
|
|
|
|
then it is done here. */
|
|
|
|
|
|
|
|
arelent *
|
2005-10-12 10:56:46 +00:00
|
|
|
tc_gen_reloc (asection *seg ATTRIBUTE_UNUSED,
|
|
|
|
fixS *fixp)
|
2000-03-27 08:39:14 +00:00
|
|
|
{
|
|
|
|
arelent *reloc;
|
|
|
|
|
2006-03-03 15:25:31 +00:00
|
|
|
if (fixp->fx_addsy && fixp->fx_subsy)
|
|
|
|
{
|
|
|
|
long value = 0;
|
|
|
|
|
|
|
|
if ((S_GET_SEGMENT (fixp->fx_addsy) != S_GET_SEGMENT (fixp->fx_subsy))
|
|
|
|
|| S_GET_SEGMENT (fixp->fx_addsy) == undefined_section)
|
|
|
|
{
|
|
|
|
as_bad_where (fixp->fx_file, fixp->fx_line,
|
|
|
|
"Difference of symbols in different sections is not supported");
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2006-05-24 07:36:12 +00:00
|
|
|
/* We are dealing with two symbols defined in the same section.
|
2006-03-03 15:25:31 +00:00
|
|
|
Let us fix-up them here. */
|
|
|
|
value += S_GET_VALUE (fixp->fx_addsy);
|
|
|
|
value -= S_GET_VALUE (fixp->fx_subsy);
|
|
|
|
|
|
|
|
/* When fx_addsy and fx_subsy both are zero, md_apply_fix
|
|
|
|
only takes it's second operands for the fixup value. */
|
|
|
|
fixp->fx_addsy = NULL;
|
|
|
|
fixp->fx_subsy = NULL;
|
|
|
|
md_apply_fix (fixp, (valueT *) &value, NULL);
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2005-10-12 10:56:46 +00:00
|
|
|
reloc = xmalloc (sizeof (arelent));
|
2000-03-27 08:39:14 +00:00
|
|
|
|
2005-10-12 10:56:46 +00:00
|
|
|
reloc->sym_ptr_ptr = xmalloc (sizeof (asymbol *));
|
2000-03-27 08:39:14 +00:00
|
|
|
*reloc->sym_ptr_ptr = symbol_get_bfdsym (fixp->fx_addsy);
|
|
|
|
|
|
|
|
reloc->address = fixp->fx_frag->fr_address + fixp->fx_where;
|
|
|
|
reloc->howto = bfd_reloc_type_lookup (stdoutput, fixp->fx_r_type);
|
|
|
|
if (reloc->howto == (reloc_howto_type *) NULL)
|
|
|
|
{
|
|
|
|
as_bad_where (fixp->fx_file, fixp->fx_line,
|
2000-07-28 00:42:18 +00:00
|
|
|
_("reloc %d not supported by object file format"),
|
|
|
|
(int) fixp->fx_r_type);
|
2000-03-27 08:39:14 +00:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fixp->fx_r_type == BFD_RELOC_VTABLE_INHERIT
|
|
|
|
|| fixp->fx_r_type == BFD_RELOC_VTABLE_ENTRY)
|
|
|
|
reloc->address = fixp->fx_offset;
|
|
|
|
|
|
|
|
reloc->addend = fixp->fx_offset;
|
|
|
|
|
|
|
|
return reloc;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2005-10-12 10:56:46 +00:00
|
|
|
md_assemble (char *str)
|
2000-03-27 08:39:14 +00:00
|
|
|
{
|
2000-07-28 00:42:18 +00:00
|
|
|
struct avr_opcodes_s *opcode;
|
2000-03-27 08:39:14 +00:00
|
|
|
char op[11];
|
|
|
|
|
2000-07-28 00:42:18 +00:00
|
|
|
str = skip_space (extract_word (str, op, sizeof (op)));
|
2000-03-27 08:39:14 +00:00
|
|
|
|
|
|
|
if (!op[0])
|
2000-07-03 22:25:33 +00:00
|
|
|
as_bad (_("can't find opcode "));
|
2000-03-27 08:39:14 +00:00
|
|
|
|
|
|
|
opcode = (struct avr_opcodes_s *) hash_find (avr_hash, op);
|
|
|
|
|
|
|
|
if (opcode == NULL)
|
|
|
|
{
|
2000-07-03 22:25:33 +00:00
|
|
|
as_bad (_("unknown opcode `%s'"), op);
|
2000-03-27 08:39:14 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2000-05-01 08:48:32 +00:00
|
|
|
/* Special case for opcodes with optional operands (lpm, elpm) -
|
2000-06-07 17:42:44 +00:00
|
|
|
version with operands exists in avr_opcodes[] in the next entry. */
|
2000-07-28 00:42:18 +00:00
|
|
|
|
2000-06-07 17:42:44 +00:00
|
|
|
if (*str && *opcode->constraints == '?')
|
|
|
|
++opcode;
|
2000-05-01 08:48:32 +00:00
|
|
|
|
2000-07-03 22:25:33 +00:00
|
|
|
if (!avr_opt.all_opcodes && (opcode->isa & avr_mcu->isa) != opcode->isa)
|
|
|
|
as_bad (_("illegal opcode %s for mcu %s"), opcode->name, avr_mcu->name);
|
2000-03-27 08:39:14 +00:00
|
|
|
|
|
|
|
/* We used to set input_line_pointer to the result of get_operands,
|
|
|
|
but that is wrong. Our caller assumes we don't change it. */
|
|
|
|
{
|
|
|
|
char *t = input_line_pointer;
|
2005-10-12 10:56:46 +00:00
|
|
|
|
2000-03-27 08:39:14 +00:00
|
|
|
avr_operands (opcode, &str);
|
2000-05-01 08:48:32 +00:00
|
|
|
if (*skip_space (str))
|
2000-07-03 22:25:33 +00:00
|
|
|
as_bad (_("garbage at end of line"));
|
2000-03-27 08:39:14 +00:00
|
|
|
input_line_pointer = t;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Flag to pass `pm' mode between `avr_parse_cons_expression' and
|
2000-07-28 00:42:18 +00:00
|
|
|
`avr_cons_fix_new'. */
|
2000-03-27 08:39:14 +00:00
|
|
|
static int exp_mod_pm = 0;
|
|
|
|
|
|
|
|
/* Parse special CONS expression: pm (expression)
|
2006-05-24 07:36:12 +00:00
|
|
|
or alternatively: gs (expression).
|
|
|
|
These are used for addressing program memory.
|
2000-07-28 00:42:18 +00:00
|
|
|
Relocation: BFD_RELOC_AVR_16_PM. */
|
|
|
|
|
2000-03-27 08:39:14 +00:00
|
|
|
void
|
2005-10-12 10:56:46 +00:00
|
|
|
avr_parse_cons_expression (expressionS *exp, int nbytes)
|
2000-03-27 08:39:14 +00:00
|
|
|
{
|
2000-07-28 00:42:18 +00:00
|
|
|
char *tmp;
|
2000-03-27 08:39:14 +00:00
|
|
|
|
|
|
|
exp_mod_pm = 0;
|
|
|
|
|
|
|
|
tmp = input_line_pointer = skip_space (input_line_pointer);
|
|
|
|
|
|
|
|
if (nbytes == 2)
|
|
|
|
{
|
2006-05-24 07:36:12 +00:00
|
|
|
char *pm_name1 = "pm";
|
|
|
|
char *pm_name2 = "gs";
|
|
|
|
int len = strlen (pm_name1);
|
|
|
|
/* len must be the same for both pm identifiers. */
|
2000-09-15 01:06:52 +00:00
|
|
|
|
2006-05-24 07:36:12 +00:00
|
|
|
if (strncasecmp (input_line_pointer, pm_name1, len) == 0
|
|
|
|
|| strncasecmp (input_line_pointer, pm_name2, len) == 0)
|
2000-03-27 08:39:14 +00:00
|
|
|
{
|
|
|
|
input_line_pointer = skip_space (input_line_pointer + len);
|
2000-09-15 01:06:52 +00:00
|
|
|
|
2000-03-27 08:39:14 +00:00
|
|
|
if (*input_line_pointer == '(')
|
|
|
|
{
|
|
|
|
input_line_pointer = skip_space (input_line_pointer + 1);
|
|
|
|
exp_mod_pm = 1;
|
|
|
|
expression (exp);
|
2000-09-15 01:06:52 +00:00
|
|
|
|
2000-03-27 08:39:14 +00:00
|
|
|
if (*input_line_pointer == ')')
|
|
|
|
++input_line_pointer;
|
|
|
|
else
|
|
|
|
{
|
2000-07-03 22:25:33 +00:00
|
|
|
as_bad (_("`)' required"));
|
2000-03-27 08:39:14 +00:00
|
|
|
exp_mod_pm = 0;
|
|
|
|
}
|
2000-09-15 01:06:52 +00:00
|
|
|
|
2000-03-27 08:39:14 +00:00
|
|
|
return;
|
|
|
|
}
|
2000-09-15 01:06:52 +00:00
|
|
|
|
2000-03-27 08:39:14 +00:00
|
|
|
input_line_pointer = tmp;
|
|
|
|
}
|
|
|
|
}
|
2000-09-15 01:06:52 +00:00
|
|
|
|
2000-03-27 08:39:14 +00:00
|
|
|
expression (exp);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2005-10-12 10:56:46 +00:00
|
|
|
avr_cons_fix_new (fragS *frag,
|
|
|
|
int where,
|
|
|
|
int nbytes,
|
|
|
|
expressionS *exp)
|
2000-03-27 08:39:14 +00:00
|
|
|
{
|
|
|
|
if (exp_mod_pm == 0)
|
|
|
|
{
|
|
|
|
if (nbytes == 2)
|
2002-11-30 08:39:46 +00:00
|
|
|
fix_new_exp (frag, where, nbytes, exp, FALSE, BFD_RELOC_16);
|
2000-03-27 08:39:14 +00:00
|
|
|
else if (nbytes == 4)
|
2002-11-30 08:39:46 +00:00
|
|
|
fix_new_exp (frag, where, nbytes, exp, FALSE, BFD_RELOC_32);
|
2000-03-27 08:39:14 +00:00
|
|
|
else
|
2000-07-03 22:25:33 +00:00
|
|
|
as_bad (_("illegal %srelocation size: %d"), "", nbytes);
|
2000-03-27 08:39:14 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (nbytes == 2)
|
2002-11-30 08:39:46 +00:00
|
|
|
fix_new_exp (frag, where, nbytes, exp, FALSE, BFD_RELOC_AVR_16_PM);
|
2000-03-27 08:39:14 +00:00
|
|
|
else
|
2000-07-03 22:25:33 +00:00
|
|
|
as_bad (_("illegal %srelocation size: %d"), "`pm' ", nbytes);
|
2000-03-27 08:39:14 +00:00
|
|
|
exp_mod_pm = 0;
|
|
|
|
}
|
|
|
|
}
|