mirror of
https://github.com/autc04/Retro68.git
synced 2024-12-11 03:52:59 +00:00
2794 lines
82 KiB
C
2794 lines
82 KiB
C
/* tc-avr.c -- Assembler code for the ATMEL AVR
|
|
|
|
Copyright (C) 1999-2017 Free Software Foundation, Inc.
|
|
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
|
|
the Free Software Foundation; either version 3, or (at your option)
|
|
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
|
|
the Free Software Foundation, 51 Franklin Street - Fifth Floor,
|
|
Boston, MA 02110-1301, USA. */
|
|
|
|
#include "as.h"
|
|
#include "safe-ctype.h"
|
|
#include "subsegs.h"
|
|
#include "struc-symbol.h"
|
|
#include "dwarf2dbg.h"
|
|
#include "dw2gencfi.h"
|
|
#include "elf/avr.h"
|
|
#include "elf32-avr.h"
|
|
|
|
/* For building a linked list of AVR_PROPERTY_RECORD structures. */
|
|
struct avr_property_record_link
|
|
{
|
|
struct avr_property_record record;
|
|
struct avr_property_record_link *next;
|
|
};
|
|
|
|
struct avr_opcodes_s
|
|
{
|
|
const char * name;
|
|
const char * constraints;
|
|
const char * opcode;
|
|
int insn_size; /* In words. */
|
|
int isa;
|
|
unsigned int bin_opcode;
|
|
};
|
|
|
|
#define AVR_INSN(NAME, CONSTR, OPCODE, SIZE, ISA, BIN) \
|
|
{#NAME, CONSTR, OPCODE, SIZE, ISA, BIN},
|
|
|
|
struct avr_opcodes_s avr_opcodes[] =
|
|
{
|
|
#include "opcode/avr.h"
|
|
{NULL, NULL, NULL, 0, 0, 0}
|
|
};
|
|
|
|
|
|
/* Stuff for the `__gcc_isr' pseudo instruction.
|
|
|
|
Purpose of the pseudo instruction is to emit more efficient ISR prologues
|
|
and epilogues than GCC currently does. GCC has no explicit (on RTL level)
|
|
modelling of SREG, TMP_REG or ZERO_REG. These regs are used implicitly
|
|
during instruction printing. That doesn't hurt too much for ordinary
|
|
functions, however for small ISRs there might be some overhead.
|
|
|
|
As implementing http://gcc.gnu.org/PR20296 would imply an almost complete
|
|
rewite of GCC's AVR back-end (which might pop up less optimized code in
|
|
other places), we provide a pseudo-instruction which is resolved by GAS
|
|
into ISR prologue / epilogue as expected by GCC.
|
|
|
|
Using GAS for this purpose has the additional benefit that it can scan
|
|
code emit by inline asm which is opaque to GCC.
|
|
|
|
The pseudo-instruction is only supposed to handle the starting of
|
|
prologue and the ending of epilogues (without RETI) which deal with
|
|
SREG, TMP_REG and ZERO_REG and one additional, optional general purpose
|
|
register.
|
|
|
|
__gcc_isr consists of 3 different "chunks":
|
|
|
|
__gcc_isr 1
|
|
Chunk 1 (ISR_CHUNK_Prologue)
|
|
Start the ISR code. Will be replaced by ISR prologue by next Done chunk.
|
|
Must be the 1st chunk in a file or follow a Done chunk from previous
|
|
ISR (which has been patched already).
|
|
|
|
It will finish the current frag and emit a new frag of
|
|
type rs_machine_dependent, subtype ISR_CHUNK_Prologue.
|
|
|
|
__gcc_isr 2
|
|
Chunk 2 (ISR_CHUNK_Epilogue)
|
|
Will be replaced by ISR epilogue by next Done chunk. Must follow
|
|
chunk 1 (Prologue) or chunk 2 (Epilogue). Functions might come
|
|
without epilogue or with more than one epilogue, and even code
|
|
located statically after the last epilogue might belong to a function.
|
|
|
|
It will finish the current frag and emit a new frag of
|
|
type rs_machine_dependent, subtype ISR_CHUNK_Epilogue.
|
|
|
|
__gcc_isr 0, Rx
|
|
Chunk 0 (ISR_CHUNK_Done)
|
|
Must follow chunk 1 (Prologue) or chunk 2 (Epilogue) and finishes
|
|
the ISR code. Only GCC can know where a function's code ends.
|
|
|
|
It triggers the patch-up of all rs_machine_dependent frags in the
|
|
current frag chain and turns them into ordinary rs_fill code frags.
|
|
|
|
If Rx is a register > ZERO_REG then GCC also wants to push / pop Rx.
|
|
If neither TMP_REG nor ZERO_REG are needed, Rx will be used in
|
|
the push / pop sequence avoiding the need for TMP_REG / ZERO_REG.
|
|
If Rx <= ZERO_REG then GCC doesn't assume anything about Rx.
|
|
|
|
Assumptions:
|
|
|
|
o GCC takes care of code that is opaque to GAS like tail calls
|
|
or non-local goto.
|
|
|
|
o Using SEI / CLI does not count as clobbering SREG. This is
|
|
because a final RETI will restore the I-flag.
|
|
|
|
o Using OUT or ST* is supposed not to clobber SREG. Sequences like
|
|
|
|
IN-SREG + CLI + Atomic-Code + OUT-SREG
|
|
|
|
will still work as expected because the scan will reveal any
|
|
clobber of SREG other than I-flag and emit PUSH / POP of SREG.
|
|
*/
|
|
|
|
enum
|
|
{
|
|
ISR_CHUNK_Done = 0,
|
|
ISR_CHUNK_Prologue = 1,
|
|
ISR_CHUNK_Epilogue = 2
|
|
};
|
|
|
|
static struct
|
|
{
|
|
/* Previous __gcc_isr chunk (one of the enums above)
|
|
and it's location for diagnostics. */
|
|
int prev_chunk;
|
|
unsigned line;
|
|
const char *file;
|
|
/* Replacer for __gcc_isr.n_pushed once we know how many regs are
|
|
pushed by the Prologue chunk. */
|
|
symbolS *sym_n_pushed;
|
|
|
|
/* Set and used during parse from chunk 1 (Prologue) up to chunk 0 (Done).
|
|
Set by `avr_update_gccisr' and used by `avr_patch_gccisr_frag'. */
|
|
int need_reg_tmp;
|
|
int need_reg_zero;
|
|
int need_sreg;
|
|
} avr_isr;
|
|
|
|
static void avr_gccisr_operands (struct avr_opcodes_s*, char**);
|
|
static void avr_update_gccisr (struct avr_opcodes_s*, int, int);
|
|
static struct avr_opcodes_s *avr_gccisr_opcode;
|
|
|
|
const char comment_chars[] = ";";
|
|
const char line_comment_chars[] = "#";
|
|
const char line_separator_chars[] = "$";
|
|
|
|
const char *md_shortopts = "m:";
|
|
struct mcu_type_s
|
|
{
|
|
const char *name;
|
|
int isa;
|
|
int mach;
|
|
};
|
|
|
|
/* XXX - devices that don't seem to exist (renamed, replaced with larger
|
|
ones, or planned but never produced), left here for compatibility. */
|
|
|
|
static struct mcu_type_s mcu_types[] =
|
|
{
|
|
{"avr1", AVR_ISA_AVR1, bfd_mach_avr1},
|
|
/* TODO: instruction set for avr2 architecture should be AVR_ISA_AVR2,
|
|
but set to AVR_ISA_AVR25 for some following version
|
|
of GCC (from 4.3) for backward compatibility. */
|
|
{"avr2", AVR_ISA_AVR25, bfd_mach_avr2},
|
|
{"avr25", AVR_ISA_AVR25, bfd_mach_avr25},
|
|
/* TODO: instruction set for avr3 architecture should be AVR_ISA_AVR3,
|
|
but set to AVR_ISA_AVR3_ALL for some following version
|
|
of GCC (from 4.3) for backward compatibility. */
|
|
{"avr3", AVR_ISA_AVR3_ALL, bfd_mach_avr3},
|
|
{"avr31", AVR_ISA_AVR31, bfd_mach_avr31},
|
|
{"avr35", AVR_ISA_AVR35, bfd_mach_avr35},
|
|
{"avr4", AVR_ISA_AVR4, bfd_mach_avr4},
|
|
/* TODO: instruction set for avr5 architecture should be AVR_ISA_AVR5,
|
|
but set to AVR_ISA_AVR51 for some following version
|
|
of GCC (from 4.3) for backward compatibility. */
|
|
{"avr5", AVR_ISA_AVR51, bfd_mach_avr5},
|
|
{"avr51", AVR_ISA_AVR51, bfd_mach_avr51},
|
|
{"avr6", AVR_ISA_AVR6, bfd_mach_avr6},
|
|
{"avrxmega1", AVR_ISA_XMEGA, bfd_mach_avrxmega1},
|
|
{"avrxmega2", AVR_ISA_XMEGA, bfd_mach_avrxmega2},
|
|
{"avrxmega3", AVR_ISA_XMEGA, bfd_mach_avrxmega3},
|
|
{"avrxmega4", AVR_ISA_XMEGA, bfd_mach_avrxmega4},
|
|
{"avrxmega5", AVR_ISA_XMEGA, bfd_mach_avrxmega5},
|
|
{"avrxmega6", AVR_ISA_XMEGA, bfd_mach_avrxmega6},
|
|
{"avrxmega7", AVR_ISA_XMEGA, bfd_mach_avrxmega7},
|
|
{"avrtiny", AVR_ISA_AVRTINY, bfd_mach_avrtiny},
|
|
{"at90s1200", AVR_ISA_1200, bfd_mach_avr1},
|
|
{"attiny11", AVR_ISA_AVR1, bfd_mach_avr1},
|
|
{"attiny12", AVR_ISA_AVR1, bfd_mach_avr1},
|
|
{"attiny15", AVR_ISA_AVR1, bfd_mach_avr1},
|
|
{"attiny28", AVR_ISA_AVR1, bfd_mach_avr1},
|
|
{"at90s2313", AVR_ISA_AVR2, bfd_mach_avr2},
|
|
{"at90s2323", AVR_ISA_AVR2, bfd_mach_avr2},
|
|
{"at90s2333", AVR_ISA_AVR2, bfd_mach_avr2}, /* XXX -> 4433 */
|
|
{"at90s2343", AVR_ISA_AVR2, bfd_mach_avr2},
|
|
{"attiny22", AVR_ISA_AVR2, bfd_mach_avr2}, /* XXX -> 2343 */
|
|
{"attiny26", AVR_ISA_2xxe, bfd_mach_avr2},
|
|
{"at90s4414", AVR_ISA_AVR2, bfd_mach_avr2}, /* XXX -> 8515 */
|
|
{"at90s4433", AVR_ISA_AVR2, bfd_mach_avr2},
|
|
{"at90s4434", AVR_ISA_AVR2, bfd_mach_avr2}, /* XXX -> 8535 */
|
|
{"at90s8515", AVR_ISA_AVR2, bfd_mach_avr2},
|
|
{"at90c8534", AVR_ISA_AVR2, bfd_mach_avr2},
|
|
{"at90s8535", AVR_ISA_AVR2, bfd_mach_avr2},
|
|
{"ata5272", AVR_ISA_AVR25, bfd_mach_avr25},
|
|
{"attiny13", AVR_ISA_AVR25, bfd_mach_avr25},
|
|
{"attiny13a", AVR_ISA_AVR25, bfd_mach_avr25},
|
|
{"attiny2313", AVR_ISA_AVR25, bfd_mach_avr25},
|
|
{"attiny2313a",AVR_ISA_AVR25, bfd_mach_avr25},
|
|
{"attiny24", AVR_ISA_AVR25, bfd_mach_avr25},
|
|
{"attiny24a", AVR_ISA_AVR25, bfd_mach_avr25},
|
|
{"attiny4313", AVR_ISA_AVR25, bfd_mach_avr25},
|
|
{"attiny44", AVR_ISA_AVR25, bfd_mach_avr25},
|
|
{"attiny44a", AVR_ISA_AVR25, bfd_mach_avr25},
|
|
{"attiny84", AVR_ISA_AVR25, bfd_mach_avr25},
|
|
{"attiny84a", AVR_ISA_AVR25, bfd_mach_avr25},
|
|
{"attiny25", AVR_ISA_AVR25, bfd_mach_avr25},
|
|
{"attiny45", AVR_ISA_AVR25, bfd_mach_avr25},
|
|
{"attiny85", AVR_ISA_AVR25, bfd_mach_avr25},
|
|
{"attiny261", AVR_ISA_AVR25, bfd_mach_avr25},
|
|
{"attiny261a", AVR_ISA_AVR25, bfd_mach_avr25},
|
|
{"attiny461", AVR_ISA_AVR25, bfd_mach_avr25},
|
|
{"attiny461a", AVR_ISA_AVR25, bfd_mach_avr25},
|
|
{"attiny861", AVR_ISA_AVR25, bfd_mach_avr25},
|
|
{"attiny861a", AVR_ISA_AVR25, bfd_mach_avr25},
|
|
{"attiny87", AVR_ISA_AVR25, bfd_mach_avr25},
|
|
{"attiny43u", AVR_ISA_AVR25, bfd_mach_avr25},
|
|
{"attiny48", AVR_ISA_AVR25, bfd_mach_avr25},
|
|
{"attiny88", AVR_ISA_AVR25, bfd_mach_avr25},
|
|
{"attiny828", AVR_ISA_AVR25, bfd_mach_avr25},
|
|
{"at86rf401", AVR_ISA_RF401, bfd_mach_avr25},
|
|
{"at43usb355", AVR_ISA_AVR3, bfd_mach_avr3},
|
|
{"at76c711", AVR_ISA_AVR3, bfd_mach_avr3},
|
|
{"atmega103", AVR_ISA_AVR31, bfd_mach_avr31},
|
|
{"at43usb320", AVR_ISA_AVR31, bfd_mach_avr31},
|
|
{"attiny167", AVR_ISA_AVR35, bfd_mach_avr35},
|
|
{"at90usb82", AVR_ISA_AVR35, bfd_mach_avr35},
|
|
{"at90usb162", AVR_ISA_AVR35, bfd_mach_avr35},
|
|
{"ata5505", AVR_ISA_AVR35, bfd_mach_avr35},
|
|
{"atmega8u2", AVR_ISA_AVR35, bfd_mach_avr35},
|
|
{"atmega16u2", AVR_ISA_AVR35, bfd_mach_avr35},
|
|
{"atmega32u2", AVR_ISA_AVR35, bfd_mach_avr35},
|
|
{"attiny1634", AVR_ISA_AVR35, bfd_mach_avr35},
|
|
{"atmega8", AVR_ISA_M8, bfd_mach_avr4},
|
|
{"ata6289", AVR_ISA_AVR4, bfd_mach_avr4},
|
|
{"atmega8a", AVR_ISA_M8, bfd_mach_avr4},
|
|
{"ata6285", AVR_ISA_AVR4, bfd_mach_avr4},
|
|
{"ata6286", AVR_ISA_AVR4, bfd_mach_avr4},
|
|
{"atmega48", AVR_ISA_AVR4, bfd_mach_avr4},
|
|
{"atmega48a", AVR_ISA_AVR4, bfd_mach_avr4},
|
|
{"atmega48pa", AVR_ISA_AVR4, bfd_mach_avr4},
|
|
{"atmega48p", AVR_ISA_AVR4, bfd_mach_avr4},
|
|
{"atmega88", AVR_ISA_AVR4, bfd_mach_avr4},
|
|
{"atmega88a", AVR_ISA_AVR4, bfd_mach_avr4},
|
|
{"atmega88p", AVR_ISA_AVR4, bfd_mach_avr4},
|
|
{"atmega88pa", AVR_ISA_AVR4, bfd_mach_avr4},
|
|
{"atmega8515", AVR_ISA_M8, bfd_mach_avr4},
|
|
{"atmega8535", AVR_ISA_M8, bfd_mach_avr4},
|
|
{"atmega8hva", AVR_ISA_AVR4, bfd_mach_avr4},
|
|
{"at90pwm1", AVR_ISA_AVR4, bfd_mach_avr4},
|
|
{"at90pwm2", AVR_ISA_AVR4, bfd_mach_avr4},
|
|
{"at90pwm2b", AVR_ISA_AVR4, bfd_mach_avr4},
|
|
{"at90pwm3", AVR_ISA_AVR4, bfd_mach_avr4},
|
|
{"at90pwm3b", AVR_ISA_AVR4, bfd_mach_avr4},
|
|
{"at90pwm81", AVR_ISA_AVR4, bfd_mach_avr4},
|
|
{"at90pwm161", AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"ata5790", AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"ata5795", AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega16", AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega16a", AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega161", AVR_ISA_M161, bfd_mach_avr5},
|
|
{"atmega162", AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega163", AVR_ISA_M161, bfd_mach_avr5},
|
|
{"atmega164a", AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega164p", AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega164pa",AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega165", AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega165a", AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega165p", AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega165pa",AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega168", AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega168a", AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega168p", AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega168pa",AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega169", AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega169a", AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega169p", AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega169pa",AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega32", AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega32a", AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega323", AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega324a", AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega324p", AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega324pa",AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega325", AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega325a", AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega325p", AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega325pa",AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega3250", AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega3250a",AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega3250p",AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega3250pa",AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega328", AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega328p", AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega329", AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega329a", AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega329p", AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega329pa",AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega3290", AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega3290a",AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega3290p",AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega3290pa",AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega406", AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega64rfr2", AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega644rfr2",AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega64", AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega64a", AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega640", AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega644", AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega644a", AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega644p", AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega644pa",AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega645", AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega645a", AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega645p", AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega649", AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega649a", AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega649p", AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega6450", AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega6450a",AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega6450p",AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega6490", AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega6490a",AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega6490p",AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega64rfr2",AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega644rfr2",AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega16hva",AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega16hva2",AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega16hvb",AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega16hvbrevb",AVR_ISA_AVR5,bfd_mach_avr5},
|
|
{"atmega32hvb",AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega32hvbrevb",AVR_ISA_AVR5,bfd_mach_avr5},
|
|
{"atmega64hve",AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"at90can32" , AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"at90can64" , AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"at90pwm161", AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"at90pwm216", AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"at90pwm316", AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega32c1", AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega64c1", AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega16m1", AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega32m1", AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega64m1", AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega16u4", AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega32u4", AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega32u6", AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"at90usb646", AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"at90usb647", AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"at90scr100", AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"at94k", AVR_ISA_94K, bfd_mach_avr5},
|
|
{"m3000", AVR_ISA_AVR5, bfd_mach_avr5},
|
|
{"atmega128", AVR_ISA_AVR51, bfd_mach_avr51},
|
|
{"atmega128a", AVR_ISA_AVR51, bfd_mach_avr51},
|
|
{"atmega1280", AVR_ISA_AVR51, bfd_mach_avr51},
|
|
{"atmega1281", AVR_ISA_AVR51, bfd_mach_avr51},
|
|
{"atmega1284", AVR_ISA_AVR51, bfd_mach_avr51},
|
|
{"atmega1284p",AVR_ISA_AVR51, bfd_mach_avr51},
|
|
{"atmega128rfa1",AVR_ISA_AVR51, bfd_mach_avr51},
|
|
{"atmega128rfr2",AVR_ISA_AVR51, bfd_mach_avr51},
|
|
{"atmega1284rfr2",AVR_ISA_AVR51, bfd_mach_avr51},
|
|
{"at90can128", AVR_ISA_AVR51, bfd_mach_avr51},
|
|
{"at90usb1286",AVR_ISA_AVR51, bfd_mach_avr51},
|
|
{"at90usb1287",AVR_ISA_AVR51, bfd_mach_avr51},
|
|
{"atmega2560", AVR_ISA_AVR6, bfd_mach_avr6},
|
|
{"atmega2561", AVR_ISA_AVR6, bfd_mach_avr6},
|
|
{"atmega256rfr2", AVR_ISA_AVR6, bfd_mach_avr6},
|
|
{"atmega2564rfr2", AVR_ISA_AVR6, bfd_mach_avr6},
|
|
{"atxmega16a4", AVR_ISA_XMEGA, bfd_mach_avrxmega2},
|
|
{"atxmega16a4u",AVR_ISA_XMEGAU, bfd_mach_avrxmega2},
|
|
{"atxmega16c4", AVR_ISA_XMEGAU, bfd_mach_avrxmega2},
|
|
{"atxmega16d4", AVR_ISA_XMEGA, bfd_mach_avrxmega2},
|
|
{"atxmega32a4", AVR_ISA_XMEGA, bfd_mach_avrxmega2},
|
|
{"atxmega32a4u",AVR_ISA_XMEGAU, bfd_mach_avrxmega2},
|
|
{"atxmega32c4", AVR_ISA_XMEGAU, bfd_mach_avrxmega2},
|
|
{"atxmega32d4", AVR_ISA_XMEGA, bfd_mach_avrxmega2},
|
|
{"atxmega32e5", AVR_ISA_XMEGA, bfd_mach_avrxmega2},
|
|
{"atxmega16e5", AVR_ISA_XMEGA, bfd_mach_avrxmega2},
|
|
{"atxmega8e5", AVR_ISA_XMEGA, bfd_mach_avrxmega2},
|
|
{"atxmega32x1", AVR_ISA_XMEGA, bfd_mach_avrxmega2},
|
|
{"attiny416", AVR_ISA_XMEGA, bfd_mach_avrxmega3},
|
|
{"attiny417", AVR_ISA_XMEGA, bfd_mach_avrxmega3},
|
|
{"attiny816", AVR_ISA_XMEGA, bfd_mach_avrxmega3},
|
|
{"attiny817", AVR_ISA_XMEGA, bfd_mach_avrxmega3},
|
|
{"atxmega64a3", AVR_ISA_XMEGA, bfd_mach_avrxmega4},
|
|
{"atxmega64a3u",AVR_ISA_XMEGAU, bfd_mach_avrxmega4},
|
|
{"atxmega64a4u",AVR_ISA_XMEGAU, bfd_mach_avrxmega4},
|
|
{"atxmega64b1", AVR_ISA_XMEGAU, bfd_mach_avrxmega4},
|
|
{"atxmega64b3", AVR_ISA_XMEGAU, bfd_mach_avrxmega4},
|
|
{"atxmega64c3", AVR_ISA_XMEGAU, bfd_mach_avrxmega4},
|
|
{"atxmega64d3", AVR_ISA_XMEGA, bfd_mach_avrxmega4},
|
|
{"atxmega64d4", AVR_ISA_XMEGA, bfd_mach_avrxmega4},
|
|
{"atxmega64a1", AVR_ISA_XMEGA, bfd_mach_avrxmega5},
|
|
{"atxmega64a1u",AVR_ISA_XMEGAU, bfd_mach_avrxmega5},
|
|
{"atxmega128a3", AVR_ISA_XMEGA, bfd_mach_avrxmega6},
|
|
{"atxmega128a3u",AVR_ISA_XMEGAU,bfd_mach_avrxmega6},
|
|
{"atxmega128b1", AVR_ISA_XMEGAU, bfd_mach_avrxmega6},
|
|
{"atxmega128b3", AVR_ISA_XMEGAU,bfd_mach_avrxmega6},
|
|
{"atxmega128c3", AVR_ISA_XMEGAU,bfd_mach_avrxmega6},
|
|
{"atxmega128d3", AVR_ISA_XMEGA, bfd_mach_avrxmega6},
|
|
{"atxmega128d4", AVR_ISA_XMEGA, bfd_mach_avrxmega6},
|
|
{"atxmega192a3", AVR_ISA_XMEGA, bfd_mach_avrxmega6},
|
|
{"atxmega192a3u",AVR_ISA_XMEGAU,bfd_mach_avrxmega6},
|
|
{"atxmega192c3", AVR_ISA_XMEGAU, bfd_mach_avrxmega6},
|
|
{"atxmega192d3", AVR_ISA_XMEGA, bfd_mach_avrxmega6},
|
|
{"atxmega256a3", AVR_ISA_XMEGA, bfd_mach_avrxmega6},
|
|
{"atxmega256a3u",AVR_ISA_XMEGAU,bfd_mach_avrxmega6},
|
|
{"atxmega256a3b",AVR_ISA_XMEGA, bfd_mach_avrxmega6},
|
|
{"atxmega256a3bu",AVR_ISA_XMEGAU, bfd_mach_avrxmega6},
|
|
{"atxmega256c3", AVR_ISA_XMEGAU,bfd_mach_avrxmega6},
|
|
{"atxmega256d3", AVR_ISA_XMEGA, bfd_mach_avrxmega6},
|
|
{"atxmega384c3", AVR_ISA_XMEGAU,bfd_mach_avrxmega6},
|
|
{"atxmega384d3", AVR_ISA_XMEGA, bfd_mach_avrxmega6},
|
|
{"atxmega128a1", AVR_ISA_XMEGA, bfd_mach_avrxmega7},
|
|
{"atxmega128a1u", AVR_ISA_XMEGAU, bfd_mach_avrxmega7},
|
|
{"atxmega128a4u", AVR_ISA_XMEGAU, bfd_mach_avrxmega7},
|
|
{"attiny4", AVR_ISA_AVRTINY, bfd_mach_avrtiny},
|
|
{"attiny5", AVR_ISA_AVRTINY, bfd_mach_avrtiny},
|
|
{"attiny9", AVR_ISA_AVRTINY, bfd_mach_avrtiny},
|
|
{"attiny10", AVR_ISA_AVRTINY, bfd_mach_avrtiny},
|
|
{"attiny20", AVR_ISA_AVRTINY, bfd_mach_avrtiny},
|
|
{"attiny40", AVR_ISA_AVRTINY, bfd_mach_avrtiny},
|
|
{NULL, 0, 0}
|
|
};
|
|
|
|
|
|
/* Current MCU type. */
|
|
static struct mcu_type_s default_mcu = {"avr2", AVR_ISA_AVR2, bfd_mach_avr2};
|
|
static struct mcu_type_s specified_mcu;
|
|
static struct mcu_type_s * avr_mcu = & default_mcu;
|
|
|
|
/* AVR target-specific switches. */
|
|
struct avr_opt_s
|
|
{
|
|
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. */
|
|
int no_link_relax; /* -mno-link-relax / -mlink-relax: generate (or not)
|
|
relocations for linker relaxation. */
|
|
int have_gccisr; /* Whether "__gcc_isr" is a known (pseudo) insn. */
|
|
};
|
|
|
|
static struct avr_opt_s avr_opt = { 0, 0, 0, 0, 0 };
|
|
|
|
const char EXP_CHARS[] = "eE";
|
|
const char FLT_CHARS[] = "dD";
|
|
|
|
static void avr_set_arch (int);
|
|
|
|
/* 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))
|
|
|
|
#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
|
|
|
|
struct exp_mod_s
|
|
{
|
|
const char * name;
|
|
bfd_reloc_code_real_type reloc;
|
|
bfd_reloc_code_real_type neg_reloc;
|
|
int have_pm;
|
|
};
|
|
|
|
static struct exp_mod_s exp_mod[] =
|
|
{
|
|
{"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},
|
|
{"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},
|
|
};
|
|
|
|
/* A union used to store indices into the exp_mod[] array
|
|
in a hash table which expects void * data types. */
|
|
typedef union
|
|
{
|
|
void * ptr;
|
|
int index;
|
|
} mod_index;
|
|
|
|
/* 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;
|
|
|
|
/* Whether some opcode does not change SREG. */
|
|
static struct hash_control *avr_no_sreg_hash;
|
|
|
|
static const char* const avr_no_sreg[] =
|
|
{
|
|
/* Arithmetic */
|
|
"ldi", "swap", "mov", "movw",
|
|
/* Special instructions. I-Flag will be restored by RETI, and we don't
|
|
consider I-Flag as being clobbered when changed. */
|
|
"sei", "cli", "reti", "brie", "brid",
|
|
"nop", "wdr", "sleep",
|
|
/* Load / Store */
|
|
"ld", "ldd", "lds", "pop", "in", "lpm", "elpm",
|
|
"st", "std", "sts", "push", "out",
|
|
/* Jumps and Calls. Calls might call code that changes SREG.
|
|
GCC has to filter out ABI calls. The non-ABI transparent calls
|
|
must use [R]CALL and are filtered out now by not mentioning them. */
|
|
"rjmp", "jmp", "ijmp", "ret",
|
|
/* Skipping. Branches need SREG to be set, hence we regard them
|
|
as if they changed SREG and don't list them here. */
|
|
"sbrc", "sbrs", "sbic", "sbis", "cpse",
|
|
/* I/O Manipulation */
|
|
"sbi", "cbi",
|
|
/* Read-Modify-Write */
|
|
"lac", "las", "lat", "xch"
|
|
};
|
|
|
|
#define OPTION_MMCU 'm'
|
|
enum options
|
|
{
|
|
OPTION_ALL_OPCODES = OPTION_MD_BASE + 1,
|
|
OPTION_NO_SKIP_BUG,
|
|
OPTION_NO_WRAP,
|
|
OPTION_ISA_RMW,
|
|
OPTION_LINK_RELAX,
|
|
OPTION_NO_LINK_RELAX,
|
|
OPTION_HAVE_GCCISR
|
|
};
|
|
|
|
struct option md_longopts[] =
|
|
{
|
|
{ "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 },
|
|
{ "mrmw", no_argument, NULL, OPTION_ISA_RMW },
|
|
{ "mlink-relax", no_argument, NULL, OPTION_LINK_RELAX },
|
|
{ "mno-link-relax", no_argument, NULL, OPTION_NO_LINK_RELAX },
|
|
{ "mgcc-isr", no_argument, NULL, OPTION_HAVE_GCCISR },
|
|
{ NULL, no_argument, NULL, 0 }
|
|
};
|
|
|
|
size_t md_longopts_size = sizeof (md_longopts);
|
|
|
|
/* Display nicely formatted list of known MCU names. */
|
|
|
|
static void
|
|
show_mcu_list (FILE *stream)
|
|
{
|
|
int i, x;
|
|
|
|
fprintf (stream, _("Known MCU names:"));
|
|
x = 1000;
|
|
|
|
for (i = 0; mcu_types[i].name; i++)
|
|
{
|
|
int len = strlen (mcu_types[i].name);
|
|
|
|
x += len + 1;
|
|
|
|
if (x < 75)
|
|
fprintf (stream, " %s", mcu_types[i].name);
|
|
else
|
|
{
|
|
fprintf (stream, "\n %s", mcu_types[i].name);
|
|
x = len + 2;
|
|
}
|
|
}
|
|
|
|
fprintf (stream, "\n");
|
|
}
|
|
|
|
static inline char *
|
|
skip_space (char *s)
|
|
{
|
|
while (*s == ' ' || *s == '\t')
|
|
++s;
|
|
return s;
|
|
}
|
|
|
|
/* Extract one word from FROM and copy it to TO. */
|
|
|
|
static char *
|
|
extract_word (char *from, char *to, int limit)
|
|
{
|
|
char *op_end;
|
|
int size = 0;
|
|
|
|
/* Drop leading whitespace. */
|
|
from = skip_space (from);
|
|
*to = 0;
|
|
|
|
/* Find the op code end. */
|
|
for (op_end = from; *op_end != 0 && is_part_of_name (*op_end);)
|
|
{
|
|
to[size++] = *op_end++;
|
|
if (size + 1 >= limit)
|
|
break;
|
|
}
|
|
|
|
to[size] = 0;
|
|
return op_end;
|
|
}
|
|
|
|
int
|
|
md_estimate_size_before_relax (fragS *fragp ATTRIBUTE_UNUSED,
|
|
asection *seg ATTRIBUTE_UNUSED)
|
|
{
|
|
abort ();
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
md_show_usage (FILE *stream)
|
|
{
|
|
fprintf (stream,
|
|
_("AVR Assembler options:\n"
|
|
" -mmcu=[avr-name] select microcontroller variant\n"
|
|
" [avr-name] can be:\n"
|
|
" avr1 - classic AVR core without data RAM\n"
|
|
" avr2 - classic AVR core with up to 8K program memory\n"
|
|
" avr25 - classic AVR core with up to 8K program memory\n"
|
|
" plus the MOVW instruction\n"
|
|
" avr3 - classic AVR core with up to 64K program memory\n"
|
|
" avr31 - classic AVR core with up to 128K program memory\n"
|
|
" avr35 - classic AVR core with up to 64K program memory\n"
|
|
" plus the MOVW instruction\n"
|
|
" avr4 - enhanced AVR core with up to 8K program memory\n"
|
|
" avr5 - enhanced AVR core with up to 64K program memory\n"
|
|
" avr51 - enhanced AVR core with up to 128K program memory\n"
|
|
" avr6 - enhanced AVR core with up to 256K program memory\n"
|
|
" avrxmega2 - XMEGA, > 8K, < 64K FLASH, < 64K RAM\n"
|
|
" avrxmega3 - XMEGA, > 8K, <= 64K FLASH, > 64K RAM\n"
|
|
" avrxmega4 - XMEGA, > 64K, <= 128K FLASH, <= 64K RAM\n"
|
|
" avrxmega5 - XMEGA, > 64K, <= 128K FLASH, > 64K RAM\n"
|
|
" avrxmega6 - XMEGA, > 128K, <= 256K FLASH, <= 64K RAM\n"
|
|
" avrxmega7 - XMEGA, > 128K, <= 256K FLASH, > 64K RAM\n"
|
|
" avrtiny - AVR Tiny core with 16 gp registers\n"));
|
|
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"
|
|
" -mrmw accept Read-Modify-Write instructions\n"
|
|
" -mlink-relax generate relocations for linker relaxation (default)\n"
|
|
" -mno-link-relax don't generate relocations for linker relaxation.\n"
|
|
" -mgcc-isr accept the __gcc_isr pseudo-instruction.\n"
|
|
));
|
|
show_mcu_list (stream);
|
|
}
|
|
|
|
static void
|
|
avr_set_arch (int dummy ATTRIBUTE_UNUSED)
|
|
{
|
|
char str[20];
|
|
|
|
input_line_pointer = extract_word (input_line_pointer, str, 20);
|
|
md_parse_option (OPTION_MMCU, str);
|
|
bfd_set_arch_mach (stdoutput, TARGET_ARCH, avr_mcu->mach);
|
|
}
|
|
|
|
int
|
|
md_parse_option (int c, const char *arg)
|
|
{
|
|
switch (c)
|
|
{
|
|
case OPTION_MMCU:
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; mcu_types[i].name; ++i)
|
|
if (strcasecmp (mcu_types[i].name, arg) == 0)
|
|
break;
|
|
|
|
if (!mcu_types[i].name)
|
|
{
|
|
show_mcu_list (stderr);
|
|
as_fatal (_("unknown MCU: %s\n"), arg);
|
|
}
|
|
|
|
/* 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)
|
|
{
|
|
specified_mcu.name = mcu_types[i].name;
|
|
specified_mcu.isa |= mcu_types[i].isa;
|
|
specified_mcu.mach = mcu_types[i].mach;
|
|
avr_mcu = &specified_mcu;
|
|
}
|
|
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;
|
|
return 1;
|
|
case OPTION_ISA_RMW:
|
|
specified_mcu.isa |= AVR_ISA_RMW;
|
|
return 1;
|
|
case OPTION_LINK_RELAX:
|
|
avr_opt.no_link_relax = 0;
|
|
return 1;
|
|
case OPTION_NO_LINK_RELAX:
|
|
avr_opt.no_link_relax = 1;
|
|
return 1;
|
|
case OPTION_HAVE_GCCISR:
|
|
avr_opt.have_gccisr = 1;
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* Implement `md_undefined_symbol' */
|
|
/* If we are in `__gcc_isr' chunk, pop up `__gcc_isr.n_pushed.<NUM>'
|
|
instead of `__gcc_isr.n_pushed'. This will be resolved by the Done
|
|
chunk in `avr_patch_gccisr_frag' to the number of PUSHes produced by
|
|
the Prologue chunk. */
|
|
|
|
symbolS *
|
|
avr_undefined_symbol (char *name)
|
|
{
|
|
if (ISR_CHUNK_Done != avr_isr.prev_chunk
|
|
&& 0 == strcmp (name, "__gcc_isr.n_pushed"))
|
|
{
|
|
if (!avr_isr.sym_n_pushed)
|
|
{
|
|
static unsigned suffix;
|
|
char xname[30];
|
|
sprintf (xname, "%s.%03u", name, (++suffix) % 1000);
|
|
avr_isr.sym_n_pushed = symbol_new (xname, undefined_section,
|
|
(valueT) 0, &zero_address_frag);
|
|
}
|
|
return avr_isr.sym_n_pushed;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
const char *
|
|
md_atof (int type, char *litP, int *sizeP)
|
|
{
|
|
return ieee_md_atof (type, litP, sizeP, FALSE);
|
|
}
|
|
|
|
void
|
|
md_convert_frag (bfd *abfd ATTRIBUTE_UNUSED,
|
|
asection *sec ATTRIBUTE_UNUSED,
|
|
fragS *fragP ATTRIBUTE_UNUSED)
|
|
{
|
|
abort ();
|
|
}
|
|
|
|
void
|
|
md_begin (void)
|
|
{
|
|
unsigned int i;
|
|
struct avr_opcodes_s *opcode;
|
|
|
|
avr_hash = hash_new ();
|
|
|
|
/* 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 ();
|
|
|
|
for (i = 0; i < ARRAY_SIZE (exp_mod); ++i)
|
|
{
|
|
mod_index m;
|
|
|
|
m.index = i + 10;
|
|
hash_insert (avr_mod_hash, EXP_MOD_NAME (i), m.ptr);
|
|
}
|
|
|
|
avr_no_sreg_hash = hash_new ();
|
|
|
|
for (i = 0; i < ARRAY_SIZE (avr_no_sreg); ++i)
|
|
{
|
|
gas_assert (hash_find (avr_hash, avr_no_sreg[i]));
|
|
hash_insert (avr_no_sreg_hash, avr_no_sreg[i], (char*) 4 /* dummy */);
|
|
}
|
|
|
|
avr_gccisr_opcode = (struct avr_opcodes_s*) hash_find (avr_hash, "__gcc_isr");
|
|
gas_assert (avr_gccisr_opcode);
|
|
|
|
bfd_set_arch_mach (stdoutput, TARGET_ARCH, avr_mcu->mach);
|
|
linkrelax = !avr_opt.no_link_relax;
|
|
}
|
|
|
|
/* Resolve STR as a constant expression and return the result.
|
|
If result greater than MAX then error. */
|
|
|
|
static unsigned int
|
|
avr_get_constant (char *str, int max)
|
|
{
|
|
expressionS ex;
|
|
|
|
str = skip_space (str);
|
|
input_line_pointer = str;
|
|
expression (& ex);
|
|
|
|
if (ex.X_op != O_constant)
|
|
as_bad (_("constant value required"));
|
|
|
|
if (ex.X_add_number > max || ex.X_add_number < 0)
|
|
as_bad (_("number must be positive and less than %d"), max + 1);
|
|
|
|
return ex.X_add_number;
|
|
}
|
|
|
|
/* Parse for ldd/std offset. */
|
|
|
|
static void
|
|
avr_offset_expression (expressionS *exp)
|
|
{
|
|
char *str = input_line_pointer;
|
|
char *tmp;
|
|
char op[8];
|
|
|
|
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)
|
|
{
|
|
int x = exp->X_add_number;
|
|
|
|
if (x < -255 || x > 255)
|
|
as_warn (_("constant out of 8-bit range: %d"), x);
|
|
}
|
|
}
|
|
|
|
/* Parse ordinary expression. */
|
|
|
|
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;
|
|
}
|
|
|
|
/* Parse special expressions (needed for LDI command):
|
|
xx8 (address)
|
|
xx8 (-address)
|
|
pm_xx8 (address)
|
|
pm_xx8 (-address)
|
|
where xx is: hh, hi, lo. */
|
|
|
|
static bfd_reloc_code_real_type
|
|
avr_ldi_expression (expressionS *exp)
|
|
{
|
|
char *str = input_line_pointer;
|
|
char *tmp;
|
|
char op[8];
|
|
int mod;
|
|
int linker_stubs_should_be_generated = 0;
|
|
|
|
tmp = str;
|
|
|
|
str = extract_word (str, op, sizeof (op));
|
|
|
|
if (op[0])
|
|
{
|
|
mod_index m;
|
|
|
|
m.ptr = hash_find (avr_mod_hash, op);
|
|
mod = m.index;
|
|
|
|
if (mod)
|
|
{
|
|
int closes = 0;
|
|
|
|
mod -= 10;
|
|
str = skip_space (str);
|
|
|
|
if (*str == '(')
|
|
{
|
|
bfd_reloc_code_real_type reloc_to_return;
|
|
int neg_p = 0;
|
|
|
|
++str;
|
|
|
|
if (strncmp ("pm(", str, 3) == 0
|
|
|| strncmp ("gs(",str,3) == 0
|
|
|| strncmp ("-(gs(",str,5) == 0
|
|
|| strncmp ("-(pm(", str, 5) == 0)
|
|
{
|
|
if (HAVE_PM_P (mod))
|
|
{
|
|
++mod;
|
|
++closes;
|
|
}
|
|
else
|
|
as_bad (_("illegal expression"));
|
|
|
|
if (str[0] == 'g' || str[2] == 'g')
|
|
linker_stubs_should_be_generated = 1;
|
|
|
|
if (*str == '-')
|
|
{
|
|
neg_p = 1;
|
|
++closes;
|
|
str += 5;
|
|
}
|
|
else
|
|
str += 3;
|
|
}
|
|
|
|
if (*str == '-' && *(str + 1) == '(')
|
|
{
|
|
neg_p ^= 1;
|
|
++closes;
|
|
str += 2;
|
|
}
|
|
|
|
input_line_pointer = str;
|
|
expression (exp);
|
|
|
|
do
|
|
{
|
|
if (*input_line_pointer != ')')
|
|
{
|
|
as_bad (_("`)' required"));
|
|
break;
|
|
}
|
|
input_line_pointer++;
|
|
}
|
|
while (closes--);
|
|
|
|
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:
|
|
/* PR 5523: Do not generate a warning here,
|
|
legitimate code can trigger this case. */
|
|
break;
|
|
}
|
|
}
|
|
return reloc_to_return;
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
if (x < -255 || x > 255)
|
|
as_warn (_("constant out of 8-bit range: %d"), x);
|
|
}
|
|
|
|
return BFD_RELOC_AVR_LDI;
|
|
}
|
|
|
|
/* Parse one instruction operand.
|
|
Return operand bitmask. Also fixups can be generated. */
|
|
|
|
static unsigned int
|
|
avr_operand (struct avr_opcodes_s *opcode,
|
|
int where,
|
|
const char *op,
|
|
char **line,
|
|
int *pregno)
|
|
{
|
|
expressionS op_expr;
|
|
unsigned int op_mask = 0;
|
|
char *str = skip_space (*line);
|
|
|
|
switch (*op)
|
|
{
|
|
/* Any register operand. */
|
|
case 'w':
|
|
case 'd':
|
|
case 'r':
|
|
case 'a':
|
|
case 'v':
|
|
{
|
|
char * old_str = str;
|
|
char *lower;
|
|
char r_name[20];
|
|
|
|
str = extract_word (str, r_name, sizeof (r_name));
|
|
for (lower = r_name; *lower; ++lower)
|
|
{
|
|
if (*lower >= 'A' && *lower <= 'Z')
|
|
*lower += 'a' - 'A';
|
|
}
|
|
|
|
if (r_name[0] == 'r' && ISDIGIT (r_name[1]) && r_name[2] == 0)
|
|
/* Single-digit register number, ie r0-r9. */
|
|
op_mask = r_name[1] - '0';
|
|
else if (r_name[0] == 'r' && ISDIGIT (r_name[1])
|
|
&& ISDIGIT (r_name[2]) && r_name[3] == 0)
|
|
/* Double-digit register number, ie r10 - r32. */
|
|
op_mask = (r_name[1] - '0') * 10 + r_name[2] - '0';
|
|
else if (r_name[0] >= 'x' && r_name[0] <= 'z'
|
|
&& (r_name[1] == 'l' || r_name[1] == 'h') && r_name[2] == 0)
|
|
/* Registers r26-r31 referred to by name, ie xl, xh, yl, yh, zl, zh. */
|
|
op_mask = (r_name[0] - 'x') * 2 + (r_name[1] == 'h') + 26;
|
|
else if ((*op == 'v' || *op == 'w')
|
|
&& r_name[0] >= 'x' && r_name[0] <= 'z' && r_name[1] == 0)
|
|
/* For the movw and addiw instructions, refer to registers x, y and z by name. */
|
|
op_mask = (r_name[0] - 'x') * 2 + 26;
|
|
else
|
|
{
|
|
/* Numeric or symbolic constant register number. */
|
|
op_mask = avr_get_constant (old_str, 31);
|
|
str = input_line_pointer;
|
|
}
|
|
}
|
|
|
|
if (pregno)
|
|
*pregno = op_mask;
|
|
|
|
if (avr_mcu->mach == bfd_mach_avrtiny)
|
|
{
|
|
if (op_mask < 16 || op_mask > 31)
|
|
{
|
|
as_bad (_("register name or number from 16 to 31 required"));
|
|
break;
|
|
}
|
|
}
|
|
else if (op_mask > 31)
|
|
{
|
|
as_bad (_("register name or number from 0 to 31 required"));
|
|
break;
|
|
}
|
|
|
|
switch (*op)
|
|
{
|
|
case 'a':
|
|
if (op_mask < 16 || op_mask > 23)
|
|
as_bad (_("register r16-r23 required"));
|
|
op_mask -= 16;
|
|
break;
|
|
|
|
case 'd':
|
|
if (op_mask < 16)
|
|
as_bad (_("register number above 15 required"));
|
|
op_mask -= 16;
|
|
break;
|
|
|
|
case 'v':
|
|
if (op_mask & 1)
|
|
as_bad (_("even register number required"));
|
|
op_mask >>= 1;
|
|
break;
|
|
|
|
case 'w':
|
|
if ((op_mask & 1) || op_mask < 24)
|
|
as_bad (_("register r24, r26, r28 or r30 required"));
|
|
op_mask = (op_mask - 24) >> 1;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case 'e':
|
|
{
|
|
char c;
|
|
|
|
if (*str == '-')
|
|
{
|
|
str = skip_space (str + 1);
|
|
op_mask = 0x1002;
|
|
}
|
|
c = TOLOWER (*str);
|
|
if (c == 'x')
|
|
op_mask |= 0x100c;
|
|
else if (c == 'y')
|
|
op_mask |= 0x8;
|
|
else if (c != 'z')
|
|
as_bad (_("pointer register (X, Y or Z) required"));
|
|
|
|
str = skip_space (str + 1);
|
|
if (*str == '+')
|
|
{
|
|
++str;
|
|
if (op_mask & 2)
|
|
as_bad (_("cannot both predecrement and postincrement"));
|
|
op_mask |= 0x1001;
|
|
}
|
|
|
|
/* avr1 can do "ld r,Z" and "st Z,r" but no other pointer
|
|
registers, no predecrement, no postincrement. */
|
|
if (!avr_opt.all_opcodes && (op_mask & 0x100F)
|
|
&& !(avr_mcu->isa & AVR_ISA_SRAM))
|
|
as_bad (_("addressing mode not supported"));
|
|
}
|
|
break;
|
|
|
|
case 'z':
|
|
if (*str == '-')
|
|
as_bad (_("can't predecrement"));
|
|
|
|
if (! (*str == 'z' || *str == 'Z'))
|
|
as_bad (_("pointer register Z required"));
|
|
|
|
str = skip_space (str + 1);
|
|
|
|
if (*str == '+')
|
|
{
|
|
++str;
|
|
const char *s;
|
|
for (s = opcode->opcode; *s; ++s)
|
|
{
|
|
if (*s == '+')
|
|
op_mask |= (1 << (15 - (s - opcode->opcode)));
|
|
}
|
|
}
|
|
|
|
/* 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"));
|
|
break;
|
|
|
|
case 'b':
|
|
{
|
|
char c = TOLOWER (*str++);
|
|
|
|
if (c == 'y')
|
|
op_mask |= 0x8;
|
|
else if (c != 'z')
|
|
as_bad (_("pointer register (Y or Z) required"));
|
|
str = skip_space (str);
|
|
if (*str++ == '+')
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'h':
|
|
str = parse_exp (str, &op_expr);
|
|
fix_new_exp (frag_now, where, opcode->insn_size * 2,
|
|
&op_expr, FALSE, BFD_RELOC_AVR_CALL);
|
|
break;
|
|
|
|
case 'L':
|
|
str = parse_exp (str, &op_expr);
|
|
fix_new_exp (frag_now, where, opcode->insn_size * 2,
|
|
&op_expr, TRUE, BFD_RELOC_AVR_13_PCREL);
|
|
break;
|
|
|
|
case 'l':
|
|
str = parse_exp (str, &op_expr);
|
|
fix_new_exp (frag_now, where, opcode->insn_size * 2,
|
|
&op_expr, TRUE, BFD_RELOC_AVR_7_PCREL);
|
|
break;
|
|
|
|
case 'i':
|
|
str = parse_exp (str, &op_expr);
|
|
fix_new_exp (frag_now, where + 2, opcode->insn_size * 2,
|
|
&op_expr, FALSE, BFD_RELOC_16);
|
|
break;
|
|
|
|
case 'j':
|
|
str = parse_exp (str, &op_expr);
|
|
fix_new_exp (frag_now, where, opcode->insn_size * 2,
|
|
&op_expr, FALSE, BFD_RELOC_AVR_LDS_STS_16);
|
|
break;
|
|
|
|
case 'M':
|
|
{
|
|
bfd_reloc_code_real_type r_type;
|
|
|
|
input_line_pointer = str;
|
|
r_type = avr_ldi_expression (&op_expr);
|
|
str = input_line_pointer;
|
|
fix_new_exp (frag_now, where, 3,
|
|
&op_expr, FALSE, r_type);
|
|
}
|
|
break;
|
|
|
|
case 'n':
|
|
{
|
|
unsigned int x;
|
|
|
|
x = ~avr_get_constant (str, 255);
|
|
str = input_line_pointer;
|
|
op_mask |= (x & 0xf) | ((x << 4) & 0xf00);
|
|
}
|
|
break;
|
|
|
|
case 'N':
|
|
{
|
|
unsigned int x;
|
|
|
|
x = avr_get_constant (str, 255);
|
|
str = input_line_pointer;
|
|
op_mask = x;
|
|
}
|
|
break;
|
|
|
|
case 'K':
|
|
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);
|
|
break;
|
|
|
|
case 'S':
|
|
case 's':
|
|
{
|
|
unsigned int x;
|
|
|
|
x = avr_get_constant (str, 7);
|
|
str = input_line_pointer;
|
|
if (*op == 'S')
|
|
x <<= 4;
|
|
op_mask |= x;
|
|
}
|
|
break;
|
|
|
|
case 'P':
|
|
str = parse_exp (str, &op_expr);
|
|
fix_new_exp (frag_now, where, opcode->insn_size * 2,
|
|
&op_expr, FALSE, BFD_RELOC_AVR_PORT6);
|
|
break;
|
|
|
|
case 'p':
|
|
str = parse_exp (str, &op_expr);
|
|
fix_new_exp (frag_now, where, opcode->insn_size * 2,
|
|
&op_expr, FALSE, BFD_RELOC_AVR_PORT5);
|
|
break;
|
|
|
|
case 'E':
|
|
{
|
|
unsigned int x;
|
|
|
|
x = avr_get_constant (str, 15);
|
|
str = input_line_pointer;
|
|
op_mask |= (x << 4);
|
|
}
|
|
break;
|
|
|
|
case '?':
|
|
break;
|
|
|
|
default:
|
|
as_bad (_("unknown constraint `%c'"), *op);
|
|
}
|
|
|
|
*line = str;
|
|
return op_mask;
|
|
}
|
|
|
|
/* Parse instruction operands.
|
|
Return binary opcode. */
|
|
|
|
static unsigned int
|
|
avr_operands (struct avr_opcodes_s *opcode, char **line)
|
|
{
|
|
const 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. */
|
|
int regno1 = -2;
|
|
int regno2 = -2;
|
|
|
|
/* 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, ®no1);
|
|
++op;
|
|
|
|
/* Parse second operand. */
|
|
if (*op)
|
|
{
|
|
if (*op == ',')
|
|
++op;
|
|
|
|
if (*op == '=')
|
|
{
|
|
reg2 = reg1;
|
|
reg2_present = 1;
|
|
regno2 = regno1;
|
|
}
|
|
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, ®no2);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
if (avr_opt.have_gccisr)
|
|
avr_update_gccisr (opcode, regno1, regno2);
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/* 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. */
|
|
|
|
valueT
|
|
md_section_align (asection *seg, valueT addr)
|
|
{
|
|
int align = bfd_get_section_alignment (stdoutput, seg);
|
|
return ((addr + (1 << align) - 1) & (-1UL << 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. */
|
|
|
|
long
|
|
md_pcrel_from_section (fixS *fixp, segT sec)
|
|
{
|
|
if (fixp->fx_addsy != (symbolS *) NULL
|
|
&& (!S_IS_DEFINED (fixp->fx_addsy)
|
|
|| (S_GET_SEGMENT (fixp->fx_addsy) != sec)))
|
|
return 0;
|
|
|
|
return fixp->fx_frag->fr_address + fixp->fx_where;
|
|
}
|
|
|
|
static bfd_boolean
|
|
relaxable_section (asection *sec)
|
|
{
|
|
return ((sec->flags & SEC_DEBUGGING) == 0
|
|
&& (sec->flags & SEC_CODE) != 0
|
|
&& (sec->flags & SEC_ALLOC) != 0);
|
|
}
|
|
|
|
/* Does whatever the xtensa port does. */
|
|
int
|
|
avr_validate_fix_sub (fixS *fix)
|
|
{
|
|
segT add_symbol_segment, sub_symbol_segment;
|
|
|
|
/* The difference of two symbols should be resolved by the assembler when
|
|
linkrelax is not set. If the linker may relax the section containing
|
|
the symbols, then an Xtensa DIFF relocation must be generated so that
|
|
the linker knows to adjust the difference value. */
|
|
if (!linkrelax || fix->fx_addsy == NULL)
|
|
return 0;
|
|
|
|
/* Make sure both symbols are in the same segment, and that segment is
|
|
"normal" and relaxable. If the segment is not "normal", then the
|
|
fix is not valid. If the segment is not "relaxable", then the fix
|
|
should have been handled earlier. */
|
|
add_symbol_segment = S_GET_SEGMENT (fix->fx_addsy);
|
|
if (! SEG_NORMAL (add_symbol_segment) ||
|
|
! relaxable_section (add_symbol_segment))
|
|
return 0;
|
|
|
|
sub_symbol_segment = S_GET_SEGMENT (fix->fx_subsy);
|
|
return (sub_symbol_segment == add_symbol_segment);
|
|
}
|
|
|
|
/* TC_FORCE_RELOCATION hook */
|
|
|
|
/* If linkrelax is turned on, and the symbol to relocate
|
|
against is in a relaxable segment, don't compute the value -
|
|
generate a relocation instead. */
|
|
int
|
|
avr_force_relocation (fixS *fix)
|
|
{
|
|
if (linkrelax && fix->fx_addsy
|
|
&& relaxable_section (S_GET_SEGMENT (fix->fx_addsy)))
|
|
return 1;
|
|
|
|
return generic_force_reloc (fix);
|
|
}
|
|
|
|
/* GAS will call this for each fixup. It should store the correct
|
|
value in the object file. */
|
|
|
|
void
|
|
md_apply_fix (fixS *fixP, valueT * valP, segT seg)
|
|
{
|
|
unsigned char *where;
|
|
unsigned long insn;
|
|
long value = *valP;
|
|
|
|
if (fixP->fx_addsy == (symbolS *) NULL)
|
|
fixP->fx_done = 1;
|
|
|
|
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;
|
|
}
|
|
}
|
|
else if (linkrelax && fixP->fx_subsy)
|
|
{
|
|
/* For a subtraction relocation expression, generate one
|
|
of the DIFF relocs, with the value being the difference.
|
|
Note that a sym1 - sym2 expression is adjusted into a
|
|
section_start_sym + sym4_offset_from_section_start - sym1
|
|
expression. fixP->fx_addsy holds the section start symbol,
|
|
fixP->fx_offset holds sym2's offset, and fixP->fx_subsy
|
|
holds sym1. Calculate the current difference and write value,
|
|
but leave fx_offset as is - during relaxation,
|
|
fx_offset - value gives sym1's value. */
|
|
|
|
switch (fixP->fx_r_type)
|
|
{
|
|
case BFD_RELOC_8:
|
|
fixP->fx_r_type = BFD_RELOC_AVR_DIFF8;
|
|
break;
|
|
case BFD_RELOC_16:
|
|
fixP->fx_r_type = BFD_RELOC_AVR_DIFF16;
|
|
break;
|
|
case BFD_RELOC_32:
|
|
fixP->fx_r_type = BFD_RELOC_AVR_DIFF32;
|
|
break;
|
|
default:
|
|
as_bad_where (fixP->fx_file, fixP->fx_line, _("expression too complex"));
|
|
break;
|
|
}
|
|
|
|
value = S_GET_VALUE (fixP->fx_addsy) +
|
|
fixP->fx_offset - S_GET_VALUE (fixP->fx_subsy);
|
|
*valP = value;
|
|
|
|
fixP->fx_subsy = NULL;
|
|
}
|
|
/* 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"));
|
|
|
|
/* For the DIFF relocs, write the value into the object file while still
|
|
keeping fx_done FALSE, as both the difference (recorded in the object file)
|
|
and the sym offset (part of fixP) are needed at link relax time. */
|
|
where = (unsigned char *) fixP->fx_frag->fr_literal + fixP->fx_where;
|
|
switch (fixP->fx_r_type)
|
|
{
|
|
default:
|
|
fixP->fx_no_overflow = 1;
|
|
break;
|
|
case BFD_RELOC_AVR_7_PCREL:
|
|
case BFD_RELOC_AVR_13_PCREL:
|
|
case BFD_RELOC_32:
|
|
case BFD_RELOC_16:
|
|
break;
|
|
case BFD_RELOC_AVR_DIFF8:
|
|
*where = value;
|
|
break;
|
|
case BFD_RELOC_AVR_DIFF16:
|
|
bfd_putl16 ((bfd_vma) value, where);
|
|
break;
|
|
case BFD_RELOC_AVR_DIFF32:
|
|
bfd_putl32 ((bfd_vma) value, where);
|
|
break;
|
|
case BFD_RELOC_AVR_CALL:
|
|
break;
|
|
}
|
|
|
|
if (fixP->fx_done)
|
|
{
|
|
/* Fetch the instruction, insert the fully resolved operand
|
|
value, and stuff the instruction back again. */
|
|
where = (unsigned char *) fixP->fx_frag->fr_literal + fixP->fx_where;
|
|
insn = bfd_getl16 (where);
|
|
|
|
switch (fixP->fx_r_type)
|
|
{
|
|
case BFD_RELOC_AVR_7_PCREL:
|
|
if (value & 1)
|
|
as_bad_where (fixP->fx_file, fixP->fx_line,
|
|
_("odd address operand: %ld"), value);
|
|
|
|
/* Instruction addresses are always right-shifted by 1. */
|
|
value >>= 1;
|
|
--value; /* Correct PC. */
|
|
|
|
if (value < -64 || value > 63)
|
|
as_bad_where (fixP->fx_file, fixP->fx_line,
|
|
_("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)
|
|
as_bad_where (fixP->fx_file, fixP->fx_line,
|
|
_("odd address operand: %ld"), value);
|
|
|
|
/* Instruction addresses are always right-shifted by 1. */
|
|
value >>= 1;
|
|
--value; /* Correct PC. */
|
|
|
|
if (value < -2048 || value > 2047)
|
|
{
|
|
/* No wrap for devices with >8K of program memory. */
|
|
if ((avr_mcu->isa & AVR_ISA_MEGA) || avr_opt.no_wrap)
|
|
as_bad_where (fixP->fx_file, fixP->fx_line,
|
|
_("operand out of range: %ld"), value);
|
|
}
|
|
|
|
value &= 0xfff;
|
|
bfd_putl16 ((bfd_vma) (value | insn), where);
|
|
break;
|
|
|
|
case BFD_RELOC_32:
|
|
bfd_putl32 ((bfd_vma) value, where);
|
|
break;
|
|
|
|
case BFD_RELOC_16:
|
|
bfd_putl16 ((bfd_vma) value, where);
|
|
break;
|
|
|
|
case BFD_RELOC_8:
|
|
if (value > 255 || value < -128)
|
|
as_warn_where (fixP->fx_file, fixP->fx_line,
|
|
_("operand out of range: %ld"), value);
|
|
*where = value;
|
|
break;
|
|
|
|
case BFD_RELOC_AVR_16_PM:
|
|
bfd_putl16 ((bfd_vma) (value >> 1), where);
|
|
break;
|
|
|
|
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_LDS_STS_16:
|
|
if ((value < 0x40) || (value > 0xBF))
|
|
as_warn_where (fixP->fx_file, fixP->fx_line,
|
|
_("operand out of range: 0x%lx"),
|
|
(unsigned long)value);
|
|
insn |= ((value & 0xF) | ((value & 0x30) << 5) | ((value & 0x40) << 2));
|
|
bfd_putl16 ((bfd_vma) insn, 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;
|
|
|
|
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;
|
|
|
|
case BFD_RELOC_AVR_MS8_LDI:
|
|
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;
|
|
|
|
case BFD_RELOC_AVR_MS8_LDI_NEG:
|
|
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;
|
|
|
|
x = bfd_getl16 (where);
|
|
if (value & 1)
|
|
as_bad_where (fixP->fx_file, fixP->fx_line,
|
|
_("odd address operand: %ld"), value);
|
|
value >>= 1;
|
|
x |= ((value & 0x10000) | ((value << 3) & 0x1f00000)) >> 16;
|
|
bfd_putl16 ((bfd_vma) x, where);
|
|
bfd_putl16 ((bfd_vma) (value & 0xffff), where + 2);
|
|
}
|
|
break;
|
|
|
|
case BFD_RELOC_AVR_8_LO:
|
|
*where = 0xff & value;
|
|
break;
|
|
|
|
case BFD_RELOC_AVR_8_HI:
|
|
*where = 0xff & (value >> 8);
|
|
break;
|
|
|
|
case BFD_RELOC_AVR_8_HLO:
|
|
*where = 0xff & (value >> 16);
|
|
break;
|
|
|
|
default:
|
|
as_fatal (_("line %d: unknown relocation type: 0x%x"),
|
|
fixP->fx_line, fixP->fx_r_type);
|
|
break;
|
|
|
|
case BFD_RELOC_AVR_PORT6:
|
|
if (value > 63)
|
|
as_bad_where (fixP->fx_file, fixP->fx_line,
|
|
_("operand out of range: %ld"), value);
|
|
bfd_putl16 ((bfd_vma) insn | ((value & 0x30) << 5) | (value & 0x0f), where);
|
|
break;
|
|
|
|
case BFD_RELOC_AVR_PORT5:
|
|
if (value > 31)
|
|
as_bad_where (fixP->fx_file, fixP->fx_line,
|
|
_("operand out of range: %ld"), value);
|
|
bfd_putl16 ((bfd_vma) insn | ((value & 0x1f) << 3), where);
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch ((int) fixP->fx_r_type)
|
|
{
|
|
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:
|
|
as_bad_where (fixP->fx_file, fixP->fx_line,
|
|
_("only constant expression allowed"));
|
|
fixP->fx_done = 1;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* 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'. */
|
|
|
|
/* If while processing a fixup, a reloc really needs to be created
|
|
then it is done here. */
|
|
|
|
arelent *
|
|
tc_gen_reloc (asection *seg ATTRIBUTE_UNUSED,
|
|
fixS *fixp)
|
|
{
|
|
arelent *reloc;
|
|
bfd_reloc_code_real_type code = fixp->fx_r_type;
|
|
|
|
if (fixp->fx_subsy != NULL)
|
|
{
|
|
as_bad_where (fixp->fx_file, fixp->fx_line, _("expression too complex"));
|
|
return NULL;
|
|
}
|
|
|
|
reloc = XNEW (arelent);
|
|
|
|
reloc->sym_ptr_ptr = XNEW (asymbol *);
|
|
*reloc->sym_ptr_ptr = symbol_get_bfdsym (fixp->fx_addsy);
|
|
|
|
reloc->address = fixp->fx_frag->fr_address + fixp->fx_where;
|
|
|
|
if ((fixp->fx_r_type == BFD_RELOC_32) && (fixp->fx_pcrel))
|
|
{
|
|
if (seg->use_rela_p)
|
|
fixp->fx_offset -= md_pcrel_from_section (fixp, seg);
|
|
else
|
|
fixp->fx_offset = reloc->address;
|
|
|
|
code = BFD_RELOC_32_PCREL;
|
|
}
|
|
|
|
reloc->addend = fixp->fx_offset;
|
|
|
|
reloc->howto = bfd_reloc_type_lookup (stdoutput, code);
|
|
|
|
if (reloc->howto == (reloc_howto_type *) NULL)
|
|
{
|
|
as_bad_where (fixp->fx_file, fixp->fx_line,
|
|
_("reloc %d not supported by object file format"),
|
|
(int) fixp->fx_r_type);
|
|
return NULL;
|
|
}
|
|
|
|
if (fixp->fx_r_type == BFD_RELOC_VTABLE_INHERIT
|
|
|| fixp->fx_r_type == BFD_RELOC_VTABLE_ENTRY)
|
|
reloc->address = fixp->fx_offset;
|
|
|
|
|
|
return reloc;
|
|
}
|
|
|
|
void
|
|
md_assemble (char *str)
|
|
{
|
|
struct avr_opcodes_s *opcode;
|
|
char op[11];
|
|
|
|
str = skip_space (extract_word (str, op, sizeof (op)));
|
|
|
|
if (!op[0])
|
|
as_bad (_("can't find opcode "));
|
|
|
|
opcode = (struct avr_opcodes_s *) hash_find (avr_hash, op);
|
|
|
|
if (opcode && !avr_opt.all_opcodes)
|
|
{
|
|
/* Check if the instruction's ISA bit is ON in the ISA bits of the part
|
|
specified by the user. If not look for other instructions
|
|
specifications with same mnemonic who's ISA bits matches.
|
|
|
|
This requires include/opcode/avr.h to have the instructions with
|
|
same mnemonic to be specified in sequence. */
|
|
|
|
while ((opcode->isa & avr_mcu->isa) != opcode->isa)
|
|
{
|
|
opcode++;
|
|
|
|
if (opcode->name && strcmp(op, opcode->name))
|
|
{
|
|
as_bad (_("illegal opcode %s for mcu %s"),
|
|
opcode->name, avr_mcu->name);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (opcode == NULL)
|
|
{
|
|
as_bad (_("unknown opcode `%s'"), op);
|
|
return;
|
|
}
|
|
|
|
if (opcode == avr_gccisr_opcode
|
|
&& !avr_opt.have_gccisr)
|
|
{
|
|
as_bad (_("pseudo instruction `%s' not supported"), op);
|
|
return;
|
|
}
|
|
|
|
/* Special case for opcodes with optional operands (lpm, elpm) -
|
|
version with operands exists in avr_opcodes[] in the next entry. */
|
|
|
|
if (*str && *opcode->constraints == '?')
|
|
++opcode;
|
|
|
|
dwarf2_emit_insn (0);
|
|
|
|
/* 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;
|
|
|
|
if (opcode == avr_gccisr_opcode)
|
|
avr_gccisr_operands (opcode, &str);
|
|
else
|
|
avr_operands (opcode, &str);
|
|
if (*skip_space (str))
|
|
as_bad (_("garbage at end of line"));
|
|
input_line_pointer = t;
|
|
}
|
|
}
|
|
|
|
const exp_mod_data_t exp_mod_data[] =
|
|
{
|
|
/* Default, must be first. */
|
|
{ "", 0, BFD_RELOC_16, "" },
|
|
/* Divides by 2 to get word address. Generate Stub. */
|
|
{ "gs", 2, BFD_RELOC_AVR_16_PM, "`gs' " },
|
|
{ "pm", 2, BFD_RELOC_AVR_16_PM, "`pm' " },
|
|
/* The following are used together with avr-gcc's __memx address space
|
|
in order to initialize a 24-bit pointer variable with a 24-bit address.
|
|
For address in flash, hlo8 will contain the flash segment if the
|
|
symbol is located in flash. If the symbol is located in RAM; hlo8
|
|
will contain 0x80 which matches avr-gcc's notion of how 24-bit RAM/flash
|
|
addresses linearize address space. */
|
|
{ "lo8", 1, BFD_RELOC_AVR_8_LO, "`lo8' " },
|
|
{ "hi8", 1, BFD_RELOC_AVR_8_HI, "`hi8' " },
|
|
{ "hlo8", 1, BFD_RELOC_AVR_8_HLO, "`hlo8' " },
|
|
{ "hh8", 1, BFD_RELOC_AVR_8_HLO, "`hh8' " },
|
|
};
|
|
|
|
/* Parse special CONS expression: pm (expression) or alternatively
|
|
gs (expression). These are used for addressing program memory. Moreover,
|
|
define lo8 (expression), hi8 (expression) and hlo8 (expression). */
|
|
|
|
const exp_mod_data_t *
|
|
avr_parse_cons_expression (expressionS *exp, int nbytes)
|
|
{
|
|
char *tmp;
|
|
unsigned int i;
|
|
|
|
tmp = input_line_pointer = skip_space (input_line_pointer);
|
|
|
|
/* The first entry of exp_mod_data[] contains an entry if no
|
|
expression modifier is present. Skip it. */
|
|
|
|
for (i = 0; i < ARRAY_SIZE (exp_mod_data); i++)
|
|
{
|
|
const exp_mod_data_t *pexp = &exp_mod_data[i];
|
|
int len = strlen (pexp->name);
|
|
|
|
if (nbytes == pexp->nbytes
|
|
&& strncasecmp (input_line_pointer, pexp->name, len) == 0)
|
|
{
|
|
input_line_pointer = skip_space (input_line_pointer + len);
|
|
|
|
if (*input_line_pointer == '(')
|
|
{
|
|
input_line_pointer = skip_space (input_line_pointer + 1);
|
|
expression (exp);
|
|
|
|
if (*input_line_pointer == ')')
|
|
{
|
|
++input_line_pointer;
|
|
return pexp;
|
|
}
|
|
else
|
|
{
|
|
as_bad (_("`)' required"));
|
|
return &exp_mod_data[0];
|
|
}
|
|
}
|
|
|
|
input_line_pointer = tmp;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
expression (exp);
|
|
return &exp_mod_data[0];
|
|
}
|
|
|
|
void
|
|
avr_cons_fix_new (fragS *frag,
|
|
int where,
|
|
int nbytes,
|
|
expressionS *exp,
|
|
const exp_mod_data_t *pexp_mod_data)
|
|
{
|
|
int bad = 0;
|
|
|
|
switch (pexp_mod_data->reloc)
|
|
{
|
|
default:
|
|
if (nbytes == 1)
|
|
fix_new_exp (frag, where, nbytes, exp, FALSE, BFD_RELOC_8);
|
|
else if (nbytes == 2)
|
|
fix_new_exp (frag, where, nbytes, exp, FALSE, BFD_RELOC_16);
|
|
else if (nbytes == 4)
|
|
fix_new_exp (frag, where, nbytes, exp, FALSE, BFD_RELOC_32);
|
|
else
|
|
bad = 1;
|
|
break;
|
|
|
|
case BFD_RELOC_AVR_16_PM:
|
|
case BFD_RELOC_AVR_8_LO:
|
|
case BFD_RELOC_AVR_8_HI:
|
|
case BFD_RELOC_AVR_8_HLO:
|
|
if (nbytes == pexp_mod_data->nbytes)
|
|
fix_new_exp (frag, where, nbytes, exp, FALSE, pexp_mod_data->reloc);
|
|
else
|
|
bad = 1;
|
|
break;
|
|
}
|
|
|
|
if (bad)
|
|
as_bad (_("illegal %s relocation size: %d"), pexp_mod_data->error, nbytes);
|
|
}
|
|
|
|
static bfd_boolean
|
|
mcu_has_3_byte_pc (void)
|
|
{
|
|
int mach = avr_mcu->mach;
|
|
|
|
return mach == bfd_mach_avr6
|
|
|| mach == bfd_mach_avrxmega6
|
|
|| mach == bfd_mach_avrxmega7;
|
|
}
|
|
|
|
void
|
|
tc_cfi_frame_initial_instructions (void)
|
|
{
|
|
/* AVR6 pushes 3 bytes for calls. */
|
|
int return_size = (mcu_has_3_byte_pc () ? 3 : 2);
|
|
|
|
/* The CFA is the caller's stack location before the call insn. */
|
|
/* Note that the stack pointer is dwarf register number 32. */
|
|
cfi_add_CFA_def_cfa (32, return_size);
|
|
|
|
/* Note that AVR consistently uses post-decrement, which means that things
|
|
do not line up the same way as for targets that use pre-decrement. */
|
|
cfi_add_CFA_offset (DWARF2_DEFAULT_RETURN_COLUMN, 1-return_size);
|
|
}
|
|
|
|
bfd_boolean
|
|
avr_allow_local_subtract (expressionS * left,
|
|
expressionS * right,
|
|
segT section)
|
|
{
|
|
/* If we are not in relaxation mode, subtraction is OK. */
|
|
if (!linkrelax)
|
|
return TRUE;
|
|
|
|
/* If the symbols are not in a code section then they are OK. */
|
|
if ((section->flags & SEC_CODE) == 0)
|
|
return TRUE;
|
|
|
|
if (left->X_add_symbol == right->X_add_symbol)
|
|
return TRUE;
|
|
|
|
/* We have to assume that there may be instructions between the
|
|
two symbols and that relaxation may increase the distance between
|
|
them. */
|
|
return FALSE;
|
|
}
|
|
|
|
void
|
|
avr_elf_final_processing (void)
|
|
{
|
|
if (linkrelax)
|
|
elf_elfheader (stdoutput)->e_flags |= EF_AVR_LINKRELAX_PREPARED;
|
|
}
|
|
|
|
/* Write out the header of a .avr.prop section into the area pointed to by
|
|
DATA. The RECORD_COUNT will be placed in the header as the number of
|
|
records that are to follow.
|
|
The area DATA must be big enough the receive the header, which is
|
|
AVR_PROPERTY_SECTION_HEADER_SIZE bytes long. */
|
|
|
|
static char *
|
|
avr_output_property_section_header (char *data,
|
|
unsigned int record_count)
|
|
{
|
|
char *orig_data = data;
|
|
|
|
md_number_to_chars (data, AVR_PROPERTY_RECORDS_VERSION, 1);
|
|
data++;
|
|
/* There's space for a single byte flags field, but right now there's
|
|
nothing to go in here, so just set the value to zero. */
|
|
md_number_to_chars (data, 0, 1);
|
|
data++;
|
|
md_number_to_chars (data, record_count, 2);
|
|
data+=2;
|
|
|
|
gas_assert (data - orig_data == AVR_PROPERTY_SECTION_HEADER_SIZE);
|
|
|
|
return data;
|
|
}
|
|
|
|
/* Return the number of bytes required to store RECORD into the .avr.prop
|
|
section. The size returned is the compressed size that corresponds to
|
|
how the record will be written out in AVR_OUTPUT_PROPERTY_RECORD. */
|
|
|
|
static int
|
|
avr_record_size (const struct avr_property_record *record)
|
|
{
|
|
/* The first 5 bytes are a 4-byte address, followed by a 1-byte type
|
|
identifier. */
|
|
int size = 5;
|
|
|
|
switch (record->type)
|
|
{
|
|
case RECORD_ORG:
|
|
size += 0; /* No extra information. */
|
|
break;
|
|
|
|
case RECORD_ORG_AND_FILL:
|
|
size += 4; /* A 4-byte fill value. */
|
|
break;
|
|
|
|
case RECORD_ALIGN:
|
|
size += 4; /* A 4-byte alignment value. */
|
|
break;
|
|
|
|
case RECORD_ALIGN_AND_FILL:
|
|
size += 8; /* A 4-byte alignment, and 4-byte fill value. */
|
|
break;
|
|
|
|
default:
|
|
as_fatal (_("unknown record type %d (in %s)"),
|
|
record->type, __PRETTY_FUNCTION__);
|
|
}
|
|
|
|
return size;
|
|
}
|
|
|
|
/* Write out RECORD. FRAG_BASE points to the start of the data area setup
|
|
to hold all of the .avr.prop content, FRAG_PTR points to the next
|
|
writable location. The data area must be big enough to hold all of the
|
|
records. The size of the data written out for this RECORD must match
|
|
the size from AVR_RECORD_SIZE. */
|
|
|
|
static char *
|
|
avr_output_property_record (char * const frag_base, char *frag_ptr,
|
|
const struct avr_property_record *record)
|
|
{
|
|
fixS *fix;
|
|
int where;
|
|
char *init_frag_ptr = frag_ptr;
|
|
|
|
where = frag_ptr - frag_base;
|
|
fix = fix_new (frag_now, where, 4,
|
|
section_symbol (record->section),
|
|
record->offset, FALSE, BFD_RELOC_32);
|
|
fix->fx_file = "<internal>";
|
|
fix->fx_line = 0;
|
|
frag_ptr += 4;
|
|
|
|
md_number_to_chars (frag_ptr, (bfd_byte) record->type, 1);
|
|
frag_ptr += 1;
|
|
|
|
/* Write out the rest of the data. */
|
|
switch (record->type)
|
|
{
|
|
case RECORD_ORG:
|
|
break;
|
|
|
|
case RECORD_ORG_AND_FILL:
|
|
md_number_to_chars (frag_ptr, record->data.org.fill, 4);
|
|
frag_ptr += 4;
|
|
break;
|
|
|
|
case RECORD_ALIGN:
|
|
md_number_to_chars (frag_ptr, record->data.align.bytes, 4);
|
|
frag_ptr += 4;
|
|
break;
|
|
|
|
case RECORD_ALIGN_AND_FILL:
|
|
md_number_to_chars (frag_ptr, record->data.align.bytes, 4);
|
|
md_number_to_chars (frag_ptr + 4, record->data.align.fill, 4);
|
|
frag_ptr += 8;
|
|
break;
|
|
|
|
default:
|
|
as_fatal (_("unknown record type %d (in %s)"),
|
|
record->type, __PRETTY_FUNCTION__);
|
|
}
|
|
|
|
gas_assert (frag_ptr - init_frag_ptr == avr_record_size (record));
|
|
|
|
return frag_ptr;
|
|
}
|
|
|
|
/* Create the section to hold the AVR property information. Return the
|
|
section. */
|
|
|
|
static asection *
|
|
avr_create_property_section (void)
|
|
{
|
|
asection *sec;
|
|
flagword flags = (SEC_RELOC | SEC_HAS_CONTENTS | SEC_READONLY);
|
|
const char *section_name = AVR_PROPERTY_RECORD_SECTION_NAME;
|
|
|
|
sec = bfd_make_section (stdoutput, section_name);
|
|
if (sec == NULL)
|
|
as_fatal (_("Failed to create property section `%s'\n"), section_name);
|
|
bfd_set_section_flags (stdoutput, sec, flags);
|
|
sec->output_section = sec;
|
|
return sec;
|
|
}
|
|
|
|
/* This hook is called when alignment is performed, and allows us to
|
|
capture the details of both .org and .align directives. */
|
|
|
|
void
|
|
avr_handle_align (fragS *fragP)
|
|
{
|
|
if (linkrelax)
|
|
{
|
|
/* Ignore alignment requests at FR_ADDRESS 0, these are at the very
|
|
start of a section, and will be handled by the standard section
|
|
alignment mechanism. */
|
|
if ((fragP->fr_type == rs_align
|
|
|| fragP->fr_type == rs_align_code)
|
|
&& fragP->fr_offset > 0)
|
|
{
|
|
char *p = fragP->fr_literal + fragP->fr_fix;
|
|
|
|
fragP->tc_frag_data.is_align = TRUE;
|
|
fragP->tc_frag_data.alignment = fragP->fr_offset;
|
|
fragP->tc_frag_data.fill = *p;
|
|
fragP->tc_frag_data.has_fill = (fragP->tc_frag_data.fill != 0);
|
|
}
|
|
|
|
if (fragP->fr_type == rs_org && fragP->fr_offset > 0)
|
|
{
|
|
char *p = fragP->fr_literal + fragP->fr_fix;
|
|
|
|
fragP->tc_frag_data.is_org = TRUE;
|
|
fragP->tc_frag_data.fill = *p;
|
|
fragP->tc_frag_data.has_fill = (fragP->tc_frag_data.fill != 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Return TRUE if this section is not one for which we need to record
|
|
information in the avr property section. */
|
|
|
|
static bfd_boolean
|
|
exclude_section_from_property_tables (segT sec)
|
|
{
|
|
/* Only generate property information for sections on which linker
|
|
relaxation could be performed. */
|
|
return !relaxable_section (sec);
|
|
}
|
|
|
|
/* Create a property record for fragment FRAGP from section SEC and place
|
|
it into an AVR_PROPERTY_RECORD_LINK structure, which can then formed
|
|
into a linked list by the caller. */
|
|
|
|
static struct avr_property_record_link *
|
|
create_record_for_frag (segT sec, fragS *fragP)
|
|
{
|
|
struct avr_property_record_link *prop_rec_link;
|
|
|
|
prop_rec_link = XCNEW (struct avr_property_record_link);
|
|
gas_assert (fragP->fr_next != NULL);
|
|
|
|
if (fragP->tc_frag_data.is_org)
|
|
{
|
|
prop_rec_link->record.offset = fragP->fr_next->fr_address;
|
|
prop_rec_link->record.section = sec;
|
|
|
|
if (fragP->tc_frag_data.has_fill)
|
|
{
|
|
prop_rec_link->record.data.org.fill = fragP->tc_frag_data.fill;
|
|
prop_rec_link->record.type = RECORD_ORG_AND_FILL;
|
|
}
|
|
else
|
|
prop_rec_link->record.type = RECORD_ORG;
|
|
}
|
|
else
|
|
{
|
|
prop_rec_link->record.offset = fragP->fr_next->fr_address;
|
|
prop_rec_link->record.section = sec;
|
|
|
|
gas_assert (fragP->tc_frag_data.is_align);
|
|
if (fragP->tc_frag_data.has_fill)
|
|
{
|
|
prop_rec_link->record.data.align.fill = fragP->tc_frag_data.fill;
|
|
prop_rec_link->record.type = RECORD_ALIGN_AND_FILL;
|
|
}
|
|
else
|
|
prop_rec_link->record.type = RECORD_ALIGN;
|
|
prop_rec_link->record.data.align.bytes = fragP->tc_frag_data.alignment;
|
|
}
|
|
|
|
return prop_rec_link;
|
|
}
|
|
|
|
/* Build a list of AVR_PROPERTY_RECORD_LINK structures for section SEC, and
|
|
merged them onto the list pointed to by NEXT_PTR. Return a pointer to
|
|
the last list item created. */
|
|
|
|
static struct avr_property_record_link **
|
|
append_records_for_section (segT sec,
|
|
struct avr_property_record_link **next_ptr)
|
|
{
|
|
segment_info_type *seginfo = seg_info (sec);
|
|
fragS *fragP;
|
|
|
|
if (seginfo && seginfo->frchainP)
|
|
{
|
|
for (fragP = seginfo->frchainP->frch_root;
|
|
fragP;
|
|
fragP = fragP->fr_next)
|
|
{
|
|
if (fragP->tc_frag_data.is_align
|
|
|| fragP->tc_frag_data.is_org)
|
|
{
|
|
/* Create a single new entry. */
|
|
struct avr_property_record_link *new_link
|
|
= create_record_for_frag (sec, fragP);
|
|
|
|
*next_ptr = new_link;
|
|
next_ptr = &new_link->next;
|
|
}
|
|
}
|
|
}
|
|
|
|
return next_ptr;
|
|
}
|
|
|
|
/* Create the AVR property section and fill it with records of .org and
|
|
.align directives that were used. The section is only created if it
|
|
will actually have any content. */
|
|
|
|
static void
|
|
avr_create_and_fill_property_section (void)
|
|
{
|
|
segT *seclist;
|
|
asection *prop_sec;
|
|
struct avr_property_record_link *r_list, **next_ptr;
|
|
char *frag_ptr, *frag_base;
|
|
bfd_size_type sec_size;
|
|
struct avr_property_record_link *rec;
|
|
unsigned int record_count;
|
|
|
|
/* First walk over all sections. For sections on which linker
|
|
relaxation could be applied, extend the record list. The record list
|
|
holds information that the linker will need to know. */
|
|
|
|
prop_sec = NULL;
|
|
r_list = NULL;
|
|
next_ptr = &r_list;
|
|
for (seclist = &stdoutput->sections;
|
|
seclist && *seclist;
|
|
seclist = &(*seclist)->next)
|
|
{
|
|
segT sec = *seclist;
|
|
|
|
if (exclude_section_from_property_tables (sec))
|
|
continue;
|
|
|
|
next_ptr = append_records_for_section (sec, next_ptr);
|
|
}
|
|
|
|
/* Create property section and ensure the size is correct. We've already
|
|
passed the point where gas could size this for us. */
|
|
sec_size = AVR_PROPERTY_SECTION_HEADER_SIZE;
|
|
record_count = 0;
|
|
for (rec = r_list; rec != NULL; rec = rec->next)
|
|
{
|
|
record_count++;
|
|
sec_size += avr_record_size (&rec->record);
|
|
}
|
|
|
|
if (record_count == 0)
|
|
return;
|
|
|
|
prop_sec = avr_create_property_section ();
|
|
bfd_set_section_size (stdoutput, prop_sec, sec_size);
|
|
|
|
subseg_set (prop_sec, 0);
|
|
frag_base = frag_more (sec_size);
|
|
|
|
frag_ptr =
|
|
avr_output_property_section_header (frag_base, record_count);
|
|
|
|
for (rec = r_list; rec != NULL; rec = rec->next)
|
|
frag_ptr = avr_output_property_record (frag_base, frag_ptr, &rec->record);
|
|
|
|
frag_wane (frag_now);
|
|
frag_new (0);
|
|
frag_wane (frag_now);
|
|
}
|
|
|
|
/* We're using this hook to build up the AVR property section. It's called
|
|
late in the assembly process which suits our needs. */
|
|
void
|
|
avr_post_relax_hook (void)
|
|
{
|
|
avr_create_and_fill_property_section ();
|
|
}
|
|
|
|
|
|
/* Accumulate information about instruction sequence to `avr_isr':
|
|
wheter TMP_REG, ZERO_REG and SREG might be touched. Used during parse.
|
|
REG1 is either -1 or a register number used by the instruction as input
|
|
or output operand. Similar for REG2. */
|
|
|
|
static void
|
|
avr_update_gccisr (struct avr_opcodes_s *opcode, int reg1, int reg2)
|
|
{
|
|
const int tiny_p = avr_mcu->mach == bfd_mach_avrtiny;
|
|
const int reg_tmp = tiny_p ? 16 : 0;
|
|
const int reg_zero = 1 + reg_tmp;
|
|
|
|
if (ISR_CHUNK_Done == avr_isr.prev_chunk
|
|
|| (avr_isr.need_sreg
|
|
&& avr_isr.need_reg_tmp
|
|
&& avr_isr.need_reg_zero))
|
|
{
|
|
/* Nothing (more) to do */
|
|
return;
|
|
}
|
|
|
|
/* SREG: Look up instructions that don't clobber SREG. */
|
|
|
|
if (!avr_isr.need_sreg
|
|
&& !hash_find (avr_no_sreg_hash, opcode->name))
|
|
{
|
|
avr_isr.need_sreg = 1;
|
|
}
|
|
|
|
/* Handle explicit register operands. Record *any* use as clobber.
|
|
This is because TMP_REG and ZERO_REG are not global and using
|
|
them makes no sense without a previous set. */
|
|
|
|
avr_isr.need_reg_tmp |= reg1 == reg_tmp || reg2 == reg_tmp;
|
|
avr_isr.need_reg_zero |= reg1 == reg_zero || reg2 == reg_zero;
|
|
|
|
/* Handle implicit register operands and some opaque stuff. */
|
|
|
|
if (strstr (opcode->name, "lpm")
|
|
&& '?' == *opcode->constraints)
|
|
{
|
|
avr_isr.need_reg_tmp = 1;
|
|
}
|
|
|
|
if (strstr (opcode->name, "call")
|
|
|| strstr (opcode->name, "mul")
|
|
|| 0 == strcmp (opcode->name, "des")
|
|
|| (0 == strcmp (opcode->name, "movw")
|
|
&& (reg1 == reg_tmp || reg2 == reg_tmp)))
|
|
{
|
|
avr_isr.need_reg_tmp = 1;
|
|
avr_isr.need_reg_zero = 1;
|
|
}
|
|
}
|
|
|
|
|
|
/* Emit some 1-word instruction to **PWHERE and advance *PWHERE by the number
|
|
of octets written. INSN specifies the desired instruction and REG is the
|
|
register used by it. This function is only used with restricted subset of
|
|
instructions as might be emit by `__gcc_isr'. IN / OUT will use SREG
|
|
and LDI loads 0. */
|
|
|
|
static void
|
|
avr_emit_insn (const char *insn, int reg, char **pwhere)
|
|
{
|
|
const int sreg = 0x3f;
|
|
unsigned bin = 0;
|
|
const struct avr_opcodes_s *op
|
|
= (struct avr_opcodes_s*) hash_find (avr_hash, insn);
|
|
|
|
/* We only have to deal with: IN, OUT, PUSH, POP, CLR, LDI 0. All of
|
|
these deal with at least one Reg and are 1-word instructions. */
|
|
|
|
gas_assert (op && 1 == op->insn_size);
|
|
gas_assert (reg >= 0 && reg <= 31);
|
|
|
|
if (strchr (op->constraints, 'r'))
|
|
{
|
|
bin = op->bin_opcode | (reg << 4);
|
|
}
|
|
else if (strchr (op->constraints, 'd'))
|
|
{
|
|
gas_assert (reg >= 16);
|
|
bin = op->bin_opcode | ((reg & 0xf) << 4);
|
|
}
|
|
else
|
|
abort();
|
|
|
|
if (strchr (op->constraints, 'P'))
|
|
{
|
|
bin |= ((sreg & 0x30) << 5) | (sreg & 0x0f);
|
|
}
|
|
else if (0 == strcmp ("r=r", op->constraints))
|
|
{
|
|
bin |= ((reg & 0x10) << 5) | (reg & 0x0f);
|
|
}
|
|
else
|
|
gas_assert (0 == strcmp ("r", op->constraints)
|
|
|| 0 == strcmp ("ldi", op->name));
|
|
|
|
bfd_putl16 ((bfd_vma) bin, *pwhere);
|
|
(*pwhere) += 2 * op->insn_size;
|
|
}
|
|
|
|
|
|
/* Turn rs_machine_dependent frag *FR into an ordinary rs_fill code frag,
|
|
using information gathered in `avr_isr'. REG is the register number as
|
|
supplied by Done chunk "__gcc_isr 0,REG". */
|
|
|
|
static void
|
|
avr_patch_gccisr_frag (fragS *fr, int reg)
|
|
{
|
|
int treg;
|
|
int n_pushed = 0;
|
|
char *where = fr->fr_literal;
|
|
const int tiny_p = avr_mcu->mach == bfd_mach_avrtiny;
|
|
const int reg_tmp = tiny_p ? 16 : 0;
|
|
const int reg_zero = 1 + reg_tmp;
|
|
|
|
/* Clearing ZERO_REG on non-Tiny needs CLR which clobbers SREG. */
|
|
|
|
avr_isr.need_sreg |= !tiny_p && avr_isr.need_reg_zero;
|
|
|
|
/* A working register to PUSH / POP the SREG. We might use the register
|
|
as supplied by ISR_CHUNK_Done for that purpose as GCC wants to push
|
|
it anyways. If GCC passes ZERO_REG or TMP_REG, it has no clue (and
|
|
no additional regs to safe) and we use that reg. */
|
|
|
|
treg
|
|
= avr_isr.need_reg_tmp ? reg_tmp
|
|
: avr_isr.need_reg_zero ? reg_zero
|
|
: avr_isr.need_sreg ? reg
|
|
: reg > reg_zero ? reg
|
|
: -1;
|
|
|
|
if (treg >= 0)
|
|
{
|
|
/* Non-empty prologue / epilogue */
|
|
|
|
if (ISR_CHUNK_Prologue == fr->fr_subtype)
|
|
{
|
|
avr_emit_insn ("push", treg, &where);
|
|
n_pushed++;
|
|
|
|
if (avr_isr.need_sreg)
|
|
{
|
|
avr_emit_insn ("in", treg, &where);
|
|
avr_emit_insn ("push", treg, &where);
|
|
n_pushed++;
|
|
}
|
|
|
|
if (avr_isr.need_reg_zero)
|
|
{
|
|
if (reg_zero != treg)
|
|
{
|
|
avr_emit_insn ("push", reg_zero, &where);
|
|
n_pushed++;
|
|
}
|
|
avr_emit_insn (tiny_p ? "ldi" : "clr", reg_zero, &where);
|
|
}
|
|
|
|
if (reg > reg_zero && reg != treg)
|
|
{
|
|
avr_emit_insn ("push", reg, &where);
|
|
n_pushed++;
|
|
}
|
|
}
|
|
else if (ISR_CHUNK_Epilogue == fr->fr_subtype)
|
|
{
|
|
/* Same logic as in Prologue but in reverse order and with counter
|
|
parts of either instruction: POP instead of PUSH and OUT instead
|
|
of IN. Clearing ZERO_REG has no couter part. */
|
|
|
|
if (reg > reg_zero && reg != treg)
|
|
avr_emit_insn ("pop", reg, &where);
|
|
|
|
if (avr_isr.need_reg_zero
|
|
&& reg_zero != treg)
|
|
avr_emit_insn ("pop", reg_zero, &where);
|
|
|
|
if (avr_isr.need_sreg)
|
|
{
|
|
avr_emit_insn ("pop", treg, &where);
|
|
avr_emit_insn ("out", treg, &where);
|
|
}
|
|
|
|
avr_emit_insn ("pop", treg, &where);
|
|
}
|
|
else
|
|
abort();
|
|
} /* treg >= 0 */
|
|
|
|
if (ISR_CHUNK_Prologue == fr->fr_subtype
|
|
&& avr_isr.sym_n_pushed)
|
|
{
|
|
symbolS *sy = avr_isr.sym_n_pushed;
|
|
/* Turn magic `__gcc_isr.n_pushed' into its now known value. */
|
|
|
|
sy->sy_value.X_op = O_constant;
|
|
sy->sy_value.X_add_number = n_pushed;
|
|
S_SET_SEGMENT (sy, expr_section);
|
|
avr_isr.sym_n_pushed = NULL;
|
|
}
|
|
|
|
/* Turn frag into ordinary code frag of now known size. */
|
|
|
|
fr->fr_var = 0;
|
|
fr->fr_fix = (offsetT) (where - fr->fr_literal);
|
|
gas_assert (fr->fr_fix <= fr->fr_offset);
|
|
fr->fr_offset = 0;
|
|
fr->fr_type = rs_fill;
|
|
fr->fr_subtype = 0;
|
|
}
|
|
|
|
|
|
/* Implements `__gcc_isr' pseudo-instruction. For Prologue and Epilogue
|
|
chunks, emit a new rs_machine_dependent frag. For Done chunks, traverse
|
|
the current segment and patch all rs_machine_dependent frags to become
|
|
appropriate rs_fill code frags. If chunks are seen in an odd ordering,
|
|
throw an error instead. */
|
|
|
|
static void
|
|
avr_gccisr_operands (struct avr_opcodes_s *opcode, char **line)
|
|
{
|
|
int bad = 0;
|
|
int chunk, reg = 0;
|
|
char *str = *line;
|
|
|
|
gas_assert (avr_opt.have_gccisr);
|
|
|
|
/* We only use operands "N" and "r" which don't pop new fix-ups. */
|
|
|
|
/* 1st operand: Which chunk of __gcc_isr: 0...2. */
|
|
|
|
chunk = avr_operand (opcode, -1, "N", &str, NULL);
|
|
if (chunk < 0 || chunk > 2)
|
|
as_bad (_("%s requires value 0-2 as operand 1"), opcode->name);
|
|
|
|
if (ISR_CHUNK_Done == chunk)
|
|
{
|
|
/* 2nd operand: A register to push / pop. */
|
|
|
|
str = skip_space (str);
|
|
if (*str == '\0' || *str++ != ',')
|
|
as_bad (_("`,' required"));
|
|
else
|
|
avr_operand (opcode, -1, "r", &str, ®);
|
|
}
|
|
|
|
*line = str;
|
|
|
|
/* Chunks must follow in a specific order:
|
|
- Prologue: Exactly one
|
|
- Epilogue: Any number
|
|
- Done: Exactly one. */
|
|
bad |= ISR_CHUNK_Prologue == chunk && avr_isr.prev_chunk != ISR_CHUNK_Done;
|
|
bad |= ISR_CHUNK_Epilogue == chunk && avr_isr.prev_chunk == ISR_CHUNK_Done;
|
|
bad |= ISR_CHUNK_Done == chunk && avr_isr.prev_chunk == ISR_CHUNK_Done;
|
|
if (bad)
|
|
{
|
|
if (avr_isr.file)
|
|
as_bad (_("`%s %d' after `%s %d' from %s:%u"), opcode->name, chunk,
|
|
opcode->name, avr_isr.prev_chunk, avr_isr.file, avr_isr.line);
|
|
else
|
|
as_bad (_("`%s %d' but no chunk open yet"), opcode->name, chunk);
|
|
}
|
|
|
|
if (!had_errors())
|
|
{
|
|
/* The longest sequence (prologue) might have up to 6 insns (words):
|
|
|
|
push R0
|
|
in R0, SREG
|
|
push R0
|
|
push R1
|
|
clr R1
|
|
push Rx
|
|
*/
|
|
unsigned int size = 2 * 6;
|
|
fragS *fr;
|
|
|
|
switch (chunk)
|
|
{
|
|
case ISR_CHUNK_Prologue:
|
|
avr_isr.need_reg_tmp = 0;
|
|
avr_isr.need_reg_zero = 0;
|
|
avr_isr.need_sreg = 0;
|
|
avr_isr.sym_n_pushed = NULL;
|
|
/* FALLTHRU */
|
|
|
|
case ISR_CHUNK_Epilogue:
|
|
/* Emit a new rs_machine_dependent fragment into the fragment chain.
|
|
It will be patched and cleaned up once we see the matching
|
|
ISR_CHUNK_Done. */
|
|
frag_wane (frag_now);
|
|
frag_new (0);
|
|
frag_more (size);
|
|
|
|
frag_now->fr_var = 1;
|
|
frag_now->fr_offset = size;
|
|
frag_now->fr_fix = 0;
|
|
frag_now->fr_type = rs_machine_dependent;
|
|
frag_now->fr_subtype = chunk;
|
|
frag_new (size);
|
|
break;
|
|
|
|
case ISR_CHUNK_Done:
|
|
/* Traverse all frags of the current subseg and turn ones of type
|
|
rs_machine_dependent into ordinary code as expected by GCC. */
|
|
|
|
for (fr = frchain_now->frch_root; fr; fr = fr->fr_next)
|
|
if (fr->fr_type == rs_machine_dependent)
|
|
avr_patch_gccisr_frag (fr, reg);
|
|
break;
|
|
|
|
default:
|
|
abort();
|
|
break;
|
|
}
|
|
} /* !had_errors */
|
|
|
|
avr_isr.prev_chunk = chunk;
|
|
avr_isr.file = as_where (&avr_isr.line);
|
|
}
|
|
|
|
|
|
/* Callback used by the function below. Diagnose any dangling stuff from
|
|
`__gcc_isr', i.e. frags of type rs_machine_dependent. Such frags should
|
|
have been resolved during parse by ISR_CHUNK_Done. If such a frag is
|
|
seen, report an error and turn it into something harmless. */
|
|
|
|
static void
|
|
avr_check_gccisr_done (bfd *abfd ATTRIBUTE_UNUSED,
|
|
segT section,
|
|
void *xxx ATTRIBUTE_UNUSED)
|
|
{
|
|
segment_info_type *info = seg_info (section);
|
|
|
|
if (SEG_NORMAL (section)
|
|
/* BFD may have introduced its own sections without using
|
|
subseg_new, so it is possible that seg_info is NULL. */
|
|
&& info)
|
|
{
|
|
fragS *fr;
|
|
frchainS *frch;
|
|
|
|
for (frch = info->frchainP; frch; frch = frch->frch_next)
|
|
for (fr = frch->frch_root; fr; fr = fr->fr_next)
|
|
if (fr->fr_type == rs_machine_dependent)
|
|
{
|
|
if (avr_isr.file)
|
|
as_bad_where (avr_isr.file, avr_isr.line,
|
|
_("dangling `__gcc_isr %d'"), avr_isr.prev_chunk);
|
|
else if (!had_errors())
|
|
as_bad (_("dangling `__gcc_isr'"));
|
|
|
|
avr_isr.file = NULL;
|
|
|
|
/* Avoid Internal errors due to rs_machine_dependent in the
|
|
remainder: Turn frag into something harmless. */
|
|
fr->fr_var = 0;
|
|
fr->fr_fix = 0;
|
|
fr->fr_offset = 0;
|
|
fr->fr_type = rs_fill;
|
|
fr->fr_subtype = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* Implement `md_pre_output_hook' */
|
|
/* Run over all relevant sections and diagnose any dangling `__gcc_isr'.
|
|
This runs after parsing all inputs but before relaxing and writing. */
|
|
|
|
void
|
|
avr_pre_output_hook (void)
|
|
{
|
|
if (avr_opt.have_gccisr)
|
|
bfd_map_over_sections (stdoutput, avr_check_gccisr_done, NULL);
|
|
}
|