| /* tc-msp430.c -- Assembler code for the Texas Instruments MSP430 |
| |
| Copyright (C) 2002-2021 Free Software Foundation, Inc. |
| Contributed by Dmitry Diky <diwil@mail.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 <limits.h> |
| #include "subsegs.h" |
| #include "opcode/msp430.h" |
| #include "safe-ctype.h" |
| #include "dwarf2dbg.h" |
| #include "elf/msp430.h" |
| #include "libiberty.h" |
| |
| /* We will disable polymorphs by default because it is dangerous. |
| The potential problem here is the following: assume we got the |
| following code: |
| |
| jump .l1 |
| nop |
| jump subroutine ; external symbol |
| .l1: |
| nop |
| ret |
| |
| In case of assembly time relaxation we'll get: |
| 0: jmp .l1 <.text +0x08> (reloc deleted) |
| 2: nop |
| 4: br subroutine |
| .l1: |
| 8: nop |
| 10: ret |
| |
| If the 'subroutine' is within +-1024 bytes range then linker |
| will produce: |
| 0: jmp .text +0x08 |
| 2: nop |
| 4: jmp subroutine |
| .l1: |
| 6: nop |
| 8: ret ; 'jmp .text +0x08' will land here. WRONG!!! |
| |
| The workaround is the following: |
| 1. Declare global var enable_polymorphs which set to 1 via option -mp. |
| 2. Declare global var enable_relax which set to 1 via option -mQ. |
| |
| If polymorphs are enabled, and relax isn't, treat all jumps as long jumps, |
| do not delete any relocs and leave them for linker. |
| |
| If relax is enabled, relax at assembly time and kill relocs as necessary. */ |
| |
| int msp430_enable_relax; |
| int msp430_enable_polys; |
| |
| /* GCC uses the some condition codes which we'll |
| implement as new polymorph instructions. |
| |
| COND EXPL SHORT JUMP LONG JUMP |
| =============================================== |
| eq == jeq jne +4; br lab |
| ne != jne jeq +4; br lab |
| |
| ltn honours no-overflow flag |
| ltn < jn jn +2; jmp +4; br lab |
| |
| lt < jl jge +4; br lab |
| ltu < jlo lhs +4; br lab |
| le <= see below |
| leu <= see below |
| |
| gt > see below |
| gtu > see below |
| ge >= jge jl +4; br lab |
| geu >= jhs jlo +4; br lab |
| =============================================== |
| |
| Therefore, new opcodes are (BranchEQ -> beq; and so on...) |
| beq,bne,blt,bltn,bltu,bge,bgeu |
| 'u' means unsigned compares |
| |
| Also, we add 'jump' instruction: |
| jump UNCOND -> jmp br lab |
| |
| They will have fmt == 4, and insn_opnumb == number of instruction. */ |
| |
| struct rcodes_s |
| { |
| const char * name; |
| int index; /* Corresponding insn_opnumb. */ |
| int sop; /* Opcode if jump length is short. */ |
| long lpos; /* Label position. */ |
| long lop0; /* Opcode 1 _word_ (16 bits). */ |
| long lop1; /* Opcode second word. */ |
| long lop2; /* Opcode third word. */ |
| }; |
| |
| #define MSP430_RLC(n,i,sop,o1) \ |
| {#n, i, sop, 2, (o1 + 2), 0x4010, 0} |
| |
| static struct rcodes_s msp430_rcodes[] = |
| { |
| MSP430_RLC (beq, 0, 0x2400, 0x2000), |
| MSP430_RLC (bne, 1, 0x2000, 0x2400), |
| MSP430_RLC (blt, 2, 0x3800, 0x3400), |
| MSP430_RLC (bltu, 3, 0x2800, 0x2c00), |
| MSP430_RLC (bge, 4, 0x3400, 0x3800), |
| MSP430_RLC (bgeu, 5, 0x2c00, 0x2800), |
| {"bltn", 6, 0x3000, 3, 0x3000 + 1, 0x3c00 + 2,0x4010}, |
| {"jump", 7, 0x3c00, 1, 0x4010, 0, 0}, |
| {0,0,0,0,0,0,0} |
| }; |
| |
| #undef MSP430_RLC |
| #define MSP430_RLC(n,i,sop,o1) \ |
| {#n, i, sop, 2, (o1 + 2), 0x0030, 0} |
| |
| static struct rcodes_s msp430x_rcodes[] = |
| { |
| MSP430_RLC (beq, 0, 0x2400, 0x2000), |
| MSP430_RLC (bne, 1, 0x2000, 0x2400), |
| MSP430_RLC (blt, 2, 0x3800, 0x3400), |
| MSP430_RLC (bltu, 3, 0x2800, 0x2c00), |
| MSP430_RLC (bge, 4, 0x3400, 0x3800), |
| MSP430_RLC (bgeu, 5, 0x2c00, 0x2800), |
| {"bltn", 6, 0x3000, 3, 0x0030 + 1, 0x3c00 + 2, 0x3000}, |
| {"jump", 7, 0x3c00, 1, 0x0030, 0, 0}, |
| {0,0,0,0,0,0,0} |
| }; |
| #undef MSP430_RLC |
| |
| /* More difficult than above and they have format 5. |
| |
| COND EXPL SHORT LONG |
| ================================================================= |
| gt > jeq +2; jge label jeq +6; jl +4; br label |
| gtu > jeq +2; jhs label jeq +6; jlo +4; br label |
| leu <= jeq label; jlo label jeq +2; jhs +4; br label |
| le <= jeq label; jl label jeq +2; jge +4; br label |
| ================================================================= */ |
| |
| struct hcodes_s |
| { |
| const char * name; |
| int index; /* Corresponding insn_opnumb. */ |
| int tlab; /* Number of labels in short mode. */ |
| int op0; /* Opcode for first word of short jump. */ |
| int op1; /* Opcode for second word of short jump. */ |
| int lop0; /* Opcodes for long jump mode. */ |
| int lop1; |
| int lop2; |
| }; |
| |
| static struct hcodes_s msp430_hcodes[] = |
| { |
| {"bgt", 0, 1, 0x2401, 0x3400, 0x2403, 0x3802, 0x4010 }, |
| {"bgtu", 1, 1, 0x2401, 0x2c00, 0x2403, 0x2802, 0x4010 }, |
| {"bleu", 2, 2, 0x2400, 0x2800, 0x2401, 0x2c02, 0x4010 }, |
| {"ble", 3, 2, 0x2400, 0x3800, 0x2401, 0x3402, 0x4010 }, |
| {0,0,0,0,0,0,0,0} |
| }; |
| |
| static struct hcodes_s msp430x_hcodes[] = |
| { |
| {"bgt", 0, 1, 0x2401, 0x3400, 0x2403, 0x3802, 0x0030 }, |
| {"bgtu", 1, 1, 0x2401, 0x2c00, 0x2403, 0x2802, 0x0030 }, |
| {"bleu", 2, 2, 0x2400, 0x2800, 0x2401, 0x2c02, 0x0030 }, |
| {"ble", 3, 2, 0x2400, 0x3800, 0x2401, 0x3402, 0x0030 }, |
| {0,0,0,0,0,0,0,0} |
| }; |
| |
| const char comment_chars[] = ";"; |
| const char line_comment_chars[] = "#"; |
| const char line_separator_chars[] = "{"; |
| const char EXP_CHARS[] = "eE"; |
| const char FLT_CHARS[] = "dD"; |
| |
| /* Handle long expressions. */ |
| extern LITTLENUM_TYPE generic_bignum[]; |
| |
| static htab_t msp430_hash; |
| |
| /* Relaxations. */ |
| #define STATE_UNCOND_BRANCH 1 /* jump */ |
| #define STATE_NOOV_BRANCH 3 /* bltn */ |
| #define STATE_SIMPLE_BRANCH 2 /* bne, beq, etc... */ |
| #define STATE_EMUL_BRANCH 4 |
| |
| #define CNRL 2 |
| #define CUBL 4 |
| #define CNOL 8 |
| #define CSBL 6 |
| #define CEBL 4 |
| |
| /* Length. */ |
| #define STATE_BITS10 1 /* Wild guess. short jump. */ |
| #define STATE_WORD 2 /* 2 bytes pc rel. addr. more. */ |
| #define STATE_UNDEF 3 /* Cannot handle this yet. convert to word mode. */ |
| |
| #define ENCODE_RELAX(what,length) (((what) << 2) + (length)) |
| #define RELAX_STATE(s) ((s) & 3) |
| #define RELAX_LEN(s) ((s) >> 2) |
| #define RELAX_NEXT(a,b) ENCODE_RELAX (a, b + 1) |
| |
| relax_typeS md_relax_table[] = |
| { |
| /* Unused. */ |
| {1, 1, 0, 0}, |
| {1, 1, 0, 0}, |
| {1, 1, 0, 0}, |
| {1, 1, 0, 0}, |
| |
| /* Unconditional jump. */ |
| {1, 1, 8, 5}, |
| {1024, -1024, CNRL, RELAX_NEXT (STATE_UNCOND_BRANCH, STATE_BITS10)}, /* state 10 bits displ */ |
| {0, 0, CUBL, RELAX_NEXT (STATE_UNCOND_BRANCH, STATE_WORD)}, /* state word */ |
| {1, 1, CUBL, 0}, /* state undef */ |
| |
| /* Simple branches. */ |
| {0, 0, 8, 9}, |
| {1024, -1024, CNRL, RELAX_NEXT (STATE_SIMPLE_BRANCH, STATE_BITS10)}, /* state 10 bits displ */ |
| {0, 0, CSBL, RELAX_NEXT (STATE_SIMPLE_BRANCH, STATE_WORD)}, /* state word */ |
| {1, 1, CSBL, 0}, |
| |
| /* blt no overflow branch. */ |
| {1, 1, 8, 13}, |
| {1024, -1024, CNRL, RELAX_NEXT (STATE_NOOV_BRANCH, STATE_BITS10)}, /* state 10 bits displ */ |
| {0, 0, CNOL, RELAX_NEXT (STATE_NOOV_BRANCH, STATE_WORD)}, /* state word */ |
| {1, 1, CNOL, 0}, |
| |
| /* Emulated branches. */ |
| {1, 1, 8, 17}, |
| {1020, -1020, CEBL, RELAX_NEXT (STATE_EMUL_BRANCH, STATE_BITS10)}, /* state 10 bits displ */ |
| {0, 0, CNOL, RELAX_NEXT (STATE_EMUL_BRANCH, STATE_WORD)}, /* state word */ |
| {1, 1, CNOL, 0} |
| }; |
| |
| |
| #define MAX_OP_LEN 4096 |
| |
| typedef enum msp_isa |
| { |
| MSP_ISA_430, |
| MSP_ISA_430X, |
| MSP_ISA_430Xv2 |
| } msp_isa; |
| |
| static enum msp_isa selected_isa = MSP_ISA_430Xv2; |
| |
| static inline bool |
| target_is_430x (void) |
| { |
| return selected_isa >= MSP_ISA_430X; |
| } |
| |
| static inline bool |
| target_is_430xv2 (void) |
| { |
| return selected_isa == MSP_ISA_430Xv2; |
| } |
| |
| /* Generate an absolute 16-bit relocation, for 430 (!extended_op) instructions |
| only. |
| For the 430X we generate a 430 relocation only for the case where part of an |
| expression is being extracted (e.g. #hi(EXP), #lo(EXP). Otherwise generate |
| a 430X relocation. |
| For the 430 we generate a relocation without assembler range checking |
| if we are handling an immediate value or a byte-width instruction. */ |
| |
| #undef CHECK_RELOC_MSP430 |
| #define CHECK_RELOC_MSP430(OP) \ |
| (target_is_430x () \ |
| ? ((OP).expp == MSP_EXPP_ALL \ |
| ? BFD_RELOC_MSP430X_ABS16 \ |
| : ((OP).vshift == 1 \ |
| ? BFD_RELOC_MSP430_ABS_HI16 : BFD_RELOC_16)) \ |
| : ((imm_op || byte_op) \ |
| ? BFD_RELOC_MSP430_16_BYTE : BFD_RELOC_MSP430_16)) |
| |
| /* Generate a 16-bit pc-relative relocation. |
| For the 430X we generate a relocation without linker range checking. |
| For the 430 we generate a relocation without assembler range checking |
| if we are handling an immediate value or a byte-width instruction. */ |
| #undef CHECK_RELOC_MSP430_PCREL |
| #define CHECK_RELOC_MSP430_PCREL \ |
| (target_is_430x () \ |
| ? BFD_RELOC_MSP430X_PCR16 \ |
| : (imm_op || byte_op) \ |
| ? BFD_RELOC_MSP430_16_PCREL_BYTE : BFD_RELOC_MSP430_16_PCREL) |
| |
| /* Profiling capability: |
| It is a performance hit to use gcc's profiling approach for this tiny target. |
| Even more -- jtag hardware facility does not perform any profiling functions. |
| However we've got gdb's built-in simulator where we can do anything. |
| Therefore my suggestion is: |
| |
| We define new section ".profiler" which holds all profiling information. |
| We define new pseudo operation .profiler which will instruct assembler to |
| add new profile entry to the object file. Profile should take place at the |
| present address. |
| |
| Pseudo-op format: |
| |
| .profiler flags,function_to_profile [, cycle_corrector, extra] |
| |
| where 'flags' is a combination of the following chars: |
| s - function Start |
| x - function eXit |
| i - function is in Init section |
| f - function is in Fini section |
| l - Library call |
| c - libC standard call |
| d - stack value Demand (saved at run-time in simulator) |
| I - Interrupt service routine |
| P - Prologue start |
| p - Prologue end |
| E - Epilogue start |
| e - Epilogue end |
| j - long Jump/ sjlj unwind |
| a - an Arbitrary code fragment |
| t - exTra parameter saved (constant value like frame size) |
| '""' optional: "sil" == sil |
| |
| function_to_profile - function's address |
| cycle_corrector - a value which should be added to the cycle |
| counter, zero if omitted |
| extra - some extra parameter, zero if omitted. |
| |
| For example: |
| ------------------------------ |
| .global fxx |
| .type fxx,@function |
| fxx: |
| .LFrameOffset_fxx=0x08 |
| .profiler "scdP", fxx ; function entry. |
| ; we also demand stack value to be displayed |
| push r11 |
| push r10 |
| push r9 |
| push r8 |
| .profiler "cdp",fxx,0, .LFrameOffset_fxx ; check stack value at this point |
| ; (this is a prologue end) |
| ; note, that spare var filled with the frame size |
| mov r15,r8 |
| .... |
| .profiler cdE,fxx ; check stack |
| pop r8 |
| pop r9 |
| pop r10 |
| pop r11 |
| .profiler xcde,fxx,3 ; exit adds 3 to the cycle counter |
| ret ; cause 'ret' insn takes 3 cycles |
| ------------------------------- |
| |
| This profiling approach does not produce any overhead and |
| absolutely harmless. |
| So, even profiled code can be uploaded to the MCU. */ |
| #define MSP430_PROFILER_FLAG_ENTRY 1 /* s */ |
| #define MSP430_PROFILER_FLAG_EXIT 2 /* x */ |
| #define MSP430_PROFILER_FLAG_INITSECT 4 /* i */ |
| #define MSP430_PROFILER_FLAG_FINISECT 8 /* f */ |
| #define MSP430_PROFILER_FLAG_LIBCALL 0x10 /* l */ |
| #define MSP430_PROFILER_FLAG_STDCALL 0x20 /* c */ |
| #define MSP430_PROFILER_FLAG_STACKDMD 0x40 /* d */ |
| #define MSP430_PROFILER_FLAG_ISR 0x80 /* I */ |
| #define MSP430_PROFILER_FLAG_PROLSTART 0x100 /* P */ |
| #define MSP430_PROFILER_FLAG_PROLEND 0x200 /* p */ |
| #define MSP430_PROFILER_FLAG_EPISTART 0x400 /* E */ |
| #define MSP430_PROFILER_FLAG_EPIEND 0x800 /* e */ |
| #define MSP430_PROFILER_FLAG_JUMP 0x1000 /* j */ |
| #define MSP430_PROFILER_FLAG_FRAGMENT 0x2000 /* a */ |
| #define MSP430_PROFILER_FLAG_EXTRA 0x4000 /* t */ |
| #define MSP430_PROFILER_FLAG_notyet 0x8000 /* ? */ |
| |
| static int |
| pow2value (int y) |
| { |
| int n = 0; |
| unsigned int x; |
| |
| x = y; |
| |
| if (!x) |
| return 1; |
| |
| for (; x; x = x >> 1) |
| if (x & 1) |
| n++; |
| |
| return n == 1; |
| } |
| |
| /* 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")); |
| |
| /* Our caller is likely to check that the entire expression was parsed. |
| If we have found a hex constant with an 'h' suffix, ilp will be left |
| pointing at the 'h', so skip it here. */ |
| if (input_line_pointer != NULL |
| && op->X_op == O_constant |
| && (*input_line_pointer == 'h' || *input_line_pointer == 'H')) |
| ++ input_line_pointer; |
| return input_line_pointer; |
| } |
| |
| |
| /* Delete spaces from s: X ( r 1 2) => X(r12). */ |
| |
| static void |
| del_spaces (char * s) |
| { |
| while (*s) |
| { |
| if (ISSPACE (*s)) |
| { |
| char *m = s + 1; |
| |
| while (ISSPACE (*m) && *m) |
| m++; |
| memmove (s, m, strlen (m) + 1); |
| } |
| else |
| s++; |
| } |
| } |
| |
| static inline char * |
| skip_space (char * s) |
| { |
| while (ISSPACE (*s)) |
| ++s; |
| return s; |
| } |
| |
| /* Extract one word from FROM and copy it to TO. Delimiters are ",;\n" */ |
| |
| static char * |
| extract_operand (char * from, char * to, int limit) |
| { |
| int size = 0; |
| |
| /* Drop leading whitespace. */ |
| from = skip_space (from); |
| |
| while (size < limit && *from) |
| { |
| *(to + size) = *from; |
| if (*from == ',' || *from == ';' || *from == '\n') |
| break; |
| from++; |
| size++; |
| } |
| |
| *(to + size) = 0; |
| del_spaces (to); |
| |
| from++; |
| |
| return from; |
| } |
| |
| static void |
| msp430_profiler (int dummy ATTRIBUTE_UNUSED) |
| { |
| char buffer[1024]; |
| char f[32]; |
| char * str = buffer; |
| char * flags = f; |
| int p_flags = 0; |
| char * halt; |
| int ops = 0; |
| int left; |
| char * s; |
| segT seg; |
| int subseg; |
| char * end = 0; |
| expressionS exp; |
| expressionS exp1; |
| |
| s = input_line_pointer; |
| end = input_line_pointer; |
| |
| while (*end && *end != '\n') |
| end++; |
| |
| while (*s && *s != '\n') |
| { |
| if (*s == ',') |
| ops++; |
| s++; |
| } |
| |
| left = 3 - ops; |
| |
| if (ops < 1) |
| { |
| as_bad (_(".profiler pseudo requires at least two operands.")); |
| input_line_pointer = end; |
| return; |
| } |
| |
| input_line_pointer = extract_operand (input_line_pointer, flags, 32); |
| |
| while (*flags) |
| { |
| switch (*flags) |
| { |
| case '"': |
| break; |
| case 'a': |
| p_flags |= MSP430_PROFILER_FLAG_FRAGMENT; |
| break; |
| case 'j': |
| p_flags |= MSP430_PROFILER_FLAG_JUMP; |
| break; |
| case 'P': |
| p_flags |= MSP430_PROFILER_FLAG_PROLSTART; |
| break; |
| case 'p': |
| p_flags |= MSP430_PROFILER_FLAG_PROLEND; |
| break; |
| case 'E': |
| p_flags |= MSP430_PROFILER_FLAG_EPISTART; |
| break; |
| case 'e': |
| p_flags |= MSP430_PROFILER_FLAG_EPIEND; |
| break; |
| case 's': |
| p_flags |= MSP430_PROFILER_FLAG_ENTRY; |
| break; |
| case 'x': |
| p_flags |= MSP430_PROFILER_FLAG_EXIT; |
| break; |
| case 'i': |
| p_flags |= MSP430_PROFILER_FLAG_INITSECT; |
| break; |
| case 'f': |
| p_flags |= MSP430_PROFILER_FLAG_FINISECT; |
| break; |
| case 'l': |
| p_flags |= MSP430_PROFILER_FLAG_LIBCALL; |
| break; |
| case 'c': |
| p_flags |= MSP430_PROFILER_FLAG_STDCALL; |
| break; |
| case 'd': |
| p_flags |= MSP430_PROFILER_FLAG_STACKDMD; |
| break; |
| case 'I': |
| p_flags |= MSP430_PROFILER_FLAG_ISR; |
| break; |
| case 't': |
| p_flags |= MSP430_PROFILER_FLAG_EXTRA; |
| break; |
| default: |
| as_warn (_("unknown profiling flag - ignored.")); |
| break; |
| } |
| flags++; |
| } |
| |
| if (p_flags |
| && ( ! pow2value (p_flags & ( MSP430_PROFILER_FLAG_ENTRY |
| | MSP430_PROFILER_FLAG_EXIT)) |
| || ! pow2value (p_flags & ( MSP430_PROFILER_FLAG_PROLSTART |
| | MSP430_PROFILER_FLAG_PROLEND |
| | MSP430_PROFILER_FLAG_EPISTART |
| | MSP430_PROFILER_FLAG_EPIEND)) |
| || ! pow2value (p_flags & ( MSP430_PROFILER_FLAG_INITSECT |
| | MSP430_PROFILER_FLAG_FINISECT)))) |
| { |
| as_bad (_("ambiguous flags combination - '.profiler' directive ignored.")); |
| input_line_pointer = end; |
| return; |
| } |
| |
| /* Generate temp symbol which denotes current location. */ |
| if (now_seg == absolute_section) /* Paranoia ? */ |
| { |
| exp1.X_op = O_constant; |
| exp1.X_add_number = abs_section_offset; |
| as_warn (_("profiling in absolute section?")); |
| } |
| else |
| { |
| exp1.X_op = O_symbol; |
| exp1.X_add_symbol = symbol_temp_new_now (); |
| exp1.X_add_number = 0; |
| } |
| |
| /* Generate a symbol which holds flags value. */ |
| exp.X_op = O_constant; |
| exp.X_add_number = p_flags; |
| |
| /* Save current section. */ |
| seg = now_seg; |
| subseg = now_subseg; |
| |
| /* Now go to .profiler section. */ |
| obj_elf_change_section (".profiler", SHT_PROGBITS, 0, 0, 0, 0, 0); |
| |
| /* Save flags. */ |
| emit_expr (& exp, 2); |
| |
| /* Save label value. */ |
| emit_expr (& exp1, 2); |
| |
| while (ops--) |
| { |
| /* Now get profiling info. */ |
| halt = extract_operand (input_line_pointer, str, 1024); |
| /* Process like ".word xxx" directive. */ |
| (void) parse_exp (str, & exp); |
| emit_expr (& exp, 2); |
| input_line_pointer = halt; |
| } |
| |
| /* Fill the rest with zeros. */ |
| exp.X_op = O_constant; |
| exp.X_add_number = 0; |
| while (left--) |
| emit_expr (& exp, 2); |
| |
| /* Return to current section. */ |
| subseg_set (seg, subseg); |
| } |
| |
| 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; |
| } |
| |
| #define OPTION_MMCU 'm' |
| #define OPTION_RELAX 'Q' |
| #define OPTION_POLYMORPHS 'P' |
| #define OPTION_LARGE 'l' |
| static bool large_model = false; |
| #define OPTION_NO_INTR_NOPS 'N' |
| #define OPTION_INTR_NOPS 'n' |
| static bool gen_interrupt_nops = false; |
| #define OPTION_WARN_INTR_NOPS 'y' |
| #define OPTION_NO_WARN_INTR_NOPS 'Y' |
| static bool warn_interrupt_nops = true; |
| #define OPTION_UNKNOWN_INTR_NOPS 'u' |
| #define OPTION_NO_UNKNOWN_INTR_NOPS 'U' |
| static bool do_unknown_interrupt_nops = true; |
| #define OPTION_MCPU 'c' |
| #define OPTION_DATA_REGION 'r' |
| static bool upper_data_region_in_use = false; |
| /* The default is to use the lower region only. */ |
| static bool lower_data_region_only = true; |
| |
| /* Deprecated option, silently ignore it for compatibility with GCC <= 10. */ |
| #define OPTION_MOVE_DATA 'd' |
| |
| enum |
| { |
| OPTION_SILICON_ERRATA = OPTION_MD_BASE, |
| OPTION_SILICON_ERRATA_WARN, |
| }; |
| |
| static unsigned int silicon_errata_fix = 0; |
| static unsigned int silicon_errata_warn = 0; |
| #define SILICON_ERRATA_CPU4 (1 << 0) |
| #define SILICON_ERRATA_CPU8 (1 << 1) |
| #define SILICON_ERRATA_CPU11 (1 << 2) |
| #define SILICON_ERRATA_CPU12 (1 << 3) |
| #define SILICON_ERRATA_CPU13 (1 << 4) |
| #define SILICON_ERRATA_CPU19 (1 << 5) |
| |
| static void |
| msp430_set_arch (int option) |
| { |
| char str[32]; /* 32 for good measure. */ |
| |
| input_line_pointer = extract_word (input_line_pointer, str, 32); |
| |
| md_parse_option (option, str); |
| bfd_set_arch_mach (stdoutput, TARGET_ARCH, |
| target_is_430x () ? bfd_mach_msp430x : bfd_mach_msp11); |
| } |
| |
| /* This is a copy of the same data structure found in gcc/config/msp430/msp430.c |
| Keep these two structures in sync. |
| The data in this structure has been extracted from version 1.194 of the |
| devices.csv file released by TI in September 2016. */ |
| |
| struct msp430_mcu_data |
| { |
| const char * name; |
| unsigned int revision; /* 0=> MSP430, 1=>MSP430X, 2=> MSP430Xv2. */ |
| unsigned int hwmpy; /* 0=>none, 1=>16-bit, 2=>16-bit w/sign extend, 4=>32-bit, 8=> 32-bit (5xx). */ |
| } |
| msp430_mcu_data [] = |
| { |
| { "cc430f5123",2,8 }, |
| { "cc430f5125",2,8 }, |
| { "cc430f5133",2,8 }, |
| { "cc430f5135",2,8 }, |
| { "cc430f5137",2,8 }, |
| { "cc430f5143",2,8 }, |
| { "cc430f5145",2,8 }, |
| { "cc430f5147",2,8 }, |
| { "cc430f6125",2,8 }, |
| { "cc430f6126",2,8 }, |
| { "cc430f6127",2,8 }, |
| { "cc430f6135",2,8 }, |
| { "cc430f6137",2,8 }, |
| { "cc430f6143",2,8 }, |
| { "cc430f6145",2,8 }, |
| { "cc430f6147",2,8 }, |
| { "msp430afe221",0,2 }, |
| { "msp430afe222",0,2 }, |
| { "msp430afe223",0,2 }, |
| { "msp430afe231",0,2 }, |
| { "msp430afe232",0,2 }, |
| { "msp430afe233",0,2 }, |
| { "msp430afe251",0,2 }, |
| { "msp430afe252",0,2 }, |
| { "msp430afe253",0,2 }, |
| { "msp430bt5190",2,8 }, |
| { "msp430c091",0,0 }, |
| { "msp430c092",0,0 }, |
| { "msp430c111",0,0 }, |
| { "msp430c1111",0,0 }, |
| { "msp430c112",0,0 }, |
| { "msp430c1121",0,0 }, |
| { "msp430c1331",0,0 }, |
| { "msp430c1351",0,0 }, |
| { "msp430c311s",0,0 }, |
| { "msp430c312",0,0 }, |
| { "msp430c313",0,0 }, |
| { "msp430c314",0,0 }, |
| { "msp430c315",0,0 }, |
| { "msp430c323",0,0 }, |
| { "msp430c325",0,0 }, |
| { "msp430c336",0,1 }, |
| { "msp430c337",0,1 }, |
| { "msp430c412",0,0 }, |
| { "msp430c413",0,0 }, |
| { "msp430cg4616",1,1 }, |
| { "msp430cg4617",1,1 }, |
| { "msp430cg4618",1,1 }, |
| { "msp430cg4619",1,1 }, |
| { "msp430e112",0,0 }, |
| { "msp430e313",0,0 }, |
| { "msp430e315",0,0 }, |
| { "msp430e325",0,0 }, |
| { "msp430e337",0,1 }, |
| { "msp430f110",0,0 }, |
| { "msp430f1101",0,0 }, |
| { "msp430f1101a",0,0 }, |
| { "msp430f1111",0,0 }, |
| { "msp430f1111a",0,0 }, |
| { "msp430f112",0,0 }, |
| { "msp430f1121",0,0 }, |
| { "msp430f1121a",0,0 }, |
| { "msp430f1122",0,0 }, |
| { "msp430f1132",0,0 }, |
| { "msp430f122",0,0 }, |
| { "msp430f1222",0,0 }, |
| { "msp430f123",0,0 }, |
| { "msp430f1232",0,0 }, |
| { "msp430f133",0,0 }, |
| { "msp430f135",0,0 }, |
| { "msp430f147",0,1 }, |
| { "msp430f1471",0,1 }, |
| { "msp430f148",0,1 }, |
| { "msp430f1481",0,1 }, |
| { "msp430f149",0,1 }, |
| { "msp430f1491",0,1 }, |
| { "msp430f155",0,0 }, |
| { "msp430f156",0,0 }, |
| { "msp430f157",0,0 }, |
| { "msp430f1610",0,1 }, |
| { "msp430f1611",0,1 }, |
| { "msp430f1612",0,1 }, |
| { "msp430f167",0,1 }, |
| { "msp430f168",0,1 }, |
| { "msp430f169",0,1 }, |
| { "msp430f2001",0,0 }, |
| { "msp430f2002",0,0 }, |
| { "msp430f2003",0,0 }, |
| { "msp430f2011",0,0 }, |
| { "msp430f2012",0,0 }, |
| { "msp430f2013",0,0 }, |
| { "msp430f2101",0,0 }, |
| { "msp430f2111",0,0 }, |
| { "msp430f2112",0,0 }, |
| { "msp430f2121",0,0 }, |
| { "msp430f2122",0,0 }, |
| { "msp430f2131",0,0 }, |
| { "msp430f2132",0,0 }, |
| { "msp430f2232",0,0 }, |
| { "msp430f2234",0,0 }, |
| { "msp430f2252",0,0 }, |
| { "msp430f2254",0,0 }, |
| { "msp430f2272",0,0 }, |
| { "msp430f2274",0,0 }, |
| { "msp430f233",0,2 }, |
| { "msp430f2330",0,2 }, |
| { "msp430f235",0,2 }, |
| { "msp430f2350",0,2 }, |
| { "msp430f2370",0,2 }, |
| { "msp430f2410",0,2 }, |
| { "msp430f2416",1,2 }, |
| { "msp430f2417",1,2 }, |
| { "msp430f2418",1,2 }, |
| { "msp430f2419",1,2 }, |
| { "msp430f247",0,2 }, |
| { "msp430f2471",0,2 }, |
| { "msp430f248",0,2 }, |
| { "msp430f2481",0,2 }, |
| { "msp430f249",0,2 }, |
| { "msp430f2491",0,2 }, |
| { "msp430f2616",1,2 }, |
| { "msp430f2617",1,2 }, |
| { "msp430f2618",1,2 }, |
| { "msp430f2619",1,2 }, |
| { "msp430f412",0,0 }, |
| { "msp430f413",0,0 }, |
| { "msp430f4132",0,0 }, |
| { "msp430f415",0,0 }, |
| { "msp430f4152",0,0 }, |
| { "msp430f417",0,0 }, |
| { "msp430f423",0,1 }, |
| { "msp430f423a",0,1 }, |
| { "msp430f425",0,1 }, |
| { "msp430f4250",0,0 }, |
| { "msp430f425a",0,1 }, |
| { "msp430f4260",0,0 }, |
| { "msp430f427",0,1 }, |
| { "msp430f4270",0,0 }, |
| { "msp430f427a",0,1 }, |
| { "msp430f435",0,0 }, |
| { "msp430f4351",0,0 }, |
| { "msp430f436",0,0 }, |
| { "msp430f4361",0,0 }, |
| { "msp430f437",0,0 }, |
| { "msp430f4371",0,0 }, |
| { "msp430f438",0,0 }, |
| { "msp430f439",0,0 }, |
| { "msp430f447",0,1 }, |
| { "msp430f448",0,1 }, |
| { "msp430f4481",0,1 }, |
| { "msp430f449",0,1 }, |
| { "msp430f4491",0,1 }, |
| { "msp430f4616",1,1 }, |
| { "msp430f46161",1,1 }, |
| { "msp430f4617",1,1 }, |
| { "msp430f46171",1,1 }, |
| { "msp430f4618",1,1 }, |
| { "msp430f46181",1,1 }, |
| { "msp430f4619",1,1 }, |
| { "msp430f46191",1,1 }, |
| { "msp430f47126",1,4 }, |
| { "msp430f47127",1,4 }, |
| { "msp430f47163",1,4 }, |
| { "msp430f47166",1,4 }, |
| { "msp430f47167",1,4 }, |
| { "msp430f47173",1,4 }, |
| { "msp430f47176",1,4 }, |
| { "msp430f47177",1,4 }, |
| { "msp430f47183",1,4 }, |
| { "msp430f47186",1,4 }, |
| { "msp430f47187",1,4 }, |
| { "msp430f47193",1,4 }, |
| { "msp430f47196",1,4 }, |
| { "msp430f47197",1,4 }, |
| { "msp430f477",0,0 }, |
| { "msp430f478",0,0 }, |
| { "msp430f4783",0,4 }, |
| { "msp430f4784",0,4 }, |
| { "msp430f479",0,0 }, |
| { "msp430f4793",0,4 }, |
| { "msp430f4794",0,4 }, |
| { "msp430f5131",2,8 }, |
| { "msp430f5132",2,8 }, |
| { "msp430f5151",2,8 }, |
| { "msp430f5152",2,8 }, |
| { "msp430f5171",2,8 }, |
| { "msp430f5172",2,8 }, |
| { "msp430f5212",2,8 }, |
| { "msp430f5213",2,8 }, |
| { "msp430f5214",2,8 }, |
| { "msp430f5217",2,8 }, |
| { "msp430f5218",2,8 }, |
| { "msp430f5219",2,8 }, |
| { "msp430f5222",2,8 }, |
| { "msp430f5223",2,8 }, |
| { "msp430f5224",2,8 }, |
| { "msp430f5227",2,8 }, |
| { "msp430f5228",2,8 }, |
| { "msp430f5229",2,8 }, |
| { "msp430f5232",2,8 }, |
| { "msp430f5234",2,8 }, |
| { "msp430f5237",2,8 }, |
| { "msp430f5239",2,8 }, |
| { "msp430f5242",2,8 }, |
| { "msp430f5244",2,8 }, |
| { "msp430f5247",2,8 }, |
| { "msp430f5249",2,8 }, |
| { "msp430f5252",2,8 }, |
| { "msp430f5253",2,8 }, |
| { "msp430f5254",2,8 }, |
| { "msp430f5255",2,8 }, |
| { "msp430f5256",2,8 }, |
| { "msp430f5257",2,8 }, |
| { "msp430f5258",2,8 }, |
| { "msp430f5259",2,8 }, |
| { "msp430f5304",2,8 }, |
| { "msp430f5308",2,8 }, |
| { "msp430f5309",2,8 }, |
| { "msp430f5310",2,8 }, |
| { "msp430f5324",2,8 }, |
| { "msp430f5325",2,8 }, |
| { "msp430f5326",2,8 }, |
| { "msp430f5327",2,8 }, |
| { "msp430f5328",2,8 }, |
| { "msp430f5329",2,8 }, |
| { "msp430f5333",2,8 }, |
| { "msp430f5335",2,8 }, |
| { "msp430f5336",2,8 }, |
| { "msp430f5338",2,8 }, |
| { "msp430f5340",2,8 }, |
| { "msp430f5341",2,8 }, |
| { "msp430f5342",2,8 }, |
| { "msp430f5358",2,8 }, |
| { "msp430f5359",2,8 }, |
| { "msp430f5418",2,8 }, |
| { "msp430f5418a",2,8 }, |
| { "msp430f5419",2,8 }, |
| { "msp430f5419a",2,8 }, |
| { "msp430f5435",2,8 }, |
| { "msp430f5435a",2,8 }, |
| { "msp430f5436",2,8 }, |
| { "msp430f5436a",2,8 }, |
| { "msp430f5437",2,8 }, |
| { "msp430f5437a",2,8 }, |
| { "msp430f5438",2,8 }, |
| { "msp430f5438a",2,8 }, |
| { "msp430f5500",2,8 }, |
| { "msp430f5501",2,8 }, |
| { "msp430f5502",2,8 }, |
| { "msp430f5503",2,8 }, |
| { "msp430f5504",2,8 }, |
| { "msp430f5505",2,8 }, |
| { "msp430f5506",2,8 }, |
| { "msp430f5507",2,8 }, |
| { "msp430f5508",2,8 }, |
| { "msp430f5509",2,8 }, |
| { "msp430f5510",2,8 }, |
| { "msp430f5513",2,8 }, |
| { "msp430f5514",2,8 }, |
| { "msp430f5515",2,8 }, |
| { "msp430f5517",2,8 }, |
| { "msp430f5519",2,8 }, |
| { "msp430f5521",2,8 }, |
| { "msp430f5522",2,8 }, |
| { "msp430f5524",2,8 }, |
| { "msp430f5525",2,8 }, |
| { "msp430f5526",2,8 }, |
| { "msp430f5527",2,8 }, |
| { "msp430f5528",2,8 }, |
| { "msp430f5529",2,8 }, |
| { "msp430f5630",2,8 }, |
| { "msp430f5631",2,8 }, |
| { "msp430f5632",2,8 }, |
| { "msp430f5633",2,8 }, |
| { "msp430f5634",2,8 }, |
| { "msp430f5635",2,8 }, |
| { "msp430f5636",2,8 }, |
| { "msp430f5637",2,8 }, |
| { "msp430f5638",2,8 }, |
| { "msp430f5658",2,8 }, |
| { "msp430f5659",2,8 }, |
| { "msp430f5xx_6xxgeneric",2,8 }, |
| { "msp430f6433",2,8 }, |
| { "msp430f6435",2,8 }, |
| { "msp430f6436",2,8 }, |
| { "msp430f6438",2,8 }, |
| { "msp430f6458",2,8 }, |
| { "msp430f6459",2,8 }, |
| { "msp430f6630",2,8 }, |
| { "msp430f6631",2,8 }, |
| { "msp430f6632",2,8 }, |
| { "msp430f6633",2,8 }, |
| { "msp430f6634",2,8 }, |
| { "msp430f6635",2,8 }, |
| { "msp430f6636",2,8 }, |
| { "msp430f6637",2,8 }, |
| { "msp430f6638",2,8 }, |
| { "msp430f6658",2,8 }, |
| { "msp430f6659",2,8 }, |
| { "msp430f6720",2,8 }, |
| { "msp430f6720a",2,8 }, |
| { "msp430f6721",2,8 }, |
| { "msp430f6721a",2,8 }, |
| { "msp430f6723",2,8 }, |
| { "msp430f6723a",2,8 }, |
| { "msp430f6724",2,8 }, |
| { "msp430f6724a",2,8 }, |
| { "msp430f6725",2,8 }, |
| { "msp430f6725a",2,8 }, |
| { "msp430f6726",2,8 }, |
| { "msp430f6726a",2,8 }, |
| { "msp430f6730",2,8 }, |
| { "msp430f6730a",2,8 }, |
| { "msp430f6731",2,8 }, |
| { "msp430f6731a",2,8 }, |
| { "msp430f6733",2,8 }, |
| { "msp430f6733a",2,8 }, |
| { "msp430f6734",2,8 }, |
| { "msp430f6734a",2,8 }, |
| { "msp430f6735",2,8 }, |
| { "msp430f6735a",2,8 }, |
| { "msp430f6736",2,8 }, |
| { "msp430f6736a",2,8 }, |
| { "msp430f6745",2,8 }, |
| { "msp430f67451",2,8 }, |
| { "msp430f67451a",2,8 }, |
| { "msp430f6745a",2,8 }, |
| { "msp430f6746",2,8 }, |
| { "msp430f67461",2,8 }, |
| { "msp430f67461a",2,8 }, |
| { "msp430f6746a",2,8 }, |
| { "msp430f6747",2,8 }, |
| { "msp430f67471",2,8 }, |
| { "msp430f67471a",2,8 }, |
| { "msp430f6747a",2,8 }, |
| { "msp430f6748",2,8 }, |
| { "msp430f67481",2,8 }, |
| { "msp430f67481a",2,8 }, |
| { "msp430f6748a",2,8 }, |
| { "msp430f6749",2,8 }, |
| { "msp430f67491",2,8 }, |
| { "msp430f67491a",2,8 }, |
| { "msp430f6749a",2,8 }, |
| { "msp430f67621",2,8 }, |
| { "msp430f67621a",2,8 }, |
| { "msp430f67641",2,8 }, |
| { "msp430f67641a",2,8 }, |
| { "msp430f6765",2,8 }, |
| { "msp430f67651",2,8 }, |
| { "msp430f67651a",2,8 }, |
| { "msp430f6765a",2,8 }, |
| { "msp430f6766",2,8 }, |
| { "msp430f67661",2,8 }, |
| { "msp430f67661a",2,8 }, |
| { "msp430f6766a",2,8 }, |
| { "msp430f6767",2,8 }, |
| { "msp430f67671",2,8 }, |
| { "msp430f67671a",2,8 }, |
| { "msp430f6767a",2,8 }, |
| { "msp430f6768",2,8 }, |
| { "msp430f67681",2,8 }, |
| { "msp430f67681a",2,8 }, |
| { "msp430f6768a",2,8 }, |
| { "msp430f6769",2,8 }, |
| { "msp430f67691",2,8 }, |
| { "msp430f67691a",2,8 }, |
| { "msp430f6769a",2,8 }, |
| { "msp430f6775",2,8 }, |
| { "msp430f67751",2,8 }, |
| { "msp430f67751a",2,8 }, |
| { "msp430f6775a",2,8 }, |
| { "msp430f6776",2,8 }, |
| { "msp430f67761",2,8 }, |
| { "msp430f67761a",2,8 }, |
| { "msp430f6776a",2,8 }, |
| { "msp430f6777",2,8 }, |
| { "msp430f67771",2,8 }, |
| { "msp430f67771a",2,8 }, |
| { "msp430f6777a",2,8 }, |
| { "msp430f6778",2,8 }, |
| { "msp430f67781",2,8 }, |
| { "msp430f67781a",2,8 }, |
| { "msp430f6778a",2,8 }, |
| { "msp430f6779",2,8 }, |
| { "msp430f67791",2,8 }, |
| { "msp430f67791a",2,8 }, |
| { "msp430f6779a",2,8 }, |
| { "msp430fe423",0,0 }, |
| { "msp430fe4232",0,0 }, |
| { "msp430fe423a",0,0 }, |
| { "msp430fe4242",0,0 }, |
| { "msp430fe425",0,0 }, |
| { "msp430fe4252",0,0 }, |
| { "msp430fe425a",0,0 }, |
| { "msp430fe427",0,0 }, |
| { "msp430fe4272",0,0 }, |
| { "msp430fe427a",0,0 }, |
| { "msp430fg4250",0,0 }, |
| { "msp430fg4260",0,0 }, |
| { "msp430fg4270",0,0 }, |
| { "msp430fg437",0,0 }, |
| { "msp430fg438",0,0 }, |
| { "msp430fg439",0,0 }, |
| { "msp430fg4616",1,1 }, |
| { "msp430fg4617",1,1 }, |
| { "msp430fg4618",1,1 }, |
| { "msp430fg4619",1,1 }, |
| { "msp430fg477",0,0 }, |
| { "msp430fg478",0,0 }, |
| { "msp430fg479",0,0 }, |
| { "msp430fg6425",2,8 }, |
| { "msp430fg6426",2,8 }, |
| { "msp430fg6625",2,8 }, |
| { "msp430fg6626",2,8 }, |
| { "msp430fr2032",2,0 }, |
| { "msp430fr2033",2,0 }, |
| { "msp430fr2110",2,0 }, |
| { "msp430fr2111",2,0 }, |
| { "msp430fr2310",2,0 }, |
| { "msp430fr2311",2,0 }, |
| { "msp430fr2433",2,8 }, |
| { "msp430fr2532",2,8 }, |
| { "msp430fr2533",2,8 }, |
| { "msp430fr2632",2,8 }, |
| { "msp430fr2633",2,8 }, |
| { "msp430fr2xx_4xxgeneric",2,8 }, |
| { "msp430fr4131",2,0 }, |
| { "msp430fr4132",2,0 }, |
| { "msp430fr4133",2,0 }, |
| { "msp430fr5720",2,8 }, |
| { "msp430fr5721",2,8 }, |
| { "msp430fr5722",2,8 }, |
| { "msp430fr5723",2,8 }, |
| { "msp430fr5724",2,8 }, |
| { "msp430fr5725",2,8 }, |
| { "msp430fr5726",2,8 }, |
| { "msp430fr5727",2,8 }, |
| { "msp430fr5728",2,8 }, |
| { "msp430fr5729",2,8 }, |
| { "msp430fr5730",2,8 }, |
| { "msp430fr5731",2,8 }, |
| { "msp430fr5732",2,8 }, |
| { "msp430fr5733",2,8 }, |
| { "msp430fr5734",2,8 }, |
| { "msp430fr5735",2,8 }, |
| { "msp430fr5736",2,8 }, |
| { "msp430fr5737",2,8 }, |
| { "msp430fr5738",2,8 }, |
| { "msp430fr5739",2,8 }, |
| { "msp430fr57xxgeneric",2,8 }, |
| { "msp430fr5847",2,8 }, |
| { "msp430fr58471",2,8 }, |
| { "msp430fr5848",2,8 }, |
| { "msp430fr5849",2,8 }, |
| { "msp430fr5857",2,8 }, |
| { "msp430fr5858",2,8 }, |
| { "msp430fr5859",2,8 }, |
| { "msp430fr5867",2,8 }, |
| { "msp430fr58671",2,8 }, |
| { "msp430fr5868",2,8 }, |
| { "msp430fr5869",2,8 }, |
| { "msp430fr5870",2,8 }, |
| { "msp430fr5872",2,8 }, |
| { "msp430fr58721",2,8 }, |
| { "msp430fr5887",2,8 }, |
| { "msp430fr5888",2,8 }, |
| { "msp430fr5889",2,8 }, |
| { "msp430fr58891",2,8 }, |
| { "msp430fr5922",2,8 }, |
| { "msp430fr59221",2,8 }, |
| { "msp430fr5947",2,8 }, |
| { "msp430fr59471",2,8 }, |
| { "msp430fr5948",2,8 }, |
| { "msp430fr5949",2,8 }, |
| { "msp430fr5957",2,8 }, |
| { "msp430fr5958",2,8 }, |
| { "msp430fr5959",2,8 }, |
| { "msp430fr5962",2,8 }, |
| { "msp430fr5964",2,8 }, |
| { "msp430fr5967",2,8 }, |
| { "msp430fr5968",2,8 }, |
| { "msp430fr5969",2,8 }, |
| { "msp430fr59691",2,8 }, |
| { "msp430fr5970",2,8 }, |
| { "msp430fr5972",2,8 }, |
| { "msp430fr59721",2,8 }, |
| { "msp430fr5986",2,8 }, |
| { "msp430fr5987",2,8 }, |
| { "msp430fr5988",2,8 }, |
| { "msp430fr5989",2,8 }, |
| { "msp430fr59891",2,8 }, |
| { "msp430fr5992",2,8 }, |
| { "msp430fr5994",2,8 }, |
| { "msp430fr59941",2,8 }, |
| { "msp430fr5xx_6xxgeneric",2,8 }, |
| { "msp430fr6820",2,8 }, |
| { "msp430fr6822",2,8 }, |
| { "msp430fr68221",2,8 }, |
| { "msp430fr6870",2,8 }, |
| { "msp430fr6872",2,8 }, |
| { "msp430fr68721",2,8 }, |
| { "msp430fr6877",2,8 }, |
| { "msp430fr6879",2,8 }, |
| { "msp430fr68791",2,8 }, |
| { "msp430fr6887",2,8 }, |
| { "msp430fr6888",2,8 }, |
| { "msp430fr6889",2,8 }, |
| { "msp430fr68891",2,8 }, |
| { "msp430fr6920",2,8 }, |
| { "msp430fr6922",2,8 }, |
| { "msp430fr69221",2,8 }, |
| { "msp430fr6927",2,8 }, |
| { "msp430fr69271",2,8 }, |
| { "msp430fr6928",2,8 }, |
| { "msp430fr6970",2,8 }, |
| { "msp430fr6972",2,8 }, |
| { "msp430fr69721",2,8 }, |
| { "msp430fr6977",2,8 }, |
| { "msp430fr6979",2,8 }, |
| { "msp430fr69791",2,8 }, |
| { "msp430fr6987",2,8 }, |
| { "msp430fr6988",2,8 }, |
| { "msp430fr6989",2,8 }, |
| { "msp430fr69891",2,8 }, |
| { "msp430fw423",0,0 }, |
| { "msp430fw425",0,0 }, |
| { "msp430fw427",0,0 }, |
| { "msp430fw428",0,0 }, |
| { "msp430fw429",0,0 }, |
| { "msp430g2001",0,0 }, |
| { "msp430g2101",0,0 }, |
| { "msp430g2102",0,0 }, |
| { "msp430g2111",0,0 }, |
| { "msp430g2112",0,0 }, |
| { "msp430g2113",0,0 }, |
| { "msp430g2121",0,0 }, |
| { "msp430g2131",0,0 }, |
| { "msp430g2132",0,0 }, |
| { "msp430g2152",0,0 }, |
| { "msp430g2153",0,0 }, |
| { "msp430g2201",0,0 }, |
| { "msp430g2202",0,0 }, |
| { "msp430g2203",0,0 }, |
| { "msp430g2210",0,0 }, |
| { "msp430g2211",0,0 }, |
| { "msp430g2212",0,0 }, |
| { "msp430g2213",0,0 }, |
| { "msp430g2221",0,0 }, |
| { "msp430g2230",0,0 }, |
| { "msp430g2231",0,0 }, |
| { "msp430g2232",0,0 }, |
| { "msp430g2233",0,0 }, |
| { "msp430g2252",0,0 }, |
| { "msp430g2253",0,0 }, |
| { "msp430g2302",0,0 }, |
| { "msp430g2303",0,0 }, |
| { "msp430g2312",0,0 }, |
| { "msp430g2313",0,0 }, |
| { "msp430g2332",0,0 }, |
| { "msp430g2333",0,0 }, |
| { "msp430g2352",0,0 }, |
| { "msp430g2353",0,0 }, |
| { "msp430g2402",0,0 }, |
| { "msp430g2403",0,0 }, |
| { "msp430g2412",0,0 }, |
| { "msp430g2413",0,0 }, |
| { "msp430g2432",0,0 }, |
| { "msp430g2433",0,0 }, |
| { "msp430g2444",0,0 }, |
| { "msp430g2452",0,0 }, |
| { "msp430g2453",0,0 }, |
| { "msp430g2513",0,0 }, |
| { "msp430g2533",0,0 }, |
| { "msp430g2544",0,0 }, |
| { "msp430g2553",0,0 }, |
| { "msp430g2744",0,0 }, |
| { "msp430g2755",0,0 }, |
| { "msp430g2855",0,0 }, |
| { "msp430g2955",0,0 }, |
| { "msp430i2020",0,2 }, |
| { "msp430i2021",0,2 }, |
| { "msp430i2030",0,2 }, |
| { "msp430i2031",0,2 }, |
| { "msp430i2040",0,2 }, |
| { "msp430i2041",0,2 }, |
| { "msp430i2xxgeneric",0,2 }, |
| { "msp430l092",0,0 }, |
| { "msp430p112",0,0 }, |
| { "msp430p313",0,0 }, |
| { "msp430p315",0,0 }, |
| { "msp430p315s",0,0 }, |
| { "msp430p325",0,0 }, |
| { "msp430p337",0,1 }, |
| { "msp430sl5438a",2,8 }, |
| { "msp430tch5e",0,0 }, |
| { "msp430xgeneric",2,8 }, |
| { "rf430f5144",2,8 }, |
| { "rf430f5155",2,8 }, |
| { "rf430f5175",2,8 }, |
| { "rf430frl152h",0,0 }, |
| { "rf430frl152h_rom",0,0 }, |
| { "rf430frl153h",0,0 }, |
| { "rf430frl153h_rom",0,0 }, |
| { "rf430frl154h",0,0 }, |
| { "rf430frl154h_rom",0,0 } |
| }; |
| |
| int |
| md_parse_option (int c, const char * arg) |
| { |
| switch (c) |
| { |
| case OPTION_SILICON_ERRATA: |
| case OPTION_SILICON_ERRATA_WARN: |
| { |
| signed int i; |
| const struct |
| { |
| const char * name; |
| unsigned int length; |
| unsigned int bitfield; |
| } erratas[] = |
| { |
| { STRING_COMMA_LEN ("cpu4"), SILICON_ERRATA_CPU4 }, |
| { STRING_COMMA_LEN ("cpu8"), SILICON_ERRATA_CPU8 }, |
| { STRING_COMMA_LEN ("cpu11"), SILICON_ERRATA_CPU11 }, |
| { STRING_COMMA_LEN ("cpu12"), SILICON_ERRATA_CPU12 }, |
| { STRING_COMMA_LEN ("cpu13"), SILICON_ERRATA_CPU13 }, |
| { STRING_COMMA_LEN ("cpu19"), SILICON_ERRATA_CPU19 }, |
| }; |
| |
| do |
| { |
| for (i = ARRAY_SIZE (erratas); i--;) |
| if (strncasecmp (arg, erratas[i].name, erratas[i].length) == 0) |
| { |
| if (c == OPTION_SILICON_ERRATA) |
| silicon_errata_fix |= erratas[i].bitfield; |
| else |
| silicon_errata_warn |= erratas[i].bitfield; |
| arg += erratas[i].length; |
| break; |
| } |
| if (i < 0) |
| { |
| as_warn (_("Unrecognised CPU errata name starting here: %s"), arg); |
| break; |
| } |
| if (*arg == 0) |
| break; |
| if (*arg != ',') |
| as_warn (_("Expecting comma after CPU errata name, not: %s"), arg); |
| else |
| arg ++; |
| } |
| while (*arg != 0); |
| } |
| return 1; |
| |
| case OPTION_MMCU: |
| if (arg == NULL) |
| as_fatal (_("MCU option requires a name\n")); |
| |
| if (strcasecmp ("msp430", arg) == 0) |
| selected_isa = MSP_ISA_430; |
| else if (strcasecmp ("msp430xv2", arg) == 0) |
| selected_isa = MSP_ISA_430Xv2; |
| else if (strcasecmp ("msp430x", arg) == 0) |
| selected_isa = MSP_ISA_430X; |
| else |
| { |
| int i; |
| |
| for (i = ARRAY_SIZE (msp430_mcu_data); i--;) |
| if (strcasecmp (msp430_mcu_data[i].name, arg) == 0) |
| { |
| switch (msp430_mcu_data[i].revision) |
| { |
| case 0: selected_isa = MSP_ISA_430; break; |
| case 1: selected_isa = MSP_ISA_430X; break; |
| case 2: selected_isa = MSP_ISA_430Xv2; break; |
| } |
| break; |
| } |
| } |
| /* It is not an error if we do not match the MCU name. */ |
| return 1; |
| |
| case OPTION_MCPU: |
| if (strcmp (arg, "430") == 0 |
| || strcasecmp (arg, "msp430") == 0) |
| selected_isa = MSP_ISA_430; |
| else if (strcasecmp (arg, "430x") == 0 |
| || strcasecmp (arg, "msp430x") == 0) |
| selected_isa = MSP_ISA_430X; |
| else if (strcasecmp (arg, "430xv2") == 0 |
| || strcasecmp (arg, "msp430xv2") == 0) |
| selected_isa = MSP_ISA_430Xv2; |
| else |
| as_fatal (_("unrecognised argument to -mcpu option '%s'"), arg); |
| return 1; |
| |
| case OPTION_RELAX: |
| msp430_enable_relax = 1; |
| return 1; |
| |
| case OPTION_POLYMORPHS: |
| msp430_enable_polys = 1; |
| return 1; |
| |
| case OPTION_LARGE: |
| large_model = true; |
| return 1; |
| |
| case OPTION_NO_INTR_NOPS: |
| gen_interrupt_nops = false; |
| return 1; |
| case OPTION_INTR_NOPS: |
| gen_interrupt_nops = true; |
| return 1; |
| |
| case OPTION_WARN_INTR_NOPS: |
| warn_interrupt_nops = true; |
| return 1; |
| case OPTION_NO_WARN_INTR_NOPS: |
| warn_interrupt_nops = false; |
| return 1; |
| |
| case OPTION_UNKNOWN_INTR_NOPS: |
| do_unknown_interrupt_nops = true; |
| return 1; |
| case OPTION_NO_UNKNOWN_INTR_NOPS: |
| do_unknown_interrupt_nops = false; |
| return 1; |
| |
| case OPTION_MOVE_DATA: |
| /* Silently ignored. */ |
| return 1; |
| |
| case OPTION_DATA_REGION: |
| if (strcmp (arg, "upper") == 0 |
| || strcmp (arg, "either") == 0) |
| upper_data_region_in_use = true; |
| if (strcmp (arg, "upper") == 0 |
| || strcmp (arg, "either") == 0 |
| /* With data-region=none, the compiler has generated code assuming |
| data could be in the upper region, but nothing has been explicitly |
| placed there. */ |
| || strcmp (arg, "none") == 0) |
| lower_data_region_only = false; |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| /* The intention here is to have the mere presence of these sections |
| cause the object to have a reference to a well-known symbol. This |
| reference pulls in the bits of the runtime (crt0) that initialize |
| these sections. Thus, for example, the startup code to call |
| memset() to initialize .bss will only be linked in when there is a |
| non-empty .bss section. Otherwise, the call would exist but have a |
| zero length parameter, which is a waste of memory and cycles. |
| |
| The code which initializes these sections should have a global |
| label for these symbols, and should be marked with KEEP() in the |
| linker script. */ |
| |
| static void |
| msp430_make_init_symbols (const char * name) |
| { |
| if (startswith (name, ".bss") |
| || startswith (name, ".lower.bss") |
| || startswith (name, ".either.bss") |
| || startswith (name, ".gnu.linkonce.b.")) |
| (void) symbol_find_or_make ("__crt0_init_bss"); |
| |
| if (startswith (name, ".data") |
| || startswith (name, ".lower.data") |
| || startswith (name, ".either.data") |
| || startswith (name, ".gnu.linkonce.d.")) |
| (void) symbol_find_or_make ("__crt0_movedata"); |
| /* Note - data assigned to the .either.data section may end up being |
| placed in the .upper.data section if the .lower.data section is |
| full. Hence the need to define the crt0 symbol. |
| The linker may create upper or either data sections, even when none exist |
| at the moment, so use the value of the data-region flag to determine if |
| the symbol is needed. */ |
| if (startswith (name, ".either.data") |
| || startswith (name, ".upper.data") |
| || upper_data_region_in_use) |
| (void) symbol_find_or_make ("__crt0_move_highdata"); |
| |
| /* See note about .either.data above. */ |
| if (startswith (name, ".upper.bss") |
| || startswith (name, ".either.bss") |
| || upper_data_region_in_use) |
| (void) symbol_find_or_make ("__crt0_init_highbss"); |
| |
| /* The following symbols are for the crt0 functions that run through |
| the different .*_array sections and call the functions placed there. |
| - init_array stores global static C++ constructors to run before main. |
| - preinit_array is not expected to ever be used for MSP430. |
| GCC only places initialization functions for runtime "sanitizers" |
| (i.e. {a,l,t,u}san) and "virtual table verification" in preinit_array. |
| - fini_array stores global static C++ destructors to run after calling |
| exit() or returning from main. |
| __crt0_run_array is required to actually call the functions in the above |
| arrays. */ |
| if (startswith (name, ".init_array")) |
| { |
| (void) symbol_find_or_make ("__crt0_run_init_array"); |
| (void) symbol_find_or_make ("__crt0_run_array"); |
| } |
| else if (startswith (name, ".preinit_array")) |
| { |
| (void) symbol_find_or_make ("__crt0_run_preinit_array"); |
| (void) symbol_find_or_make ("__crt0_run_array"); |
| } |
| else if (startswith (name, ".fini_array")) |
| { |
| (void) symbol_find_or_make ("__crt0_run_fini_array"); |
| (void) symbol_find_or_make ("__crt0_run_array"); |
| } |
| } |
| |
| static void |
| msp430_section (int arg) |
| { |
| char * saved_ilp = input_line_pointer; |
| const char * name = obj_elf_section_name (); |
| |
| msp430_make_init_symbols (name); |
| |
| input_line_pointer = saved_ilp; |
| obj_elf_section (arg); |
| } |
| |
| void |
| msp430_frob_section (asection *sec) |
| { |
| const char *name = sec->name; |
| |
| if (sec->size == 0) |
| return; |
| |
| msp430_make_init_symbols (name); |
| } |
| |
| static void |
| msp430_lcomm (int ignore ATTRIBUTE_UNUSED) |
| { |
| symbolS *symbolP = s_comm_internal (0, s_lcomm_internal); |
| |
| if (symbolP) |
| symbol_get_bfdsym (symbolP)->flags |= BSF_OBJECT; |
| (void) symbol_find_or_make ("__crt0_init_bss"); |
| } |
| |
| static void |
| msp430_comm (int needs_align) |
| { |
| s_comm_internal (needs_align, elf_common_parse); |
| (void) symbol_find_or_make ("__crt0_init_bss"); |
| } |
| |
| static void |
| msp430_refsym (int arg ATTRIBUTE_UNUSED) |
| { |
| char sym_name[1024]; |
| input_line_pointer = extract_word (input_line_pointer, sym_name, 1024); |
| |
| (void) symbol_find_or_make (sym_name); |
| } |
| |
| /* Handle a .mspabi_attribute or .gnu_attribute directive. |
| attr_type is 0 for .mspabi_attribute or 1 for .gnu_attribute. |
| This is only used for validating the attributes in the assembly file against |
| the options gas has been invoked with. If the attributes and options are |
| compatible then we add the attributes to the assembly file in |
| msp430_md_end. */ |
| static void |
| msp430_object_attribute (int attr_type) |
| { |
| char tag_name_s[32]; |
| char tag_value_s[32]; |
| int tag_name, tag_value; |
| /* First operand is the tag name, second is the tag value e.g. |
| ".mspabi_attribute 4, 2". */ |
| input_line_pointer = extract_operand (input_line_pointer, tag_name_s, 32); |
| input_line_pointer = extract_operand (input_line_pointer, tag_value_s, 32); |
| tag_name = atoi (tag_name_s); |
| tag_value = atoi (tag_value_s); |
| /* If the attribute directive is present, the tag_value should never be set |
| to 0. */ |
| if (tag_name == 0 || tag_value == 0) |
| as_bad (_("bad arguments \"%s\" and/or \"%s\" in %s directive"), |
| tag_name_s, tag_value_s, (attr_type ? ".gnu_attribute" |
| : ".mspabi_attribute")); |
| else if (attr_type == 0) |
| /* Handle .mspabi_attribute. */ |
| switch (tag_name) |
| { |
| case OFBA_MSPABI_Tag_ISA: |
| switch (tag_value) |
| { |
| case OFBA_MSPABI_Val_ISA_MSP430: |
| if (target_is_430x ()) |
| as_bad (_("file was compiled for the 430 ISA but the %s ISA is " |
| "selected"), (target_is_430xv2 () ? "430X" : "430Xv2")); |
| break; |
| case OFBA_MSPABI_Val_ISA_MSP430X: |
| if (!target_is_430x ()) |
| as_bad (_("file was compiled for the 430X ISA but the 430 ISA is " |
| "selected")); |
| break; |
| default: |
| as_bad (_("unknown MSPABI build attribute value '%d' for " |
| "OFBA_MSPABI_Tag_ISA(%d) in .mspabi_attribute directive"), |
| tag_value, OFBA_MSPABI_Tag_ISA); |
| break; |
| } |
| break; |
| case OFBA_MSPABI_Tag_Code_Model: |
| /* Fall through. */ |
| case OFBA_MSPABI_Tag_Data_Model: |
| /* FIXME: Might we want to set the memory model to large if the assembly |
| file has the large model attribute, but -ml has not been passed? */ |
| switch (tag_value) |
| { |
| case OFBA_MSPABI_Val_Code_Model_SMALL: |
| if (large_model) |
| as_bad (_("file was compiled for the small memory model, but the " |
| "large memory model is selected")); |
| break; |
| case OFBA_MSPABI_Val_Code_Model_LARGE: |
| if (!large_model) |
| as_bad (_("file was compiled for the large memory model, " |
| "but the small memory model is selected")); |
| break; |
| default: |
| as_bad (_("unknown MSPABI build attribute value '%d' for %s(%d) " |
| "in .mspabi_attribute directive"), tag_value, |
| (tag_name == OFBA_MSPABI_Tag_Code_Model |
| ? "OFBA_MSPABI_Tag_Code_Model" |
| : "OFBA_MSPABI_Tag_Data_Model"), |
| (tag_name == OFBA_MSPABI_Tag_Code_Model |
| ? OFBA_MSPABI_Tag_Code_Model |
| : OFBA_MSPABI_Tag_Data_Model)); |
| break; |
| } |
| break; |
| default: |
| as_bad (_("unknown MSPABI build attribute tag '%d' in " |
| ".mspabi_attribute directive"), tag_name); |
| break; |
| } |
| else if (attr_type == 1) |
| /* Handle .gnu_attribute. */ |
| switch (tag_name) |
| { |
| case Tag_GNU_MSP430_Data_Region: |
| /* This attribute is only applicable in the large memory model. */ |
| if (!large_model) |
| break; |
| switch (tag_value) |
| { |
| case Val_GNU_MSP430_Data_Region_Lower: |
| if (!lower_data_region_only) |
| as_bad (_("file was compiled assuming all data will be in the " |
| "lower memory region, but the upper region is in use")); |
| break; |
| case Val_GNU_MSP430_Data_Region_Any: |
| if (lower_data_region_only) |
| as_bad (_("file was compiled assuming data could be in the upper " |
| "memory region, but the lower data region is " |
| "exclusively in use")); |
| break; |
| default: |
| as_bad (_("unknown GNU build attribute value '%d' for " |
| "Tag_GNU_MSP430_Data_Region(%d) in .gnu_attribute " |
| "directive"), tag_value, Tag_GNU_MSP430_Data_Region); |
| } |
| } |
| else |
| as_bad (_("internal: unexpected argument '%d' to msp430_object_attribute"), |
| attr_type); |
| } |
| |
| const pseudo_typeS md_pseudo_table[] = |
| { |
| {"arch", msp430_set_arch, OPTION_MMCU}, |
| {"cpu", msp430_set_arch, OPTION_MCPU}, |
| {"profiler", msp430_profiler, 0}, |
| {"section", msp430_section, 0}, |
| {"section.s", msp430_section, 0}, |
| {"sect", msp430_section, 0}, |
| {"sect.s", msp430_section, 0}, |
| {"pushsection", msp430_section, 1}, |
| {"refsym", msp430_refsym, 0}, |
| {"comm", msp430_comm, 0}, |
| {"lcomm", msp430_lcomm, 0}, |
| {"mspabi_attribute", msp430_object_attribute, 0}, |
| {"gnu_attribute", msp430_object_attribute, 1}, |
| {NULL, NULL, 0} |
| }; |
| |
| const char *md_shortopts = "mm:,mP,mQ,ml,mN,mn,my,mY,mu,mU"; |
| |
| struct option md_longopts[] = |
| { |
| {"msilicon-errata", required_argument, NULL, OPTION_SILICON_ERRATA}, |
| {"msilicon-errata-warn", required_argument, NULL, OPTION_SILICON_ERRATA_WARN}, |
| {"mmcu", required_argument, NULL, OPTION_MMCU}, |
| {"mcpu", required_argument, NULL, OPTION_MCPU}, |
| {"mP", no_argument, NULL, OPTION_POLYMORPHS}, |
| {"mQ", no_argument, NULL, OPTION_RELAX}, |
| {"ml", no_argument, NULL, OPTION_LARGE}, |
| {"mN", no_argument, NULL, OPTION_NO_INTR_NOPS}, |
| {"mn", no_argument, NULL, OPTION_INTR_NOPS}, |
| {"mY", no_argument, NULL, OPTION_NO_WARN_INTR_NOPS}, |
| {"my", no_argument, NULL, OPTION_WARN_INTR_NOPS}, |
| {"mu", no_argument, NULL, OPTION_UNKNOWN_INTR_NOPS}, |
| {"mU", no_argument, NULL, OPTION_NO_UNKNOWN_INTR_NOPS}, |
| {"md", no_argument, NULL, OPTION_MOVE_DATA}, |
| {"mdata-region", required_argument, NULL, OPTION_DATA_REGION}, |
| {NULL, no_argument, NULL, 0} |
| }; |
| |
| size_t md_longopts_size = sizeof (md_longopts); |
| |
| void |
| md_show_usage (FILE * stream) |
| { |
| fprintf (stream, |
| _("MSP430 options:\n" |
| " -mmcu=<msp430-name> - select microcontroller type\n" |
| " -mcpu={430|430x|430xv2} - select microcontroller architecture\n")); |
| fprintf (stream, |
| _(" -msilicon-errata=<name>[,<name>...] - enable fixups for silicon errata\n" |
| " -msilicon-errata-warn=<name>[,<name>...] - warn when a fixup might be needed\n" |
| " supported errata names: cpu4, cpu8, cpu11, cpu12, cpu13, cpu19\n")); |
| fprintf (stream, |
| _(" -mQ - enable relaxation at assembly time. DANGEROUS!\n" |
| " -mP - enable polymorph instructions\n")); |
| fprintf (stream, |
| _(" -ml - enable large code model\n")); |
| fprintf (stream, |
| _(" -mN - do not insert NOPs after changing interrupts (default)\n")); |
| fprintf (stream, |
| _(" -mn - insert a NOP after changing interrupts\n")); |
| fprintf (stream, |
| _(" -mY - do not warn about missing NOPs after changing interrupts\n")); |
| fprintf (stream, |
| _(" -my - warn about missing NOPs after changing interrupts (default)\n")); |
| fprintf (stream, |
| _(" -mU - for an instruction which changes interrupt state, but where it is not\n" |
| " known how the state is changed, do not warn/insert NOPs\n")); |
| fprintf (stream, |
| _(" -mu - for an instruction which changes interrupt state, but where it is not\n" |
| " known how the state is changed, warn/insert NOPs (default)\n" |
| " -mn and/or -my are required for this to have any effect\n")); |
| fprintf (stream, |
| _(" -mdata-region={none|lower|upper|either} - select region data will be\n" |
| " placed in.\n")); |
| } |
| |
| symbolS * |
| md_undefined_symbol (char * name ATTRIBUTE_UNUSED) |
| { |
| return NULL; |
| } |
| |
| static char * |
| extract_cmd (char * from, char * to, int limit) |
| { |
| int size = 0; |
| |
| while (*from && ! ISSPACE (*from) && *from != '.' && limit > size) |
| { |
| *(to + size) = *from; |
| from++; |
| size++; |
| } |
| |
| *(to + size) = 0; |
| |
| return from; |
| } |
| |
| const char * |
| md_atof (int type, char * litP, int * sizeP) |
| { |
| return ieee_md_atof (type, litP, sizeP, false); |
| } |
| |
| void |
| md_begin (void) |
| { |
| struct msp430_opcode_s * opcode; |
| msp430_hash = str_htab_create (); |
| |
| for (opcode = msp430_opcodes; opcode->name; opcode++) |
| str_hash_insert (msp430_hash, opcode->name, opcode, 0); |
| |
| bfd_set_arch_mach (stdoutput, TARGET_ARCH, |
| target_is_430x () ? bfd_mach_msp430x : bfd_mach_msp11); |
| |
| /* Set linkrelax here to avoid fixups in most sections. */ |
| linkrelax = 1; |
| } |
| |
| static inline bool |
| is_regname_end (char c) |
| { |
| return (c == 0 || ! ISALNUM (c)); |
| } |
| |
| /* Returns the register number equivalent to the string T. |
| Returns -1 if there is no such register. |
| Skips a leading 'r' or 'R' character if there is one. |
| Handles the register aliases PC and SP. */ |
| |
| static signed int |
| check_reg (char * t) |
| { |
| char * endt; |
| signed long int val; |
| |
| if (t == NULL || t[0] == 0) |
| return -1; |
| |
| if (*t == 'r' || *t == 'R') |
| ++t; |
| |
| if (strncasecmp (t, "pc", 2) == 0 && is_regname_end (t[2])) |
| return 0; |
| |
| if (strncasecmp (t, "sp", 2) == 0 && is_regname_end (t[2])) |
| return 1; |
| |
| if (strncasecmp (t, "sr", 2) == 0 && is_regname_end (t[2])) |
| return 2; |
| |
| if (*t == '0' && is_regname_end (t[1])) |
| return 0; |
| |
| val = strtol (t, & endt, 0); |
| |
| if (val < 1 || val > 15) |
| return -1; |
| |
| if (is_regname_end (*endt)) |
| return val; |
| |
| return -1; |
| } |
| |
| static int |
| msp430_srcoperand (struct msp430_operand_s * op, |
| char * l, |
| int bin, |
| bool * imm_op, |
| bool allow_20bit_values, |
| bool constants_allowed) |
| { |
| char * end; |
| char *__tl = l; |
| |
| /* Check if an immediate #VALUE. The hash sign should be only at the beginning! */ |
| if (*l == '#') |
| { |
| char *h = l; |
| int vshift = -1; |
| int rval = 0; |
| /* Use all parts of the constant expression by default. */ |
| enum msp430_expp_e expp = MSP_EXPP_ALL; |
| |
| /* Check if there is: |
| llo(x) - least significant 16 bits, x &= 0xffff |
| lhi(x) - x = (x >> 16) & 0xffff, |
| hlo(x) - x = (x >> 32) & 0xffff, |
| hhi(x) - x = (x >> 48) & 0xffff |
| The value _MUST_ be an immediate expression: #hlo(1231231231). */ |
| |
| *imm_op = true; |
| |
| if (strncasecmp (h, "#llo(", 5) == 0) |
| { |
| vshift = 0; |
| rval = 3; |
| expp = MSP_EXPP_LLO; |
| } |
| else if (strncasecmp (h, "#lhi(", 5) == 0) |
| { |
| vshift = 1; |
| rval = 3; |
| expp = MSP_EXPP_LHI; |
| } |
| else if (strncasecmp (h, "#hlo(", 5) == 0) |
| { |
| vshift = 2; |
| rval = 3; |
| expp = MSP_EXPP_HLO; |
| } |
| else if (strncasecmp (h, "#hhi(", 5) == 0) |
| { |
| vshift = 3; |
| rval = 3; |
| expp = MSP_EXPP_HHI; |
| } |
| else if (strncasecmp (h, "#lo(", 4) == 0) |
| { |
| vshift = 0; |
| rval = 2; |
| expp = MSP_EXPP_LO; |
| } |
| else if (strncasecmp (h, "#hi(", 4) == 0) |
| { |
| vshift = 1; |
| rval = 2; |
| expp = MSP_EXPP_HI; |
| } |
| |
| op->reg = 0; /* Reg PC. */ |
| op->am = 3; |
| op->ol = 1; /* Immediate will follow an instruction. */ |
| __tl = h + 1 + rval; |
| op->mode = OP_EXP; |
| op->vshift = vshift; |
| op->expp = expp; |
| |
| end = parse_exp (__tl, &(op->exp)); |
| if (end != NULL && *end != 0 && *end != ')' ) |
| { |
| as_bad (_("extra characters '%s' at end of immediate expression '%s'"), end, l); |
| return 1; |
| } |
| if (op->exp.X_op == O_constant) |
| { |
| int x = op->exp.X_add_number; |
| |
| if (vshift == 0) |
| { |
| x = x & 0xffff; |
| op->exp.X_add_number = x; |
| } |
| else if (vshift == 1) |
| { |
| x = (x >> 16) & 0xffff; |
| op->exp.X_add_number = x; |
| op->vshift = 0; |
| } |
| else if (vshift > 1) |
| { |
| if (x < 0) |
| op->exp.X_add_number = -1; |
| else |
| op->exp.X_add_number = 0; /* Nothing left. */ |
| x = op->exp.X_add_number; |
| op->vshift = 0; |
| } |
| |
| if (allow_20bit_values) |
| { |
| if (op->exp.X_add_number > 0xfffff || op->exp.X_add_number < -524288) |
| { |
| as_bad (_("value 0x%x out of extended range."), x); |
| return 1; |
| } |
| } |
| else if (op->exp.X_add_number > 65535 || op->exp.X_add_number < -32768) |
| { |
| as_bad (_("value %d out of range. Use #lo() or #hi()"), x); |
| return 1; |
| } |
| |
| /* Now check constants. */ |
| /* Substitute register mode with a constant generator if applicable. */ |
| |
| if (!allow_20bit_values) |
| x = (short) x; /* Extend sign. */ |
| |
| if (! constants_allowed) |
| ; |
| else if (x == 0) |
| { |
| op->reg = 3; |
| op->am = 0; |
| op->ol = 0; |
| op->mode = OP_REG; |
| } |
| else if (x == 1) |
| { |
| op->reg = 3; |
| op->am = 1; |
| op->ol = 0; |
| op->mode = OP_REG; |
| } |
| else if (x == 2) |
| { |
| op->reg = 3; |
| op->am = 2; |
| op->ol = 0; |
| op->mode = OP_REG; |
| } |
| else if (x == -1) |
| { |
| op->reg = 3; |
| op->am = 3; |
| op->ol = 0; |
| op->mode = OP_REG; |
| } |
| else if (x == 4) |
| { |
| if (bin == 0x1200 && ! target_is_430x ()) |
| { |
| /* CPU4: The shorter form of PUSH #4 is not supported on MSP430. */ |
| if (silicon_errata_warn & SILICON_ERRATA_CPU4) |
| as_warn (_("cpu4: not converting PUSH #4 to shorter form")); |
| /* No need to check silicon_errata_fixes - this fix is always implemented. */ |
| } |
| else |
| { |
| op->reg = 2; |
| op->am = 2; |
| op->ol = 0; |
| op->mode = OP_REG; |
| } |
| } |
| else if (x == 8) |
| { |
| if (bin == 0x1200 && ! target_is_430x ()) |
| { |
| /* CPU4: The shorter form of PUSH #8 is not supported on MSP430. */ |
| if (silicon_errata_warn & SILICON_ERRATA_CPU4) |
| as_warn (_("cpu4: not converting PUSH #8 to shorter form")); |
| } |
| else |
| { |
| op->reg = 2; |
| op->am = 3; |
| op->ol = 0; |
| op->mode = OP_REG; |
| } |
| } |
| } |
| else if (op->exp.X_op == O_symbol) |
| { |
| if (vshift > 1) |
| as_bad (_("error: unsupported #foo() directive used on symbol")); |
| op->mode = OP_EXP; |
| } |
| else if (op->exp.X_op == O_big) |
| { |
| short x; |
| |
| if (vshift != -1) |
| { |
| op->exp.X_op = O_constant; |
| op->exp.X_add_number = 0xffff & generic_bignum[vshift]; |
| x = op->exp.X_add_number; |
| op->vshift = 0; |
| } |
| else |
| { |
| as_bad (_ |
| ("unknown expression in operand %s. Use #llo(), #lhi(), #hlo() or #hhi()"), |
| l); |
| return 1; |
| } |
| |
| if (x == 0) |
| { |
| op->reg = 3; |
| op->am = 0; |
| op->ol = 0; |
| op->mode = OP_REG; |
| } |
| else if (x == 1) |
| { |
| op->reg = 3; |
| op->am = 1; |
| op->ol = 0; |
| op->mode = OP_REG; |
| } |
| else if (x == 2) |
| { |
| op->reg = 3; |
| op->am = 2; |
| op->ol = 0; |
| op->mode = OP_REG; |
| } |
| else if (x == -1) |
| { |
| op->reg = 3; |
| op->am = 3; |
| op->ol = 0; |
| op->mode = OP_REG; |
| } |
| else if (x == 4) |
| { |
| op->reg = 2; |
| op->am = 2; |
| op->ol = 0; |
| op->mode = OP_REG; |
| } |
| else if (x == 8) |
| { |
| op->reg = 2; |
| op->am = 3; |
| op->ol = 0; |
| op->mode = OP_REG; |
| } |
| } |
| /* Redundant (yet) check. */ |
| else if (op->exp.X_op == O_register) |
| as_bad |
| (_("Registers cannot be used within immediate expression [%s]"), l); |
| else |
| as_bad (_("unknown operand %s"), l); |
| |
| return 0; |
| } |
| |
| /* Check if absolute &VALUE (assume that we can construct something like ((a&b)<<7 + 25). */ |
| if (*l == '&') |
| { |
| char *h = l; |
| |
| op->reg = 2; /* Reg 2 in absolute addr mode. */ |
| op->am = 1; /* Mode As == 01 bin. */ |
| op->ol = 1; /* Immediate value followed by instruction. */ |
| __tl = h + 1; |
| end = parse_exp (__tl, &(op->exp)); |
| if (end != NULL && *end != 0) |
| { |
| as_bad (_("extra characters '%s' at the end of absolute operand '%s'"), end, l); |
| return 1; |
| } |
| op->mode = OP_EXP; |
| op->vshift = 0; |
| op->expp = MSP_EXPP_ALL; |
| if (op->exp.X_op == O_constant) |
| { |
| int x = op->exp.X_add_number; |
| |
| if (allow_20bit_values) |
| { |
| if (x > 0xfffff || x < -(0x7ffff)) |
| { |
| as_bad (_("value 0x%x out of extended range."), x); |
| return 1; |
| } |
| } |
| else if (x > 65535 || x < -32768) |
| { |
| as_bad (_("value out of range: 0x%x"), x); |
| return 1; |
| } |
| } |
| else if (op->exp.X_op == O_symbol) |
| ; |
| else |
| { |
| /* Redundant (yet) check. */ |
| if (op->exp.X_op == O_register) |
| as_bad |
| (_("Registers cannot be used within absolute expression [%s]"), l); |
| else |
| as_bad (_("unknown expression in operand %s"), l); |
| return 1; |
| } |
| return 0; |
| } |
| |
| /* Check if indirect register mode @Rn / postincrement @Rn+. */ |
| if (*l == '@') |
| { |
| char *t = l; |
| char *m = strchr (l, '+'); |
| |
| if (t != l) |
| { |
| as_bad (_("unknown addressing mode %s"), l); |
| return 1; |
| } |
| |
| t++; |
| |
| if ((op->reg = check_reg (t)) == -1) |
| { |
| as_bad (_("Bad register name %s"), t); |
| return 1; |
| } |
| |
| op->mode = OP_REG; |
| op->am = m ? 3 : 2; |
| op->ol = 0; |
| |
| /* PC cannot be used in indirect addressing. */ |
| if (target_is_430xv2 () && op->reg == 0) |
| { |
| as_bad (_("cannot use indirect addressing with the PC")); |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| /* Check if register indexed X(Rn). */ |
| do |
| { |
| char *h = strrchr (l, '('); |
| char *m = strrchr (l, ')'); |
| char *t; |
| |
| *imm_op = true; |
| |
| if (!h) |
| break; |
| if (!m) |
| { |
| as_bad (_("')' required")); |
| return 1; |
| } |
| |
| t = h; |
| op->am = 1; |
| op->ol = 1; |
| |
| /* Extract a register. */ |
| if ((op->reg = check_reg (t + 1)) == -1) |
| { |
| as_bad (_ |
| ("unknown operator %s. Did you mean X(Rn) or #[hl][hl][oi](CONST) ?"), |
| l); |
| return 1; |
| } |
| |
| if (op->reg == 2) |
| { |
| as_bad (_("r2 should not be used in indexed addressing mode")); |
| return 1; |
| } |
| |
| /* Extract constant. */ |
| __tl = l; |
| *h = 0; |
| op->mode = OP_EXP; |
| op->vshift = 0; |
| op->expp = MSP_EXPP_ALL; |
| end = parse_exp (__tl, &(op->exp)); |
| if (end != NULL && *end != 0) |
| { |
| as_bad (_("extra characters '%s' at end of operand '%s'"), end, l); |
| return 1; |
| } |
| if (op->exp.X_op == O_constant) |
| { |
| int x = op->exp.X_add_number; |
| |
| if (allow_20bit_values) |
| { |
| if (x > 0xfffff || x < - (0x7ffff)) |
| { |
| as_bad (_("value 0x%x out of extended range."), x); |
| return 1; |
| } |
| } |
| else if (x > 65535 || x < -32768) |
| { |
| as_bad (_("value out of range: 0x%x"), x); |
| return 1; |
| } |
| |
| if (x == 0) |
| { |
| op->mode = OP_REG; |
| op->am = 2; |
| op->ol = 0; |
| return 0; |
| } |
| |
| if (op->reg == 1 && (x & 1)) |
| { |
| if (silicon_errata_fix & SILICON_ERRATA_CPU8) |
| as_bad (_("CPU8: Stack pointer accessed with an odd offset")); |
| else if (silicon_errata_warn & SILICON_ERRATA_CPU8) |
| as_warn (_("CPU8: Stack pointer accessed with an odd offset")); |
| } |
| } |
| else if (op->exp.X_op == O_symbol) |
| ; |
| else |
| { |
| /* Redundant (yet) check. */ |
| if (op->exp.X_op == O_register) |
| as_bad |
| (_("Registers cannot be used as a prefix of indexed expression [%s]"), l); |
| else |
| as_bad (_("unknown expression in operand %s"), l); |
| return 1; |
| } |
| |
| return 0; |
| } |
| while (0); |
| |
| /* Possibly register mode 'mov r1,r2'. */ |
| if ((op->reg = check_reg (l)) != -1) |
| { |
| op->mode = OP_REG; |
| op->am = 0; |
| op->ol = 0; |
| return 0; |
| } |
| |
| /* Symbolic mode 'mov a, b' == 'mov x(pc), y(pc)'. */ |
| op->mode = OP_EXP; |
| op->reg = 0; /* PC relative... be careful. */ |
| /* An expression starting with a minus sign is a constant, not an address. */ |
| op->am = (*l == '-' ? 3 : 1); |
| op->ol = 1; |
| op->vshift = 0; |
| op->expp = MSP_EXPP_ALL; |
| __tl = l; |
| end = parse_exp (__tl, &(op->exp)); |
| if (end != NULL && * end != 0) |
| { |
| as_bad (_("extra characters '%s' at end of operand '%s'"), end, l); |
| return 1; |
| } |
| return 0; |
| } |
| |
| |
| static int |
| msp430_dstoperand (struct msp430_operand_s * op, |
| char * l, |
| int bin, |
| bool allow_20bit_values, |
| bool constants_allowed) |
| { |
| bool dummy; |
| int ret = msp430_srcoperand (op, l, bin, & dummy, |
| allow_20bit_values, |
| constants_allowed); |
| |
| if (ret) |
| return ret; |
| |
| if (op->am == 2) |
| { |
| char *__tl = (char *) "0"; |
| |
| op->mode = OP_EXP; |
| op->am = 1; |
| op->ol = 1; |
| op->vshift = 0; |
| op->expp = MSP_EXPP_ALL; |
| (void) parse_exp (__tl, &(op->exp)); |
| |
| if (op->exp.X_op != O_constant || op->exp.X_add_number != 0) |
| { |
| as_bad (_("Internal bug. Try to use 0(r%d) instead of @r%d"), |
| op->reg, op->reg); |
| return 1; |
| } |
| return 0; |
| } |
| |
| if (op->am > 1) |
| { |
| as_bad (_ |
| ("this addressing mode is not applicable for destination operand")); |
| return 1; |
| } |
| return 0; |
| } |
| |
| /* Attempt to encode a MOVA instruction with the given operands. |
| Returns the length of the encoded instruction if successful |
| or 0 upon failure. If the encoding fails, an error message |
| will be returned if a pointer is provided. */ |
| |
| static int |
| try_encode_mova (bool imm_op, |
| int bin, |
| struct msp430_operand_s * op1, |
| struct msp430_operand_s * op2, |
| const char ** error_message_return) |
| { |
| short ZEROS = 0; |
| char *frag; |
| int where; |
| |
| /* Only a restricted subset of the normal MSP430 addressing modes |
| are supported here, so check for the ones that are allowed. */ |
| if (imm_op) |
| { |
| if (op1->mode == OP_EXP) |
| { |
| if (op2->mode != OP_REG) |
| { |
| if (error_message_return != NULL) |
| * error_message_return = _("expected register as second argument of %s"); |
| return 0; |
| } |
| |
| if (op1->am == 3) |
| { |
| /* MOVA #imm20, Rdst. */ |
| bin |= 0x80 | op2->reg; |
| frag = frag_more (4); |
| where = frag - frag_now->fr_literal; |
| if (op1->exp.X_op == O_constant) |
| { |
| bin |= ((op1->exp.X_add_number >> 16) & 0xf) << 8; |
| bfd_putl16 ((bfd_vma) bin, frag); |
| bfd_putl16 (op1->exp.X_add_number & 0xffff, frag + 2); |
| } |
| else |
| { |
| bfd_putl16 ((bfd_vma) bin, frag); |
| fix_new_exp (frag_now, where, 4, &(op1->exp), false, |
| BFD_RELOC_MSP430X_ABS20_ADR_SRC); |
| bfd_putl16 ((bfd_vma) ZEROS, frag + 2); |
| } |
| |
| return 4; |
| } |
| else if (op1->am == 1) |
| { |
| /* MOVA z16(Rsrc), Rdst. */ |
| bin |= 0x30 | (op1->reg << 8) | op2->reg; |
| frag = frag_more (4); |
| where = frag - frag_now->fr_literal; |
| bfd_putl16 ((bfd_vma) bin, frag); |
| if (op1->exp.X_op == O_constant) |
| { |
| if (op1->exp.X_add_number > 0xffff |
| || op1->exp.X_add_number < -(0x7fff)) |
| { |
| if (error_message_return != NULL) |
| * error_message_return = _("index value too big for %s"); |
| return 0; |
| } |
| bfd_putl16 (op1->exp.X_add_number & 0xffff, frag + 2); |
| } |
| else |
| { |
| bfd_putl16 ((bfd_vma) ZEROS, frag + 2); |
| fix_new_exp (frag_now, where + 2, 2, &(op1->exp), false, |
| op1->reg == 0 ? |
| BFD_RELOC_MSP430X_PCR16 : |
| BFD_RELOC_MSP430X_ABS16); |
| } |
| return 4; |
| } |
| |
| if (error_message_return != NULL) |
| * error_message_return = _("unexpected addressing mode for %s"); |
| return 0; |
| } |
| else if (op1->am == 0) |
| { |
| /* MOVA Rsrc, ... */ |
| if (op2->mode == OP_REG) |
| { |
| bin |= 0xc0 | (op1->reg << 8) | op2->reg; |
| frag = frag_more (2); |
| where = frag - frag_now->fr_literal; |
| bfd_putl16 ((bfd_vma) bin, frag); |
| return 2; |
| } |
| else if (op2->am == 1) |
| { |
| if (op2->reg == 2) |
| { |
| /* MOVA Rsrc, &abs20. */ |
| bin |= 0x60 | (op1->reg << 8); |
| frag = frag_more (4); |
| where = frag - frag_now->fr_literal; |
| if (op2->exp.X_op == O_constant) |
| { |
| bin |= (op2->exp.X_add_number >> 16) & 0xf; |
| bfd_putl16 ((bfd_vma) bin, frag); |
| bfd_putl16 (op2->exp.X_add_number & 0xffff, frag + 2); |
| } |
| else |
| { |
| bfd_putl16 ((bfd_vma) bin, frag); |
| bfd_putl16 ((bfd_vma) ZEROS, frag + 2); |
| fix_new_exp (frag_now, where, 4, &(op2->exp), false, |
| BFD_RELOC_MSP430X_ABS20_ADR_DST); |
| } |
| return 4; |
| } |
| |
| /* MOVA Rsrc, z16(Rdst). */ |
| bin |= 0x70 | (op1->reg << 8) | op2->reg; |
| frag = frag_more (4); |
| where = frag - frag_now->fr_literal; |
| bfd_putl16 ((bfd_vma) bin, frag); |
| if (op2->exp.X_op == O_constant) |
| { |
| if (op2->exp.X_add_number > 0xffff |
| || op2->exp.X_add_number < -(0x7fff)) |
| { |
| if (error_message_return != NULL) |
| * error_message_return = _("index value too big for %s"); |
| return 0; |
| } |
| bfd_putl16 (op2->exp.X_add_number & 0xffff, frag + 2); |
| } |
| else |
| { |
| bfd_putl16 ((bfd_vma) ZEROS, frag + 2); |
| fix_new_exp (frag_now, where + 2, 2, &(op2->exp), false, |
| op2->reg == 0 ? |
| BFD_RELOC_MSP430X_PCR16 : |
| BFD_RELOC_MSP430X_ABS16); |
| } |
| return 4; |
| } |
| |
| if (error_message_return != NULL) |
| * error_message_return = _("unexpected addressing mode for %s"); |
| return 0; |
| } |
| } |
| |
| /* imm_op == false. */ |
| |
| if (op1->reg == 2 && op1->am == 1 && op1->mode == OP_EXP) |
| { |
| /* MOVA &abs20, Rdst. */ |
| if (op2->mode != OP_REG) |
| { |
| if (error_message_return != NULL) |
| * error_message_return = _("expected register as second argument of %s"); |
| return 0; |
| } |
| |
| if (op2->reg == 2 || op2->reg == 3) |
| { |
| if (error_message_return != NULL) |
| * error_message_return = _("constant generator destination register found in %s"); |
| return 0; |
| } |
| |
| bin |= 0x20 | op2->reg; |
| frag = frag_more (4); |
| where = frag - frag_now->fr_literal; |
| if (op1->exp.X_op == O_constant) |
| { |
| bin |= ((op1->exp.X_add_number >> 16) & 0xf) << 8; |
| bfd_putl16 ((bfd_vma) bin, frag); |
| bfd_putl16 (op1->exp.X_add_number & 0xffff, frag + 2); |
| } |
| else |
| { |
| bfd_putl16 ((bfd_vma) bin, frag); |
| bfd_putl16 ((bfd_vma) ZEROS, frag + 2); |
| fix_new_exp (frag_now, where, 4, &(op1->exp), false, |
| BFD_RELOC_MSP430X_ABS20_ADR_SRC); |
| } |
| return 4; |
| } |
| else if (op1->mode == OP_REG) |
| { |
| if (op1->am == 3) |
| { |
| /* MOVA @Rsrc+, Rdst. */ |
| if (op2->mode != OP_REG) |
| { |
| if (error_message_return != NULL) |
| * error_message_return = _("expected register as second argument of %s"); |
| return 0; |
| } |
| |
| if (op2->reg == 2 || op2->reg == 3) |
| { |
| if (error_message_return != NULL) |
| * error_message_return = _("constant generator destination register found in %s"); |
| return 0; |
| } |
| |
| if (op1->reg == 2 || op1->reg == 3) |
| { |
| if (error_message_return != NULL) |
| * error_message_return = _("constant generator source register found in %s"); |
| return 0; |
| } |
| |
| bin |= 0x10 | (op1->reg << 8) | op2->reg; |
| frag = frag_more (2); |
| where = frag - frag_now->fr_literal; |
| bfd_putl16 ((bfd_vma) bin, frag); |
| return 2; |
| } |
| else if (op1->am == 2) |
| { |
| /* MOVA @Rsrc,Rdst */ |
| if (op2->mode != OP_REG) |
| { |
| if (error_message_return != NULL) |
| * error_message_return = _("expected register as second argument of %s"); |
| return 0; |
| } |
| |
| if (op2->reg == 2 || op2->reg == 3) |
| { |
| if (error_message_return != NULL) |
| * error_message_return = _("constant generator destination register found in %s"); |
| return 0; |
| } |
| |
| if (op1->reg == 2 || op1->reg == 3) |
| { |
| if (error_message_return != NULL) |
| * error_message_return = _("constant generator source register found in %s"); |
| return 0; |
| } |
| |
| bin |= (op1->reg << 8) | op2->reg; |
| frag = frag_more (2); |
| where = frag - frag_now->fr_literal; |
| bfd_putl16 ((bfd_vma) bin, frag); |
| return 2; |
| } |
| } |
| |
| if (error_message_return != NULL) |
| * error_message_return = _("unexpected addressing mode for %s"); |
| |
| return 0; |
| } |
| |
| #define NOP_CHECK_INTERRUPT (1 << 0) |
| #define NOP_CHECK_CPU12 (1 << 1) |
| #define NOP_CHECK_CPU19 (1 << 2) |
| |
| static signed int check_for_nop = 0; |
| |
| #define is_opcode(NAME) (strcmp (opcode->name, NAME) == 0) |
| |
| /* is_{e,d}int only check the explicit enabling/disabling of interrupts. |
| For MOV insns, more sophisticated processing is needed to determine if they |
| result in enabling/disabling interrupts. */ |
| #define is_dint(OPCODE, BIN) ((strcmp (OPCODE, "dint") == 0) \ |
| || ((strcmp (OPCODE, "bic") == 0) \ |
| && BIN == 0xc232) \ |
| || ((strcmp (OPCODE, "clr") == 0) \ |
| && BIN == 0x4302)) |
| |
| #define is_eint(OPCODE, BIN) ((strcmp (OPCODE, "eint") == 0) \ |
| || ((strcmp (OPCODE, "bis") == 0) \ |
| && BIN == 0xd232)) |
| |
| const char * const INSERT_NOP_BEFORE_EINT = "NOP inserted here, before an interrupt enable instruction"; |
| const char * const INSERT_NOP_AFTER_DINT = "NOP inserted here, after an interrupt disable instruction"; |
| const char * const INSERT_NOP_AFTER_EINT = "NOP inserted here, after an interrupt enable instruction"; |
| const char * const INSERT_NOP_BEFORE_UNKNOWN = "NOP inserted here, before this interrupt state change"; |
| const char * const INSERT_NOP_AFTER_UNKNOWN ="NOP inserted here, after the instruction that changed interrupt state"; |
| const char * const INSERT_NOP_AT_EOF = "NOP inserted after the interrupt state change at the end of the file"; |
| |
| const char * const WARN_NOP_BEFORE_EINT = "a NOP might be needed here, before an interrupt enable instruction"; |
| const char * const WARN_NOP_AFTER_DINT = "a NOP might be needed here, after an interrupt disable instruction"; |
| const char * const WARN_NOP_AFTER_EINT = "a NOP might be needed here, after an interrupt enable instruction"; |
| const char * const WARN_NOP_BEFORE_UNKNOWN = "a NOP might be needed here, before this interrupt state change"; |
| const char * const WARN_NOP_AFTER_UNKNOWN = "a NOP might also be needed here, after the instruction that changed interrupt state"; |
| const char * const WARN_NOP_AT_EOF = "a NOP might be needed after the interrupt state change at the end of the file"; |
| |
| static void |
| gen_nop (void) |
| { |
| char *frag; |
| frag = frag_more (2); |
| bfd_putl16 ((bfd_vma) 0x4303 /* NOP */, frag); |
| dwarf2_emit_insn (2); |
| } |
| |
| /* Insert/inform about adding a NOP if this insn enables interrupts. */ |
| |
| static void |
| warn_eint_nop (bool prev_insn_is_nop, bool prev_insn_is_dint) |
| { |
| if (prev_insn_is_nop |
| /* If the last insn was a DINT, we will have already warned that a NOP is |
| required after it. */ |
| || prev_insn_is_dint |
| /* 430 ISA does not require a NOP before EINT. */ |
| || (! target_is_430x ())) |
| return; |
| |
| if (gen_interrupt_nops) |
| { |
| gen_nop (); |
| if (warn_interrupt_nops) |
| as_warn (_(INSERT_NOP_BEFORE_EINT)); |
| } |
| else if (warn_interrupt_nops) |
| as_warn (_(WARN_NOP_BEFORE_EINT)); |
| } |
| |
| /* Use when unsure what effect the insn will have on the interrupt status, |
| to insert/warn about adding a NOP before the current insn. */ |
| |
| static void |
| warn_unsure_interrupt (bool prev_insn_is_nop, |
| bool prev_insn_is_dint) |
| { |
| if (prev_insn_is_nop |
| /* If the last insn was a DINT, we will have already warned that a NOP is |
| required after it. */ |
| || prev_insn_is_dint |
| /* 430 ISA does not require a NOP before EINT or DINT. */ |
| || (! target_is_430x ())) |
| return; |
| |
| if (gen_interrupt_nops) |
| { |
| gen_nop (); |
| if (warn_interrupt_nops) |
| as_warn (_(INSERT_NOP_BEFORE_UNKNOWN)); |
| } |
| else if (warn_interrupt_nops) |
| as_warn (_(WARN_NOP_BEFORE_UNKNOWN)); |
| } |
| |
| /* Parse instruction operands. |
| Return binary opcode. */ |
| |
| static unsigned int |
| msp430_operands (struct msp430_opcode_s * opcode, char * line) |
| { |
| int bin = opcode->bin_opcode; /* Opcode mask. */ |
| int insn_length = 0; |
| char l1[MAX_OP_LEN], l2[MAX_OP_LEN]; |
| char *frag; |
| char *end; |
| int where; |
| struct msp430_operand_s op1, op2; |
| int res = 0; |
| static short ZEROS = 0; |
| bool byte_op, imm_op; |
| int op_length = 0; |
| int fmt; |
| int extended = 0x1800; |
| bool extended_op = false; |
| bool addr_op; |
| const char * error_message; |
| static signed int repeat_count = 0; |
| static bool prev_insn_is_nop = false; |
| static bool prev_insn_is_dint = false; |
| static bool prev_insn_is_eint = false; |
| /* We might decide before the end of the function that the current insn is |
| equivalent to DINT/EINT. */ |
| bool this_insn_is_dint = false; |
| bool this_insn_is_eint = false; |
| bool fix_emitted; |
| |
| /* Opcode is the one from opcodes table |
| line contains something like |
| [.w] @r2+, 5(R1) |
| or |
| .b @r2+, 5(R1). */ |
| |
| byte_op = false; |
| addr_op = false; |
| if (*line == '.') |
| { |
| bool check = false; |
| ++ line; |
| |
| switch (TOLOWER (* line)) |
| { |
| case 'b': |
| /* Byte operation. */ |
| bin |= BYTE_OPERATION; |
| byte_op = true; |
| check = true; |
| break; |
| |
| case 'a': |
| /* "Address" ops work on 20-bit values. */ |
| addr_op = true; |
| bin |= BYTE_OPERATION; |
| check = true; |
| break; |
| |
| case 'w': |
| /* Word operation - this is the default. */ |
| check = true; |
| break; |
| |
| case 0: |
| case ' ': |
| case '\n': |
| case '\r': |
| as_warn (_("no size modifier after period, .w assumed")); |
| break; |
| |
| default: |
| as_bad (_("unrecognised instruction size modifier .%c"), |
|