|  | /* TI PRU disassemble routines | 
|  | Copyright (C) 2014-2025 Free Software Foundation, Inc. | 
|  | Contributed by Dimitar Dimitrov <dimitar@dinux.eu> | 
|  |  | 
|  | This file is part of the GNU opcodes library. | 
|  |  | 
|  | This library 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. | 
|  |  | 
|  | It 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 file; see the file COPYING.  If not, write to the | 
|  | Free Software Foundation, 51 Franklin Street - Fifth Floor, Boston, | 
|  | MA 02110-1301, USA.  */ | 
|  |  | 
|  | #include "sysdep.h" | 
|  | #include "disassemble.h" | 
|  | #include "opcode/pru.h" | 
|  | #include "libiberty.h" | 
|  | #include <string.h> | 
|  | #include <assert.h> | 
|  |  | 
|  | /* No symbol table is available when this code runs out in an embedded | 
|  | system as when it is used for disassembler support in a monitor.  */ | 
|  | #if !defined (EMBEDDED_ENV) | 
|  | #define SYMTAB_AVAILABLE 1 | 
|  | #include "elf-bfd.h" | 
|  | #include "elf/pru.h" | 
|  | #endif | 
|  |  | 
|  | /* Length of PRU instruction in bytes.  */ | 
|  | #define INSNLEN 4 | 
|  |  | 
|  | /* Return a pointer to an pru_opcode struct for a given instruction | 
|  | opcode, or NULL if there is an error.  */ | 
|  | const struct pru_opcode * | 
|  | pru_find_opcode (unsigned long opcode) | 
|  | { | 
|  | const struct pru_opcode *p; | 
|  | const struct pru_opcode *op = NULL; | 
|  | const struct pru_opcode *pseudo_op = NULL; | 
|  |  | 
|  | for (p = pru_opcodes; p < &pru_opcodes[NUMOPCODES]; p++) | 
|  | { | 
|  | if ((p->mask & opcode) == p->match) | 
|  | { | 
|  | if ((p->pinfo & PRU_INSN_MACRO) == PRU_INSN_MACRO) | 
|  | pseudo_op = p; | 
|  | else if ((p->pinfo & PRU_INSN_LDI32) == PRU_INSN_LDI32) | 
|  | /* ignore - should be caught with regular patterns */; | 
|  | else | 
|  | op = p; | 
|  | } | 
|  | } | 
|  |  | 
|  | return pseudo_op ? pseudo_op : op; | 
|  | } | 
|  |  | 
|  | /* There are 32 regular registers, each with 8 possible subfield selectors.  */ | 
|  | #define NUMREGNAMES (32 * 8) | 
|  |  | 
|  | static void | 
|  | pru_print_insn_arg_reg (unsigned int r, unsigned int sel, | 
|  | disassemble_info *info) | 
|  | { | 
|  | unsigned int i = r * RSEL_NUM_ITEMS + sel; | 
|  | assert (i < (unsigned int)pru_num_regs); | 
|  | assert (i < NUMREGNAMES); | 
|  | (*info->fprintf_func) (info->stream, "%s", pru_regs[i].name); | 
|  | } | 
|  |  | 
|  | /* The function pru_print_insn_arg uses the character pointed | 
|  | to by ARGPTR to determine how it print the next token or separator | 
|  | character in the arguments to an instruction.  */ | 
|  | static int | 
|  | pru_print_insn_arg (const char *argptr, | 
|  | unsigned long opcode, bfd_vma address, | 
|  | disassemble_info *info) | 
|  | { | 
|  | long offs = 0; | 
|  | unsigned long i = 0; | 
|  | unsigned long io = 0; | 
|  |  | 
|  | switch (*argptr) | 
|  | { | 
|  | case ',': | 
|  | (*info->fprintf_func) (info->stream, "%c ", *argptr); | 
|  | break; | 
|  | case 'd': | 
|  | pru_print_insn_arg_reg (GET_INSN_FIELD (RD, opcode), | 
|  | GET_INSN_FIELD (RDSEL, opcode), | 
|  | info); | 
|  | break; | 
|  | case 'D': | 
|  | /* The first 4 values for RDB and RSEL are the same, so we | 
|  | can reuse some code.  */ | 
|  | pru_print_insn_arg_reg (GET_INSN_FIELD (RD, opcode), | 
|  | GET_INSN_FIELD (RDB, opcode), | 
|  | info); | 
|  | break; | 
|  | case 's': | 
|  | pru_print_insn_arg_reg (GET_INSN_FIELD (RS1, opcode), | 
|  | GET_INSN_FIELD (RS1SEL, opcode), | 
|  | info); | 
|  | break; | 
|  | case 'S': | 
|  | pru_print_insn_arg_reg (GET_INSN_FIELD (RS1, opcode), | 
|  | RSEL_31_0, | 
|  | info); | 
|  | break; | 
|  | case 'b': | 
|  | io = GET_INSN_FIELD (IO, opcode); | 
|  |  | 
|  | if (io) | 
|  | { | 
|  | i = GET_INSN_FIELD (IMM8, opcode); | 
|  | (*info->fprintf_func) (info->stream, "%ld", i); | 
|  | } | 
|  | else | 
|  | { | 
|  | pru_print_insn_arg_reg (GET_INSN_FIELD (RS2, opcode), | 
|  | GET_INSN_FIELD (RS2SEL, opcode), | 
|  | info); | 
|  | } | 
|  | break; | 
|  | case 'B': | 
|  | io = GET_INSN_FIELD (IO, opcode); | 
|  |  | 
|  | if (io) | 
|  | { | 
|  | i = GET_INSN_FIELD (IMM8, opcode) + 1; | 
|  | (*info->fprintf_func) (info->stream, "%ld", i); | 
|  | } | 
|  | else | 
|  | { | 
|  | pru_print_insn_arg_reg (GET_INSN_FIELD (RS2, opcode), | 
|  | GET_INSN_FIELD (RS2SEL, opcode), | 
|  | info); | 
|  | } | 
|  | break; | 
|  | case 'j': | 
|  | io = GET_INSN_FIELD (IO, opcode); | 
|  |  | 
|  | if (io) | 
|  | { | 
|  | /* For the sake of pretty-printing, dump text addresses with | 
|  | their "virtual" offset that we use for distinguishing | 
|  | PMEM vs DMEM. This is needed for printing the correct text | 
|  | labels.  */ | 
|  | bfd_vma text_offset = address & ~0x3fffff; | 
|  | i = GET_INSN_FIELD (IMM16, opcode) * 4; | 
|  | (*info->print_address_func) (i + text_offset, info); | 
|  | } | 
|  | else | 
|  | { | 
|  | pru_print_insn_arg_reg (GET_INSN_FIELD (RS2, opcode), | 
|  | GET_INSN_FIELD (RS2SEL, opcode), | 
|  | info); | 
|  | } | 
|  | break; | 
|  | case 'W': | 
|  | i = GET_INSN_FIELD (IMM16, opcode); | 
|  | (*info->fprintf_func) (info->stream, "%ld", i); | 
|  | break; | 
|  | case 'o': | 
|  | offs = GET_BROFF_SIGNED (opcode) * 4; | 
|  | (*info->print_address_func) (address + offs, info); | 
|  | break; | 
|  | case 'O': | 
|  | offs = GET_INSN_FIELD (LOOP_JMPOFFS, opcode) * 4; | 
|  | (*info->print_address_func) (address + offs, info); | 
|  | break; | 
|  | case 'l': | 
|  | i = GET_BURSTLEN (opcode); | 
|  | if (i < LSSBBO_BYTECOUNT_R0_BITS7_0) | 
|  | (*info->fprintf_func) (info->stream, "%ld", i + 1); | 
|  | else | 
|  | { | 
|  | i -= LSSBBO_BYTECOUNT_R0_BITS7_0; | 
|  | (*info->fprintf_func) (info->stream, "r0.b%ld", i); | 
|  | } | 
|  | break; | 
|  | case 'n': | 
|  | i = GET_INSN_FIELD (XFR_LENGTH, opcode); | 
|  | if (i < LSSBBO_BYTECOUNT_R0_BITS7_0) | 
|  | (*info->fprintf_func) (info->stream, "%ld", i + 1); | 
|  | else | 
|  | { | 
|  | i -= LSSBBO_BYTECOUNT_R0_BITS7_0; | 
|  | (*info->fprintf_func) (info->stream, "r0.b%ld", i); | 
|  | } | 
|  | break; | 
|  | case 'c': | 
|  | i = GET_INSN_FIELD (CB, opcode); | 
|  | (*info->fprintf_func) (info->stream, "%ld", i); | 
|  | break; | 
|  | case 'w': | 
|  | i = GET_INSN_FIELD (WAKEONSTATUS, opcode); | 
|  | (*info->fprintf_func) (info->stream, "%ld", i); | 
|  | break; | 
|  | case 'x': | 
|  | i = GET_INSN_FIELD (XFR_WBA, opcode); | 
|  | (*info->fprintf_func) (info->stream, "%ld", i); | 
|  | break; | 
|  | default: | 
|  | (*info->fprintf_func) (info->stream, "unknown"); | 
|  | break; | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* pru_disassemble does all the work of disassembling a PRU | 
|  | instruction opcode.  */ | 
|  | static int | 
|  | pru_disassemble (bfd_vma address, unsigned long opcode, | 
|  | disassemble_info *info) | 
|  | { | 
|  | const struct pru_opcode *op; | 
|  |  | 
|  | info->bytes_per_line = INSNLEN; | 
|  | info->bytes_per_chunk = INSNLEN; | 
|  | info->display_endian = info->endian; | 
|  | info->insn_info_valid = 1; | 
|  | info->branch_delay_insns = 0; | 
|  | info->data_size = 0; | 
|  | info->insn_type = dis_nonbranch; | 
|  | info->target = 0; | 
|  | info->target2 = 0; | 
|  |  | 
|  | /* Find the major opcode and use this to disassemble | 
|  | the instruction and its arguments.  */ | 
|  | op = pru_find_opcode (opcode); | 
|  |  | 
|  | if (op != NULL) | 
|  | { | 
|  | (*info->fprintf_func) (info->stream, "%s", op->name); | 
|  |  | 
|  | const char *argstr = op->args; | 
|  | if (argstr != NULL && *argstr != '\0') | 
|  | { | 
|  | (*info->fprintf_func) (info->stream, "\t"); | 
|  | while (*argstr != '\0') | 
|  | { | 
|  | pru_print_insn_arg (argstr, opcode, address, info); | 
|  | ++argstr; | 
|  | } | 
|  | } | 
|  | } | 
|  | else | 
|  | { | 
|  | /* Handle undefined instructions.  */ | 
|  | info->insn_type = dis_noninsn; | 
|  | (*info->fprintf_func) (info->stream, "0x%lx", opcode); | 
|  | } | 
|  | /* Tell the caller how far to advance the program counter.  */ | 
|  | return INSNLEN; | 
|  | } | 
|  |  | 
|  |  | 
|  | /* print_insn_pru is the main disassemble function for PRU.  */ | 
|  | int | 
|  | print_insn_pru (bfd_vma address, disassemble_info *info) | 
|  | { | 
|  | bfd_byte buffer[INSNLEN]; | 
|  | int status; | 
|  |  | 
|  | status = (*info->read_memory_func) (address, buffer, INSNLEN, info); | 
|  | if (status == 0) | 
|  | { | 
|  | unsigned long insn; | 
|  | insn = (unsigned long) bfd_getl32 (buffer); | 
|  | status = pru_disassemble (address, insn, info); | 
|  | } | 
|  | else | 
|  | { | 
|  | (*info->memory_error_func) (status, address, info); | 
|  | status = -1; | 
|  | } | 
|  | return status; | 
|  | } |