| /* OpenRISC exception, interrupts, syscall and trap support | |
| Copyright (C) 2017-2022 Free Software Foundation, Inc. | |
| This file is part of GDB, the GNU debugger. | |
| 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 3 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, see <http://www.gnu.org/licenses/>. */ | |
| /* This must come before any other includes. */ | |
| #include "defs.h" | |
| #define WANT_CPU_OR1K32BF | |
| #define WANT_CPU | |
| #include "sim-main.h" | |
| #include "sim-fpu.h" | |
| #include "sim-signal.h" | |
| #include "cgen-ops.h" | |
| /* Implement the sim invalid instruction function. This will set the error | |
| effective address to that of the invalid instruction then call the | |
| exception handler. */ | |
| SEM_PC | |
| sim_engine_invalid_insn (SIM_CPU *current_cpu, IADDR cia, SEM_PC vpc) | |
| { | |
| SET_H_SYS_EEAR0 (cia); | |
| #ifdef WANT_CPU_OR1K32BF | |
| or1k32bf_exception (current_cpu, cia, EXCEPT_ILLEGAL); | |
| #endif | |
| return vpc; | |
| } | |
| /* Generate the appropriate OpenRISC fpu exception based on the status code from | |
| the sim fpu. */ | |
| void | |
| or1k32bf_fpu_error (CGEN_FPU* fpu, int status) | |
| { | |
| SIM_CPU *current_cpu = (SIM_CPU *)fpu->owner; | |
| /* If floating point exceptions are enabled. */ | |
| if (GET_H_SYS_FPCSR_FPEE() != 0) | |
| { | |
| /* Set all of the status flag bits. */ | |
| if (status | |
| & (sim_fpu_status_invalid_snan | |
| | sim_fpu_status_invalid_qnan | |
| | sim_fpu_status_invalid_isi | |
| | sim_fpu_status_invalid_idi | |
| | sim_fpu_status_invalid_zdz | |
| | sim_fpu_status_invalid_imz | |
| | sim_fpu_status_invalid_cvi | |
| | sim_fpu_status_invalid_cmp | |
| | sim_fpu_status_invalid_sqrt)) | |
| SET_H_SYS_FPCSR_IVF (1); | |
| if (status & sim_fpu_status_invalid_snan) | |
| SET_H_SYS_FPCSR_SNF (1); | |
| if (status & sim_fpu_status_invalid_qnan) | |
| SET_H_SYS_FPCSR_QNF (1); | |
| if (status & sim_fpu_status_overflow) | |
| SET_H_SYS_FPCSR_OVF (1); | |
| if (status & sim_fpu_status_underflow) | |
| SET_H_SYS_FPCSR_UNF (1); | |
| if (status | |
| & (sim_fpu_status_invalid_isi | |
| | sim_fpu_status_invalid_idi)) | |
| SET_H_SYS_FPCSR_INF (1); | |
| if (status & sim_fpu_status_invalid_div0) | |
| SET_H_SYS_FPCSR_DZF (1); | |
| if (status & sim_fpu_status_inexact) | |
| SET_H_SYS_FPCSR_IXF (1); | |
| /* If any of the exception bits were actually set. */ | |
| if (GET_H_SYS_FPCSR() | |
| & (SPR_FIELD_MASK_SYS_FPCSR_IVF | |
| | SPR_FIELD_MASK_SYS_FPCSR_SNF | |
| | SPR_FIELD_MASK_SYS_FPCSR_QNF | |
| | SPR_FIELD_MASK_SYS_FPCSR_OVF | |
| | SPR_FIELD_MASK_SYS_FPCSR_UNF | |
| | SPR_FIELD_MASK_SYS_FPCSR_INF | |
| | SPR_FIELD_MASK_SYS_FPCSR_DZF | |
| | SPR_FIELD_MASK_SYS_FPCSR_IXF)) | |
| { | |
| SIM_DESC sd = CPU_STATE (current_cpu); | |
| /* If the sim is running in fast mode, i.e. not profiling, | |
| per-instruction callbacks are not triggered which would allow | |
| us to track the PC. This means we cannot track which | |
| instruction caused the FPU error. */ | |
| if (!PROFILE_ANY_P (current_cpu) && !TRACE_ANY_P (current_cpu)) | |
| sim_io_eprintf | |
| (sd, "WARNING: ignoring fpu error caught in fast mode.\n"); | |
| else | |
| or1k32bf_exception (current_cpu, GET_H_SYS_PPC (), EXCEPT_FPE); | |
| } | |
| } | |
| } | |
| /* Implement the OpenRISC exception function. This is mostly used by the | |
| CGEN generated files. For example, this is used when handling a | |
| overflow exception during a multiplication instruction. */ | |
| void | |
| or1k32bf_exception (sim_cpu *current_cpu, USI pc, USI exnum) | |
| { | |
| SIM_DESC sd = CPU_STATE (current_cpu); | |
| struct or1k_sim_cpu *or1k_cpu = OR1K_SIM_CPU (current_cpu); | |
| if (exnum == EXCEPT_TRAP) | |
| { | |
| /* Trap, used for breakpoints, sends control back to gdb breakpoint | |
| handling. */ | |
| sim_engine_halt (sd, current_cpu, NULL, pc, sim_stopped, SIM_SIGTRAP); | |
| } | |
| else | |
| { | |
| IADDR handler_pc; | |
| /* Calculate the exception program counter. */ | |
| switch (exnum) | |
| { | |
| case EXCEPT_RESET: | |
| break; | |
| case EXCEPT_FPE: | |
| case EXCEPT_SYSCALL: | |
| SET_H_SYS_EPCR0 (pc + 4 - (or1k_cpu->delay_slot ? 4 : 0)); | |
| break; | |
| case EXCEPT_BUSERR: | |
| case EXCEPT_ALIGN: | |
| case EXCEPT_ILLEGAL: | |
| case EXCEPT_RANGE: | |
| SET_H_SYS_EPCR0 (pc - (or1k_cpu->delay_slot ? 4 : 0)); | |
| break; | |
| default: | |
| sim_io_error (sd, "unexpected exception 0x%x raised at PC 0x%08x", | |
| exnum, pc); | |
| break; | |
| } | |
| /* Store the current SR into ESR0. */ | |
| SET_H_SYS_ESR0 (GET_H_SYS_SR ()); | |
| /* Indicate in SR if the failed instruction is in delay slot or not. */ | |
| SET_H_SYS_SR_DSX (or1k_cpu->delay_slot); | |
| or1k_cpu->next_delay_slot = 0; | |
| /* Jump program counter into handler. */ | |
| handler_pc = | |
| (GET_H_SYS_SR_EPH () ? 0xf0000000 : 0x00000000) + (exnum << 8); | |
| sim_engine_restart (sd, current_cpu, NULL, handler_pc); | |
| } | |
| } | |
| /* Implement the return from exception instruction. This is used to return | |
| the CPU to its previous state from within an exception handler. */ | |
| void | |
| or1k32bf_rfe (sim_cpu *current_cpu) | |
| { | |
| struct or1k_sim_cpu *or1k_cpu = OR1K_SIM_CPU (current_cpu); | |
| SET_H_SYS_SR (GET_H_SYS_ESR0 ()); | |
| SET_H_SYS_SR_FO (1); | |
| or1k_cpu->next_delay_slot = 0; | |
| sim_engine_restart (CPU_STATE (current_cpu), current_cpu, NULL, | |
| GET_H_SYS_EPCR0 ()); | |
| } | |
| /* Implement the move from SPR instruction. This is used to read from the | |
| CPU's special purpose registers. */ | |
| USI | |
| or1k32bf_mfspr (sim_cpu *current_cpu, USI addr) | |
| { | |
| SIM_DESC sd = CPU_STATE (current_cpu); | |
| SI val; | |
| if (!GET_H_SYS_SR_SM () && !GET_H_SYS_SR_SUMRA ()) | |
| { | |
| sim_io_eprintf (sd, "WARNING: l.mfspr in user mode (SR 0x%x)\n", | |
| GET_H_SYS_SR ()); | |
| return 0; | |
| } | |
| if (addr >= NUM_SPR) | |
| goto bad_address; | |
| val = GET_H_SPR (addr); | |
| switch (addr) | |
| { | |
| case SPR_ADDR (SYS, VR): | |
| case SPR_ADDR (SYS, UPR): | |
| case SPR_ADDR (SYS, CPUCFGR): | |
| case SPR_ADDR (SYS, SR): | |
| case SPR_ADDR (SYS, PPC): | |
| case SPR_ADDR (SYS, FPCSR): | |
| case SPR_ADDR (SYS, EPCR0): | |
| case SPR_ADDR (MAC, MACLO): | |
| case SPR_ADDR (MAC, MACHI): | |
| break; | |
| default: | |
| if (addr < SPR_ADDR (SYS, GPR0) || addr > SPR_ADDR (SYS, GPR511)) | |
| goto bad_address; | |
| break; | |
| } | |
| return val; | |
| bad_address: | |
| sim_io_eprintf (sd, "WARNING: l.mfspr with invalid SPR address 0x%x\n", addr); | |
| return 0; | |
| } | |
| /* Implement the move to SPR instruction. This is used to write too the | |
| CPU's special purpose registers. */ | |
| void | |
| or1k32bf_mtspr (sim_cpu *current_cpu, USI addr, USI val) | |
| { | |
| SIM_DESC sd = CPU_STATE (current_cpu); | |
| struct or1k_sim_cpu *or1k_cpu = OR1K_SIM_CPU (current_cpu); | |
| if (!GET_H_SYS_SR_SM () && !GET_H_SYS_SR_SUMRA ()) | |
| { | |
| sim_io_eprintf | |
| (sd, "WARNING: l.mtspr with address 0x%x in user mode (SR 0x%x)\n", | |
| addr, GET_H_SYS_SR ()); | |
| return; | |
| } | |
| if (addr >= NUM_SPR) | |
| goto bad_address; | |
| switch (addr) | |
| { | |
| case SPR_ADDR (SYS, FPCSR): | |
| case SPR_ADDR (SYS, EPCR0): | |
| case SPR_ADDR (SYS, ESR0): | |
| case SPR_ADDR (MAC, MACHI): | |
| case SPR_ADDR (MAC, MACLO): | |
| SET_H_SPR (addr, val); | |
| break; | |
| case SPR_ADDR (SYS, SR): | |
| SET_H_SPR (addr, val); | |
| SET_H_SYS_SR_FO (1); | |
| break; | |
| case SPR_ADDR (SYS, NPC): | |
| or1k_cpu->next_delay_slot = 0; | |
| sim_engine_restart (sd, current_cpu, NULL, val); | |
| break; | |
| case SPR_ADDR (TICK, TTMR): | |
| /* Allow some registers to be silently cleared. */ | |
| if (val != 0) | |
| sim_io_eprintf | |
| (sd, "WARNING: l.mtspr to SPR address 0x%x with invalid value 0x%x\n", | |
| addr, val); | |
| break; | |
| default: | |
| if (addr >= SPR_ADDR (SYS, GPR0) && addr <= SPR_ADDR (SYS, GPR511)) | |
| SET_H_SPR (addr, val); | |
| else | |
| goto bad_address; | |
| break; | |
| } | |
| return; | |
| bad_address: | |
| sim_io_eprintf (sd, "WARNING: l.mtspr with invalid SPR address 0x%x\n", addr); | |
| } |