/* Configurable Xtensa ISA support. Copyright 2003 Free Software Foundation, Inc. This file is part of BFD, the Binary File Descriptor library. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <string.h> #include "xtensa-isa.h" #include "xtensa-isa-internal.h" xtensa_isa xtensa_default_isa = NULL; static int opname_lookup_compare (const void *v1, const void *v2) { opname_lookup_entry *e1 = (opname_lookup_entry *)v1; opname_lookup_entry *e2 = (opname_lookup_entry *)v2; return strcmp (e1->key, e2->key); } xtensa_isa xtensa_isa_init (void) { xtensa_isa isa; int mod; isa = xtensa_load_isa (0); if (isa == 0) { fprintf (stderr, "Failed to initialize Xtensa base ISA module\n"); return NULL; } for (mod = 1; xtensa_isa_modules[mod].get_num_opcodes_fn; mod++) { if (!xtensa_extend_isa (isa, mod)) { fprintf (stderr, "Failed to initialize Xtensa TIE ISA module\n"); return NULL; } } return isa; } /* ISA information. */ static int xtensa_check_isa_config (xtensa_isa_internal *isa, struct config_struct *config_table) { int i, j; if (!config_table) { fprintf (stderr, "Error: Empty configuration table in ISA DLL\n"); return 0; } /* For the first module, save a pointer to the table and record the specified endianness and availability of the density option. */ if (isa->num_modules == 0) { int found_memory_order = 0; isa->config = config_table; isa->has_density = 1; /* Default to have density option. */ for (i = 0; config_table[i].param_name; i++) { if (!strcmp (config_table[i].param_name, "IsaMemoryOrder")) { isa->is_big_endian = (strcmp (config_table[i].param_value, "BigEndian") == 0); found_memory_order = 1; } if (!strcmp (config_table[i].param_name, "IsaUseDensityInstruction")) { isa->has_density = atoi (config_table[i].param_value); } } if (!found_memory_order) { fprintf (stderr, "Error: \"IsaMemoryOrder\" missing from " "configuration table in ISA DLL\n"); return 0; } return 1; } /* For subsequent modules, check that the parameters match. Note: This code is sufficient to handle the current model where there are never more than 2 modules; we might at some point want to handle cases where module N > 0 specifies some parameters not included in the base table, and we would then add those to isa->config so that subsequent modules would check against them. */ for (i = 0; config_table[i].param_name; i++) { for (j = 0; isa->config[j].param_name; j++) { if (!strcmp (config_table[i].param_name, isa->config[j].param_name)) { int mismatch; if (!strcmp (config_table[i].param_name, "IsaCoprocessorCount")) { /* Only require the coprocessor count to be <= the base. */ int tiecnt = atoi (config_table[i].param_value); int basecnt = atoi (isa->config[j].param_value); mismatch = (tiecnt > basecnt); } else mismatch = strcmp (config_table[i].param_value, isa->config[j].param_value); if (mismatch) { #define MISMATCH_MESSAGE \ "Error: Configuration mismatch in the \"%s\" parameter:\n\ the configuration used when the TIE file was compiled had a value of\n\ \"%s\", while the current configuration has a value of\n\ \"%s\". Please rerun the TIE compiler with a matching\n\ configuration.\n" fprintf (stderr, MISMATCH_MESSAGE, config_table[i].param_name, config_table[i].param_value, isa->config[j].param_value); return 0; } break; } } } return 1; } static int xtensa_add_isa (xtensa_isa_internal *isa, libisa_module_specifier libisa) { int (*get_num_opcodes_fn) (void); struct config_struct *(*get_config_table_fn) (void); xtensa_opcode_internal **(*get_opcodes_fn) (void); int (*decode_insn_fn) (const xtensa_insnbuf); xtensa_opcode_internal **opcodes; int opc, insn_size, prev_num_opcodes, new_num_opcodes, this_module; get_num_opcodes_fn = xtensa_isa_modules[libisa].get_num_opcodes_fn; get_opcodes_fn = xtensa_isa_modules[libisa].get_opcodes_fn; decode_insn_fn = xtensa_isa_modules[libisa].decode_insn_fn; get_config_table_fn = xtensa_isa_modules[libisa].get_config_table_fn; if (!get_num_opcodes_fn || !get_opcodes_fn || !decode_insn_fn || (!get_config_table_fn && isa->num_modules == 0)) return 0; if (get_config_table_fn && !xtensa_check_isa_config (isa, get_config_table_fn ())) return 0; prev_num_opcodes = isa->num_opcodes; new_num_opcodes = (*get_num_opcodes_fn) (); isa->num_opcodes += new_num_opcodes; isa->opcode_table = (xtensa_opcode_internal **) realloc (isa->opcode_table, isa->num_opcodes * sizeof (xtensa_opcode_internal *)); isa->opname_lookup_table = (opname_lookup_entry *) realloc (isa->opname_lookup_table, isa->num_opcodes * sizeof (opname_lookup_entry)); opcodes = (*get_opcodes_fn) (); insn_size = isa->insn_size; for (opc = 0; opc < new_num_opcodes; opc++) { xtensa_opcode_internal *intopc = opcodes[opc]; int newopc = prev_num_opcodes + opc; isa->opcode_table[newopc] = intopc; isa->opname_lookup_table[newopc].key = intopc->name; isa->opname_lookup_table[newopc].opcode = newopc; if (intopc->length > insn_size) insn_size = intopc->length; } isa->insn_size = insn_size; isa->insnbuf_size = ((isa->insn_size + sizeof (xtensa_insnbuf_word) - 1) / sizeof (xtensa_insnbuf_word)); qsort (isa->opname_lookup_table, isa->num_opcodes, sizeof (opname_lookup_entry), opname_lookup_compare); /* Check for duplicate opcode names. */ for (opc = 1; opc < isa->num_opcodes; opc++) { if (!opname_lookup_compare (&isa->opname_lookup_table[opc-1], &isa->opname_lookup_table[opc])) { fprintf (stderr, "Error: Duplicate TIE opcode \"%s\"\n", isa->opname_lookup_table[opc].key); return 0; } } this_module = isa->num_modules; isa->num_modules += 1; isa->module_opcode_base = (int *) realloc (isa->module_opcode_base, isa->num_modules * sizeof (int)); isa->module_decode_fn = (xtensa_insn_decode_fn *) realloc (isa->module_decode_fn, isa->num_modules * sizeof (xtensa_insn_decode_fn)); isa->module_opcode_base[this_module] = prev_num_opcodes; isa->module_decode_fn[this_module] = decode_insn_fn; xtensa_default_isa = isa; return 1; /* Library was successfully added. */ } xtensa_isa xtensa_load_isa (libisa_module_specifier libisa) { xtensa_isa_internal *isa; isa = (xtensa_isa_internal *) malloc (sizeof (xtensa_isa_internal)); memset (isa, 0, sizeof (xtensa_isa_internal)); if (!xtensa_add_isa (isa, libisa)) { xtensa_isa_free (isa); return NULL; } return (xtensa_isa) isa; } int xtensa_extend_isa (xtensa_isa isa, libisa_module_specifier libisa) { xtensa_isa_internal *intisa = (xtensa_isa_internal *) isa; return xtensa_add_isa (intisa, libisa); } void xtensa_isa_free (xtensa_isa isa) { xtensa_isa_internal *intisa = (xtensa_isa_internal *) isa; if (intisa->opcode_table) free (intisa->opcode_table); if (intisa->opname_lookup_table) free (intisa->opname_lookup_table); if (intisa->module_opcode_base) free (intisa->module_opcode_base); if (intisa->module_decode_fn) free (intisa->module_decode_fn); free (intisa); } int xtensa_insn_maxlength (xtensa_isa isa) { xtensa_isa_internal *intisa = (xtensa_isa_internal *) isa; return intisa->insn_size; } int xtensa_insnbuf_size (xtensa_isa isa) { xtensa_isa_internal *intisa = (xtensa_isa_internal *)isa; return intisa->insnbuf_size; } int xtensa_num_opcodes (xtensa_isa isa) { xtensa_isa_internal *intisa = (xtensa_isa_internal *) isa; return intisa->num_opcodes; } xtensa_opcode xtensa_opcode_lookup (xtensa_isa isa, const char *opname) { xtensa_isa_internal *intisa = (xtensa_isa_internal *) isa; opname_lookup_entry entry, *result; entry.key = opname; result = bsearch (&entry, intisa->opname_lookup_table, intisa->num_opcodes, sizeof (opname_lookup_entry), opname_lookup_compare); if (!result) return XTENSA_UNDEFINED; return result->opcode; } xtensa_opcode xtensa_decode_insn (xtensa_isa isa, const xtensa_insnbuf insn) { xtensa_isa_internal *intisa = (xtensa_isa_internal *) isa; int n, opc; for (n = 0; n < intisa->num_modules; n++) { opc = (intisa->module_decode_fn[n]) (insn); if (opc != XTENSA_UNDEFINED) return intisa->module_opcode_base[n] + opc; } return XTENSA_UNDEFINED; } /* Opcode information. */ void xtensa_encode_insn (xtensa_isa isa, xtensa_opcode opc, xtensa_insnbuf insn) { xtensa_isa_internal *intisa = (xtensa_isa_internal *) isa; xtensa_insnbuf template = intisa->opcode_table[opc]->template(); int len = intisa->opcode_table[opc]->length; int n; /* Convert length to 32-bit words. */ len = (len + 3) / 4; /* Copy the template. */ for (n = 0; n < len; n++) insn[n] = template[n]; /* Fill any unused buffer space with zeros. */ for ( ; n < intisa->insnbuf_size; n++) insn[n] = 0; } const char * xtensa_opcode_name (xtensa_isa isa, xtensa_opcode opc) { xtensa_isa_internal *intisa = (xtensa_isa_internal *) isa; return intisa->opcode_table[opc]->name; } int xtensa_insn_length (xtensa_isa isa, xtensa_opcode opc) { xtensa_isa_internal *intisa = (xtensa_isa_internal *) isa; return intisa->opcode_table[opc]->length; } int xtensa_insn_length_from_first_byte (xtensa_isa isa, char first_byte) { xtensa_isa_internal *intisa = (xtensa_isa_internal *) isa; int is_density = (first_byte & (intisa->is_big_endian ? 0x80 : 0x08)) != 0; return (intisa->has_density && is_density ? 2 : 3); } int xtensa_num_operands (xtensa_isa isa, xtensa_opcode opc) { xtensa_isa_internal *intisa = (xtensa_isa_internal *) isa; return intisa->opcode_table[opc]->iclass->num_operands; } xtensa_operand xtensa_get_operand (xtensa_isa isa, xtensa_opcode opc, int opnd) { xtensa_isa_internal *intisa = (xtensa_isa_internal *) isa; xtensa_iclass_internal *iclass = intisa->opcode_table[opc]->iclass; if (opnd >= iclass->num_operands) return NULL; return (xtensa_operand) iclass->operands[opnd]; } /* Operand information. */ char * xtensa_operand_kind (xtensa_operand opnd) { xtensa_operand_internal *intop = (xtensa_operand_internal *) opnd; return intop->operand_kind; } char xtensa_operand_inout (xtensa_operand opnd) { xtensa_operand_internal *intop = (xtensa_operand_internal *) opnd; return intop->inout; } uint32 xtensa_operand_get_field (xtensa_operand opnd, const xtensa_insnbuf insn) { xtensa_operand_internal *intop = (xtensa_operand_internal *) opnd; return (*intop->get_field) (insn); } void xtensa_operand_set_field (xtensa_operand opnd, xtensa_insnbuf insn, uint32 val) { xtensa_operand_internal *intop = (xtensa_operand_internal *) opnd; return (*intop->set_field) (insn, val); } xtensa_encode_result xtensa_operand_encode (xtensa_operand opnd, uint32 *valp) { xtensa_operand_internal *intop = (xtensa_operand_internal *) opnd; return (*intop->encode) (valp); } uint32 xtensa_operand_decode (xtensa_operand opnd, uint32 val) { xtensa_operand_internal *intop = (xtensa_operand_internal *) opnd; return (*intop->decode) (val); } int xtensa_operand_isPCRelative (xtensa_operand opnd) { xtensa_operand_internal *intop = (xtensa_operand_internal *) opnd; return intop->isPCRelative; } uint32 xtensa_operand_do_reloc (xtensa_operand opnd, uint32 addr, uint32 pc) { xtensa_operand_internal *intop = (xtensa_operand_internal *) opnd; if (!intop->isPCRelative) return addr; return (*intop->do_reloc) (addr, pc); } uint32 xtensa_operand_undo_reloc (xtensa_operand opnd, uint32 offset, uint32 pc) { xtensa_operand_internal *intop = (xtensa_operand_internal *) opnd; if (!intop->isPCRelative) return offset; return (*intop->undo_reloc) (offset, pc); } /* Instruction buffers. */ xtensa_insnbuf xtensa_insnbuf_alloc (xtensa_isa isa) { return (xtensa_insnbuf) malloc (xtensa_insnbuf_size (isa) * sizeof (xtensa_insnbuf_word)); } void xtensa_insnbuf_free (xtensa_insnbuf buf) { free( buf ); } /* Given <byte_index>, the index of a byte in a xtensa_insnbuf, our internal representation of a xtensa instruction word, return the index of its word and the bit index of its low order byte in the xtensa_insnbuf. */ static inline int byte_to_word_index (int byte_index) { return byte_index / sizeof (xtensa_insnbuf_word); } static inline int byte_to_bit_index (int byte_index) { return (byte_index & 0x3) * 8; } /* Copy an instruction in the 32 bit words pointed at by <insn> to characters pointed at by <cp>. This is more complicated than you might think because we want 16 bit instructions in bytes 2,3 for big endian. This function allows us to specify which byte in <insn> to start with and which way to increment, allowing trivial implementation for both big and little endian. And it seems to make pretty good code for both. */ void xtensa_insnbuf_to_chars (xtensa_isa isa, const xtensa_insnbuf insn, char *cp) { xtensa_isa_internal *intisa = (xtensa_isa_internal *) isa; int insn_size = xtensa_insn_maxlength (intisa); int fence_post, start, increment, i, byte_count; xtensa_opcode opc; if (intisa->is_big_endian) { start = insn_size - 1; increment = -1; } else { start = 0; increment = 1; } /* Find the opcode; do nothing if the buffer does not contain a valid instruction since we need to know how many bytes to copy. */ opc = xtensa_decode_insn (isa, insn); if (opc == XTENSA_UNDEFINED) return; byte_count = xtensa_insn_length (isa, opc); fence_post = start + (byte_count * increment); for (i = start; i != fence_post; i += increment, ++cp) { int word_inx = byte_to_word_index (i); int bit_inx = byte_to_bit_index (i); *cp = (insn[word_inx] >> bit_inx) & 0xff; } } /* Inward conversion from byte stream to xtensa_insnbuf. See xtensa_insnbuf_to_chars for a discussion of why this is complicated by endianness. */ void xtensa_insnbuf_from_chars (xtensa_isa isa, xtensa_insnbuf insn, const char* cp) { xtensa_isa_internal *intisa = (xtensa_isa_internal *) isa; int insn_size = xtensa_insn_maxlength (intisa); int fence_post, start, increment, i; if (intisa->is_big_endian) { start = insn_size - 1; increment = -1; } else { start = 0; increment = 1; } fence_post = start + (insn_size * increment); memset (insn, 0, xtensa_insnbuf_size (isa) * sizeof (xtensa_insnbuf_word)); for ( i = start; i != fence_post; i += increment, ++cp ) { int word_inx = byte_to_word_index (i); int bit_inx = byte_to_bit_index (i); insn[word_inx] |= (*cp & 0xff) << bit_inx; } }