/* tc-avr.c -- Assembler code for the ATMEL AVR Copyright (C) 1999, 2000 Free Software Foundation, Inc. Contributed by Denis Chertykov 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 2, 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, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include "as.h" #include "subsegs.h" const char comment_chars[] = ";"; const char line_comment_chars[] = "#"; const char line_separator_chars[] = "$"; #define AVR_ISA_1200 1 #define AVR_ISA_2xxx 3 #define AVR_ISA_MEGA_x03 0x17 #define AVR_ISA_MEGA 0x10 #define AVR_ISA_MEGA_161 0x1b const char *md_shortopts = "m:"; struct mcu_type_s { char *name; int isa; int mach; }; static struct mcu_type_s mcu_types[] = { {"avr1", AVR_ISA_1200, bfd_mach_avr1}, {"avr2", AVR_ISA_2xxx, bfd_mach_avr2}, {"avr3", AVR_ISA_MEGA_x03, bfd_mach_avr3}, {"avr4", AVR_ISA_MEGA_161, bfd_mach_avr4}, {"at90s1200", AVR_ISA_1200, bfd_mach_avr1}, {"at90s2313", AVR_ISA_2xxx, bfd_mach_avr2}, {"at90s2323", AVR_ISA_2xxx, bfd_mach_avr2}, {"at90s2333", AVR_ISA_2xxx, bfd_mach_avr2}, {"attiny22" , AVR_ISA_2xxx, bfd_mach_avr2}, {"at90s2343", AVR_ISA_2xxx, bfd_mach_avr2}, {"at90s4433", AVR_ISA_2xxx, bfd_mach_avr2}, {"at90s4414", AVR_ISA_2xxx, bfd_mach_avr2}, {"at90s4434", AVR_ISA_2xxx, bfd_mach_avr2}, {"at90s8515", AVR_ISA_2xxx, bfd_mach_avr2}, {"at90s8535", AVR_ISA_2xxx, bfd_mach_avr2}, {"atmega603", AVR_ISA_MEGA_x03, bfd_mach_avr3}, {"atmega103", AVR_ISA_MEGA_x03, bfd_mach_avr3}, {"atmega161", AVR_ISA_MEGA_161, bfd_mach_avr4}, {NULL, 0, 0} }; /* Current MCU type. */ static struct mcu_type_s default_mcu = {"avr2", AVR_ISA_2xxx,bfd_mach_avr2}; static struct mcu_type_s *avr_mcu = &default_mcu; const char EXP_CHARS[] = "eE"; const char FLT_CHARS[] = "dD"; static void avr_set_arch (int dummy); /* 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 REGISTER_P(x) ((x) == 'r' || (x) == 'd' || (x) == 'w') struct avr_opcodes_s { char *name; char *constraints; char *opcode; int insn_size; /* in words */ int isa; unsigned int bin_opcode; }; static char * skip_space (char * s); static char * extract_word (char *from, char *to, int limit); static unsigned int avr_operand (struct avr_opcodes_s *opcode, int where, char *op, char **line); static unsigned int avr_operands (struct avr_opcodes_s *opcode, char **line); static unsigned int avr_get_constant (char * str, unsigned int max); static char *parse_exp (char *s, expressionS * op); static bfd_reloc_code_real_type avr_ldi_expression (expressionS *exp); long md_pcrel_from_section PARAMS ((fixS *, segT)); /* constraint letters r - any register d - `ldi' register (r16-r31) M - immediate value from 0 to 255 n - immediate value from 0 to 255 ( n = ~M ). Relocation impossible w - `adiw' register (r24,r26,r28,r30) s - immediate value from 0 to 7 P - Port address value from 0 to 64. (in, out) p - Port address value from 0 to 32. (cbi, sbi, sbic, sbis) K - immediate value from 0 to 64 (used in `adiw', `sbiw') e - pointer regegisters (X,Y,Z) b - base pointer register and displacement ([YZ]+disp) i - immediate value l - signed pc relative offset from -64 to 63 L - signed pc relative offset from -2048 to 2047 h - absolut code address (call, jmp) S - immediate value from 0 to 7 (S = s << 4) */ struct avr_opcodes_s avr_opcodes[] = { {"adc", "r,r", "000111rdddddrrrr", 1, AVR_ISA_1200, 0x1c00}, {"add", "r,r", "000011rdddddrrrr", 1, AVR_ISA_1200, 0x0c00}, {"and", "r,r", "001000rdddddrrrr", 1, AVR_ISA_1200, 0x2000}, {"cp", "r,r", "000101rdddddrrrr", 1, AVR_ISA_1200, 0x1400}, {"cpc", "r,r", "000001rdddddrrrr", 1, AVR_ISA_1200, 0x0400}, {"cpse", "r,r", "000100rdddddrrrr", 1, AVR_ISA_1200, 0x1000}, {"eor", "r,r", "001001rdddddrrrr", 1, AVR_ISA_1200, 0x2400}, {"mov", "r,r", "001011rdddddrrrr", 1, AVR_ISA_1200, 0x2c00}, {"mul", "r,r", "100111rdddddrrrr", 1, AVR_ISA_MEGA_161, 0x9c00}, {"or", "r,r", "001010rdddddrrrr", 1, AVR_ISA_1200, 0x2800}, {"sbc", "r,r", "000010rdddddrrrr", 1, AVR_ISA_1200, 0x0800}, {"sub", "r,r", "000110rdddddrrrr", 1, AVR_ISA_1200, 0x1800}, {"clr", "r=r", "001001rdddddrrrr", 1, AVR_ISA_1200, 0x2400}, {"lsl", "r=r", "000011rdddddrrrr", 1, AVR_ISA_1200, 0x0c00}, {"rol", "r=r", "000111rdddddrrrr", 1, AVR_ISA_1200, 0x1c00}, {"tst", "r=r", "001000rdddddrrrr", 1, AVR_ISA_1200, 0x2000}, {"andi", "d,M", "0111KKKKddddKKKK", 1, AVR_ISA_1200, 0x7000}, /*XXX special case*/ {"cbr", "d,n", "0111KKKKddddKKKK", 1, AVR_ISA_1200, 0x7000}, {"cpi", "d,M", "0011KKKKddddKKKK", 1, AVR_ISA_1200, 0x3000}, {"ldi", "d,M", "1110KKKKddddKKKK", 1, AVR_ISA_1200, 0xe000}, {"ori", "d,M", "0110KKKKddddKKKK", 1, AVR_ISA_1200, 0x6000}, {"sbci", "d,M", "0100KKKKddddKKKK", 1, AVR_ISA_1200, 0x4000}, {"sbr", "d,M", "0110KKKKddddKKKK", 1, AVR_ISA_1200, 0x6000}, {"subi", "d,M", "0101KKKKddddKKKK", 1, AVR_ISA_1200, 0x5000}, {"sbrc", "r,s", "1111110rrrrr0sss", 1, AVR_ISA_1200, 0xfc00}, {"sbrs", "r,s", "1111111rrrrr0sss", 1, AVR_ISA_1200, 0xfe00}, {"bld", "r,s", "1111100ddddd0sss", 1, AVR_ISA_1200, 0xf800}, {"bst", "r,s", "1111101ddddd0sss", 1, AVR_ISA_1200, 0xfa00}, {"in", "r,P", "10110PPdddddPPPP", 1, AVR_ISA_1200, 0xb000}, {"out", "P,r", "10111PPrrrrrPPPP", 1, AVR_ISA_1200, 0xb800}, {"adiw", "w,K", "10010110KKddKKKK", 1, AVR_ISA_2xxx, 0x9600}, {"sbiw", "w,K", "10010111KKddKKKK", 1, AVR_ISA_2xxx, 0x9700}, {"cbi", "p,s", "10011000pppppsss", 1, AVR_ISA_1200, 0x9800}, {"sbi", "p,s", "10011010pppppsss", 1, AVR_ISA_1200, 0x9a00}, {"sbic", "p,s", "10011001pppppsss", 1, AVR_ISA_1200, 0x9900}, {"sbis", "p,s", "10011011pppppsss", 1, AVR_ISA_1200, 0x9b00}, /* ee = {X=11,Y=10,Z=00, 0} */ {"ld", "r,e", "100!000dddddee-+", 1, AVR_ISA_2xxx, 0x8000}, {"st", "e,r", "100!001rrrrree-+", 1, AVR_ISA_2xxx, 0x8200}, {"ldd", "r,b", "10o0oo0dddddbooo", 1, AVR_ISA_2xxx, 0x8000}, {"std", "b,r", "10o0oo1rrrrrbooo", 1, AVR_ISA_2xxx, 0x8200}, {"sts", "i,r", "1001001ddddd0000", 2, AVR_ISA_2xxx, 0x9200}, {"lds", "r,i", "1001000ddddd0000", 2, AVR_ISA_2xxx, 0x9000}, {"brbc", "s,l", "111101lllllllsss", 1, AVR_ISA_1200, 0xf400}, {"brbs", "s,l", "111100lllllllsss", 1, AVR_ISA_1200, 0xf000}, {"brcc", "l", "111101lllllll000", 1, AVR_ISA_1200, 0xf400}, {"brcs", "l", "111100lllllll000", 1, AVR_ISA_1200, 0xf000}, {"breq", "l", "111100lllllll001", 1, AVR_ISA_1200, 0xf001}, {"brge", "l", "111101lllllll100", 1, AVR_ISA_1200, 0xf404}, {"brhc", "l", "111101lllllll101", 1, AVR_ISA_1200, 0xf405}, {"brhs", "l", "111100lllllll101", 1, AVR_ISA_1200, 0xf005}, {"brid", "l", "111101lllllll111", 1, AVR_ISA_1200, 0xf407}, {"brie", "l", "111100lllllll111", 1, AVR_ISA_1200, 0xf007}, {"brlo", "l", "111100lllllll000", 1, AVR_ISA_1200, 0xf000}, {"brlt", "l", "111100lllllll100", 1, AVR_ISA_1200, 0xf004}, {"brmi", "l", "111100lllllll010", 1, AVR_ISA_1200, 0xf002}, {"brne", "l", "111101lllllll001", 1, AVR_ISA_1200, 0xf401}, {"brpl", "l", "111101lllllll010", 1, AVR_ISA_1200, 0xf402}, {"brsh", "l", "111101lllllll000", 1, AVR_ISA_1200, 0xf400}, {"brtc", "l", "111101lllllll110", 1, AVR_ISA_1200, 0xf406}, {"brts", "l", "111100lllllll110", 1, AVR_ISA_1200, 0xf006}, {"brvc", "l", "111101lllllll011", 1, AVR_ISA_1200, 0xf403}, {"brvs", "l", "111100lllllll011", 1, AVR_ISA_1200, 0xf003}, {"rcall", "L", "1101LLLLLLLLLLLL", 1, AVR_ISA_1200, 0xd000}, {"rjmp", "L", "1100LLLLLLLLLLLL", 1, AVR_ISA_1200, 0xc000}, {"call", "h", "1001010hhhhh111h", 2, AVR_ISA_MEGA, 0x940e}, {"jmp", "h", "1001010hhhhh110h", 2, AVR_ISA_MEGA, 0x940c}, {"asr", "r", "1001010rrrrr0101", 1, AVR_ISA_1200, 0x9405}, {"com", "r", "1001010rrrrr0000", 1, AVR_ISA_1200, 0x9400}, {"dec", "r", "1001010rrrrr1010", 1, AVR_ISA_1200, 0x940a}, {"inc", "r", "1001010rrrrr0011", 1, AVR_ISA_1200, 0x9403}, {"lsr", "r", "1001010rrrrr0110", 1, AVR_ISA_1200, 0x9406}, {"neg", "r", "1001010rrrrr0001", 1, AVR_ISA_1200, 0x9401}, {"pop", "r", "1001000rrrrr1111", 1, AVR_ISA_2xxx, 0x900f}, {"push", "r", "1001001rrrrr1111", 1, AVR_ISA_2xxx, 0x920f}, {"ror", "r", "1001010rrrrr0111", 1, AVR_ISA_1200, 0x9407}, {"ser", "d", "11101111dddd1111", 1, AVR_ISA_1200, 0xef0f}, {"swap", "r", "1001010rrrrr0010", 1, AVR_ISA_1200, 0x9402}, {"bclr", "S", "100101001SSS1000", 1, AVR_ISA_1200, 0x9488}, {"bset", "S", "100101000SSS1000", 1, AVR_ISA_1200, 0x9408}, {"clc", "", "1001010010001000", 1, AVR_ISA_1200, 0x9488}, {"clh", "", "1001010011011000", 1, AVR_ISA_1200, 0x94d8}, {"cli", "", "1001010011111000", 1, AVR_ISA_1200, 0x94f8}, {"cln", "", "1001010010101000", 1, AVR_ISA_1200, 0x94a8}, {"cls", "", "1001010011001000", 1, AVR_ISA_1200, 0x94c8}, {"clt", "", "1001010011101000", 1, AVR_ISA_1200, 0x94e8}, {"clv", "", "1001010010111000", 1, AVR_ISA_1200, 0x94b8}, {"clz", "", "1001010010011000", 1, AVR_ISA_1200, 0x9498}, {"icall","", "1001010100001001", 1, AVR_ISA_2xxx, 0x9509}, {"ijmp", "", "1001010000001001", 1, AVR_ISA_2xxx, 0x9409}, {"lpm", "", "1001010111001000", 1, AVR_ISA_2xxx, 0x95c8}, {"nop", "", "0000000000000000", 1, AVR_ISA_1200, 0x0000}, {"ret", "", "1001010100001000", 1, AVR_ISA_1200, 0x9508}, {"reti", "", "1001010100011000", 1, AVR_ISA_1200, 0x9518}, {"sec", "", "1001010000001000", 1, AVR_ISA_1200, 0x9408}, {"seh", "", "1001010001011000", 1, AVR_ISA_1200, 0x9458}, {"sei", "", "1001010001111000", 1, AVR_ISA_1200, 0x9478}, {"sen", "", "1001010000101000", 1, AVR_ISA_1200, 0x9428}, {"ses", "", "1001010001001000", 1, AVR_ISA_1200, 0x9448}, {"set", "", "1001010001101000", 1, AVR_ISA_1200, 0x9468}, {"sev", "", "1001010000111000", 1, AVR_ISA_1200, 0x9438}, {"sez", "", "1001010000011000", 1, AVR_ISA_1200, 0x9418}, {"sleep","", "1001010110001000", 1, AVR_ISA_1200, 0x9588}, {"wdr", "", "1001010110101000", 1, AVR_ISA_1200, 0x95a8}, {"elpm", "", "1001010111011000", 1, AVR_ISA_MEGA_x03, 0x95d8}, {NULL, NULL, NULL, 0, 0, 0} }; #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 { 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_LO8_LDI, -BFD_RELOC_AVR_LO8_LDI_NEG, 0}, {"hhi8", -BFD_RELOC_AVR_HI8_LDI, -BFD_RELOC_AVR_HI8_LDI_NEG, 0}, }; /* 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; #define OPTION_MMCU (OPTION_MD_BASE + 1) struct option md_longopts[] = { {"mmcu", required_argument, NULL, 'm'}, {NULL, no_argument, NULL, 0} }; size_t md_longopts_size = sizeof(md_longopts); static inline char * skip_space (s) 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_start; char *op_end; int size = 0; /* Drop leading whitespace. */ from = skip_space (from); *to = 0; /* Find the op code end. */ for (op_start = 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 (fragp, seg) fragS *fragp; asection *seg; { abort (); return 0; } void md_show_usage (stream) FILE *stream; { fprintf (stream, _ ("AVR options:\n" " -mmcu=[avr-name] select microcontroller variant\n" " [avr-name] can be:\n" " avr1 - AT90S1200\n" " avr2 - AT90S2xxx, AT90S4xxx, AT90S85xx, ATtiny22\n" " avr3 - ATmega103 or ATmega603\n" " avr4 - ATmega161\n" " or immediate microcontroller name.\n")); } static void avr_set_arch (dummy) int dummy; { char * str; str = (char *)alloca (20); input_line_pointer = extract_word (input_line_pointer, str, 20); md_parse_option ('m', str); bfd_set_arch_mach (stdoutput, TARGET_ARCH, avr_mcu->mach); } int md_parse_option (c, arg) int c; char *arg; { char *t = alloca (strlen (arg) + 1); char *s = t; char *arg1 = arg; do *t = tolower (*arg1++); while (*t++); if (c == 'm') { int i; for (i = 0; mcu_types[i].name; ++i) if (strcmp (mcu_types[i].name, s) == 0) break; if (!mcu_types[i].name) as_fatal (_ ("unknown MCU: %s\n"), arg); if (avr_mcu == &default_mcu) avr_mcu = &mcu_types[i]; else as_fatal (_ ("redefinition of mcu type `%s'"), mcu_types[i].name); return 1; } return 0; } symbolS * md_undefined_symbol (name) char *name; { return 0; } /* Convert a string pointed to by input_line_pointer into a floating point constant of type `type', and store the appropriate bytes to `*litP'. The number of LITTLENUMS emitted is stored in `*sizeP'. Returns NULL if OK, or an error message otherwise. */ char * md_atof (type, litP, sizeP) int type; char *litP; int *sizeP; { int prec; LITTLENUM_TYPE words[4]; LITTLENUM_TYPE *wordP; char *t; switch (type) { case 'f': prec = 2; break; case 'd': prec = 4; break; default: *sizeP = 0; return _("bad call to md_atof"); } t = atof_ieee (input_line_pointer, type, words); if (t) input_line_pointer = t; *sizeP = prec * sizeof (LITTLENUM_TYPE); /* This loop outputs the LITTLENUMs in REVERSE order. */ for (wordP = words + prec - 1; prec--;) { md_number_to_chars (litP, (valueT) (*wordP--), sizeof (LITTLENUM_TYPE)); litP += sizeof (LITTLENUM_TYPE); } return NULL; } void md_convert_frag (abfd, sec, fragP) bfd *abfd; asection *sec; fragS *fragP; { abort (); } void md_begin () { 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 < sizeof (exp_mod) / sizeof (exp_mod[0]); ++i) hash_insert (avr_mod_hash, EXP_MOD_NAME(i), (void*)(i+10)); for (i = 0; i < 32; i++) { char buf[5]; sprintf (buf, "r%d", i); symbol_table_insert (symbol_new (buf, reg_section, i, &zero_address_frag)); sprintf (buf, "R%d", i); symbol_table_insert (symbol_new (buf, reg_section, i, &zero_address_frag)); } bfd_set_arch_mach (stdoutput, TARGET_ARCH, avr_mcu->mach); } static unsigned int avr_operands (opcode, line) 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; /* 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; } if (opcode->insn_size == 2) { bfd_putl32 ((bfd_vma)bin, frag); } else { bfd_putl16 ((bfd_vma)bin, frag); } *line = str; return bin; } static unsigned int avr_get_constant (str, max) char * str; unsigned 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) as_bad (_("number must be less than %d"), max+1); return ex.X_add_number; } static unsigned int avr_operand (opcode, where, op, line) struct avr_opcodes_s *opcode; int where; char *op; char **line; { unsigned int op_mask = 0; char *str = *line; expressionS op_expr; str = skip_space (str); switch (*op) { /* Any register operand. */ case 'w': case 'd': case 'r': { char r_name[256]; str = extract_word (str, r_name, sizeof (r_name)); parse_exp (r_name, &op_expr); if (op_expr.X_op == O_register) { op_mask = op_expr.X_add_number; if (op_mask <= 31) { if (*op == 'd') { if (op_mask < 16) as_bad (_ ("register number above 15 required")); op_mask -= 16; } if (*op == 'w') { op_mask -= 24; if (op_mask & 1 || op_mask > 6) as_bad (_ ("register r24,r26,r28 or r30 required")); op_mask >>= 1; } break; } } as_bad (_ ("register required")); } 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; } } 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++ == '+') { unsigned int x; x = avr_get_constant (str, 63); str = input_line_pointer; op_mask |= (x & 7) | ((x & (3 << 3)) << 7) | ((x & (1 << 5)) << 8); } } 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 '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 'K': { unsigned int x; x = avr_get_constant (str, 63); str = input_line_pointer; op_mask |= (x & 0xf) | ((x & 0x30) << 2); } 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': { unsigned int x; x = avr_get_constant (str, 63); str = input_line_pointer; op_mask |= (x & 0xf) | ((x & 0x30) << 5); } break; case 'p': { unsigned int x; x = avr_get_constant (str, 31); str = input_line_pointer; op_mask |= x << 3; } break; default: as_bad (_ ("unknown constraint `%c'"), *op); } *line = str; return op_mask; } /* 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 (seg, addr) asection *seg; valueT addr; { 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. */ long md_pcrel_from_section (fixp, sec) 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; } /* GAS will call this for each fixup. It should store the correct value in the object file. */ int md_apply_fix3 (fixp, valuep, seg) fixS *fixp; valueT *valuep; segT seg; { unsigned char *where; unsigned long insn; long value; if (fixp->fx_addsy == (symbolS *) NULL) { value = *valuep; fixp->fx_done = 1; } else if (fixp->fx_pcrel) { segT s = S_GET_SEGMENT (fixp->fx_addsy); if (fixp->fx_addsy && (s == seg || s == absolute_section)) { value = S_GET_VALUE (fixp->fx_addsy) + *valuep; fixp->fx_done = 1; } else value = *valuep; } else { value = fixp->fx_offset; if (fixp->fx_subsy != (symbolS *) NULL) { if (S_GET_SEGMENT (fixp->fx_subsy) == absolute_section) { value -= S_GET_VALUE (fixp->fx_subsy); fixp->fx_done = 1; } else { /* We don't actually support subtracting a symbol. */ as_bad_where (fixp->fx_file, fixp->fx_line, _ ("expression too complex")); } } } 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: 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 = 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. */ /* XXX AT90S8515 must have WRAP here. */ if (value < -2048 || value > 2047) { if (avr_mcu->mach == bfd_mach_avr2) { if (value > 2047) value -= 4096; else value += 4096; } else 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_putl16 ((bfd_vma) value, where); break; case BFD_RELOC_16: bfd_putl16 ((bfd_vma) value, where); break; case BFD_RELOC_AVR_16_PM: bfd_putl16 ((bfd_vma) (value>>1), where); break; case BFD_RELOC_AVR_LO8_LDI: bfd_putl16 ((bfd_vma) insn | LDI_IMMEDIATE (value), where); break; case -BFD_RELOC_AVR_LO8_LDI: bfd_putl16 ((bfd_vma) insn | LDI_IMMEDIATE (value >> 16), where); break; case BFD_RELOC_AVR_HI8_LDI: bfd_putl16 ((bfd_vma) insn | LDI_IMMEDIATE (value >> 8), where); break; case -BFD_RELOC_AVR_HI8_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_LO8_LDI_NEG: bfd_putl16 ((bfd_vma) insn | LDI_IMMEDIATE (-value >> 16), where); break; case BFD_RELOC_AVR_HI8_LDI_NEG: bfd_putl16 ((bfd_vma) insn | LDI_IMMEDIATE (-value >> 8), where); break; case -BFD_RELOC_AVR_HI8_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; default: as_fatal ( _("line %d: unknown relocation type: 0x%x"), fixp->fx_line, fixp->fx_r_type); break; } } else { switch (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; } fixp->fx_addnumber = value; } return 0; } /* A `BFD_ASSEMBLER' GAS will call this to generate a reloc. GAS will pass 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 (seg, fixp) asection *seg; fixS *fixp; { arelent *reloc; reloc = (arelent *) xmalloc (sizeof (arelent)); reloc->sym_ptr_ptr = (asymbol **) xmalloc (sizeof (asymbol *)); *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, _("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; reloc->addend = fixp->fx_offset; return reloc; } void md_assemble (str) char *str; { struct avr_opcodes_s * opcode; char op[11]; str = 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 == NULL) { as_bad (_ ("unknown opcode `%s'"), op); return; } if ((opcode->isa & avr_mcu->isa) != opcode->isa) as_bad (_ ("illegal opcode %s for mcu %s"), opcode->name, avr_mcu->name); /* 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; avr_operands (opcode, &str); if (*str) as_bad (_ ("garbage at end of line")); input_line_pointer = t; } } /* Parse ordinary expression. */ static char * parse_exp (s, op) 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 (exp) expressionS *exp; { char *str = input_line_pointer; char *tmp; char op[8]; int mod; tmp = str; str = extract_word (str, op, sizeof (op)); if (op[0]) { mod = (int) hash_find (avr_mod_hash, op); if (mod) { int closes = 0; mod -= 10; str = skip_space (str); if (*str == '(') { int neg_p = 0; ++str; if (strncmp ("pm(", str, 3) == 0 || strncmp ("-(pm(", str, 5) == 0) { if (HAVE_PM_P(mod)) { ++mod; ++closes; } else as_bad (_ ("illegal expression")); 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--); return neg_p ? EXP_MOD_NEG_RELOC (mod) : EXP_MOD_RELOC (mod); } } } input_line_pointer = tmp; expression (exp); return BFD_RELOC_AVR_LO8_LDI; } /* Flag to pass `pm' mode between `avr_parse_cons_expression' and `avr_cons_fix_new' */ static int exp_mod_pm = 0; /* Parse special CONS expression: pm (expression) which is used for addressing to a program memory. Relocation: BFD_RELOC_AVR_16_PM */ void avr_parse_cons_expression (exp, nbytes) expressionS *exp; int nbytes; { char * tmp; exp_mod_pm = 0; tmp = input_line_pointer = skip_space (input_line_pointer); if (nbytes == 2) { char * pm_name = "pm"; int len = strlen (pm_name); if (strncasecmp (input_line_pointer, pm_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); exp_mod_pm = 1; expression (exp); if (*input_line_pointer == ')') ++input_line_pointer; else { as_bad (_ ("`)' required")); exp_mod_pm = 0; } return; } input_line_pointer = tmp; } } expression (exp); } void avr_cons_fix_new(frag, where, nbytes, exp) fragS *frag; int where; int nbytes; expressionS *exp; { if (exp_mod_pm == 0) { 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 as_bad (_ ("illegal %srelocation size: %d"), "", nbytes); } else { if (nbytes == 2) fix_new_exp (frag, where, nbytes, exp, false, BFD_RELOC_AVR_16_PM); else as_bad (_ ("illegal %srelocation size: %d"), "`pm' ", nbytes); exp_mod_pm = 0; } }