| /* This file is part of the program psim. |
| |
| Copyright 1994, 1995, 1996, 1997, 2003 Andrew Cagney |
| |
| 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/>. |
| |
| */ |
| |
| |
| #ifndef _INTERRUPTS_C_ |
| #define _INTERRUPTS_C_ |
| |
| #include <signal.h> |
| |
| #include "cpu.h" |
| #include "idecode.h" |
| #include "os_emul.h" |
| |
| |
| /* Operating environment support code |
| |
| Unlike the VEA, the OEA must fully model the effect an interrupt |
| has on the processors state. |
| |
| Each function below return updated values for registers effected by |
| interrupts */ |
| |
| |
| STATIC_INLINE_INTERRUPTS\ |
| (msreg) |
| interrupt_msr(msreg old_msr, |
| msreg msr_clear, |
| msreg msr_set) |
| { |
| msreg msr_set_to_0 = (msr_branch_trace_enable |
| | msr_data_relocate |
| | msr_external_interrupt_enable |
| | msr_floating_point_exception_mode_0 |
| | msr_floating_point_exception_mode_1 |
| | msr_floating_point_available |
| | msr_instruction_relocate |
| | msr_power_management_enable |
| | msr_problem_state |
| | msr_recoverable_interrupt |
| | msr_single_step_trace_enable); |
| /* remember, in 32bit mode msr_64bit_mode is zero */ |
| msreg new_msr = ((((old_msr & ~msr_set_to_0) |
| | msr_64bit_mode) |
| & ~msr_clear) |
| | msr_set); |
| return new_msr; |
| } |
| |
| |
| STATIC_INLINE_INTERRUPTS\ |
| (msreg) |
| interrupt_srr1(msreg old_msr, |
| msreg srr1_clear, |
| msreg srr1_set) |
| { |
| spreg srr1_mask = (MASK(0,32) |
| | MASK(37, 41) |
| | MASK(48, 63)); |
| spreg srr1 = (old_msr & srr1_mask & ~srr1_clear) | srr1_set; |
| return srr1; |
| } |
| |
| |
| STATIC_INLINE_INTERRUPTS\ |
| (unsigned_word) |
| interrupt_base_ea(msreg msr) |
| { |
| if (msr & msr_interrupt_prefix) |
| return MASK(0, 43); |
| else |
| return 0; |
| } |
| |
| |
| /* finish off an interrupt for the OEA model, updating all registers |
| and forcing a restart of the processor */ |
| |
| STATIC_INLINE_INTERRUPTS\ |
| (unsigned_word) |
| perform_oea_interrupt(cpu *processor, |
| unsigned_word cia, |
| unsigned_word vector_offset, |
| msreg msr_clear, |
| msreg msr_set, |
| msreg srr1_clear, |
| msreg srr1_set) |
| { |
| msreg old_msr = MSR; |
| msreg new_msr = interrupt_msr(old_msr, msr_clear, msr_set); |
| unsigned_word nia; |
| if (!(old_msr & msr_recoverable_interrupt)) { |
| cpu_error(processor, cia, |
| "double interrupt - MSR[RI] bit clear when attempting to deliver interrupt, cia=0x%lx, msr=0x%lx; srr0=0x%lx(cia), srr1=0x%lx(msr); trap-vector=0x%lx, trap-msr=0x%lx", |
| (unsigned long)cia, |
| (unsigned long)old_msr, |
| (unsigned long)SRR0, |
| (unsigned long)SRR1, |
| (unsigned long)vector_offset, |
| (unsigned long)new_msr); |
| } |
| SRR0 = (spreg)(cia); |
| SRR1 = interrupt_srr1(old_msr, srr1_clear, srr1_set); |
| MSR = new_msr; |
| nia = interrupt_base_ea(new_msr) + vector_offset; |
| cpu_synchronize_context(processor, cia); |
| return nia; |
| } |
| |
| |
| INLINE_INTERRUPTS\ |
| (void) |
| machine_check_interrupt(cpu *processor, |
| unsigned_word cia) |
| { |
| switch (CURRENT_ENVIRONMENT) { |
| |
| case USER_ENVIRONMENT: |
| case VIRTUAL_ENVIRONMENT: |
| cpu_error(processor, cia, "machine-check interrupt"); |
| |
| case OPERATING_ENVIRONMENT: |
| TRACE(trace_interrupts, ("machine-check interrupt - cia=0x%lx\n", |
| (unsigned long)cia)); |
| cia = perform_oea_interrupt(processor, cia, 0x00200, 0, 0, 0, 0); |
| cpu_restart(processor, cia); |
| |
| default: |
| error("internal error - machine_check_interrupt - bad switch"); |
| |
| } |
| } |
| |
| |
| INLINE_INTERRUPTS\ |
| (void) |
| data_storage_interrupt(cpu *processor, |
| unsigned_word cia, |
| unsigned_word ea, |
| storage_interrupt_reasons reason, |
| int is_store) |
| { |
| switch (CURRENT_ENVIRONMENT) { |
| |
| case USER_ENVIRONMENT: |
| case VIRTUAL_ENVIRONMENT: |
| error("internal error - data_storage_interrupt - should not be called in VEA mode"); |
| break; |
| |
| case OPERATING_ENVIRONMENT: |
| { |
| spreg direction = (is_store ? dsisr_store_operation : 0); |
| switch (reason) { |
| case direct_store_storage_interrupt: |
| DSISR = dsisr_direct_store_error_exception | direction; |
| break; |
| case hash_table_miss_storage_interrupt: |
| DSISR = dsisr_hash_table_or_dbat_miss | direction; |
| break; |
| case protection_violation_storage_interrupt: |
| DSISR = dsisr_protection_violation | direction; |
| break; |
| case earwax_violation_storage_interrupt: |
| DSISR = dsisr_earwax_violation | direction; |
| break; |
| case segment_table_miss_storage_interrupt: |
| DSISR = dsisr_segment_table_miss | direction; |
| break; |
| case earwax_disabled_storage_interrupt: |
| DSISR = dsisr_earwax_disabled | direction; |
| break; |
| default: |
| error("internal error - data_storage_interrupt - reason %d not implemented", reason); |
| break; |
| } |
| DAR = (spreg)ea; |
| TRACE(trace_interrupts, ("data storage interrupt - cia=0x%lx DAR=0x%lx DSISR=0x%lx\n", |
| (unsigned long)cia, |
| (unsigned long)DAR, |
| (unsigned long)DSISR)); |
| cia = perform_oea_interrupt(processor, cia, 0x00300, 0, 0, 0, 0); |
| cpu_restart(processor, cia); |
| } |
| |
| default: |
| error("internal error - data_storage_interrupt - bad switch"); |
| |
| } |
| } |
| |
| |
| INLINE_INTERRUPTS\ |
| (void) |
| instruction_storage_interrupt(cpu *processor, |
| unsigned_word cia, |
| storage_interrupt_reasons reason) |
| { |
| switch (CURRENT_ENVIRONMENT) { |
| |
| case USER_ENVIRONMENT: |
| case VIRTUAL_ENVIRONMENT: |
| error("internal error - instruction_storage_interrupt - should not be called in VEA mode"); |
| |
| case OPERATING_ENVIRONMENT: |
| { |
| msreg srr1_set; |
| switch(reason) { |
| case hash_table_miss_storage_interrupt: |
| srr1_set = srr1_hash_table_or_ibat_miss; |
| break; |
| case direct_store_storage_interrupt: |
| srr1_set = srr1_direct_store_error_exception; |
| break; |
| case protection_violation_storage_interrupt: |
| srr1_set = srr1_protection_violation; |
| break; |
| case segment_table_miss_storage_interrupt: |
| srr1_set = srr1_segment_table_miss; |
| break; |
| default: |
| srr1_set = 0; |
| error("internal error - instruction_storage_interrupt - reason %d not implemented"); |
| break; |
| } |
| TRACE(trace_interrupts, ("instruction storage interrupt - cia=0x%lx SRR1|=0x%lx\n", |
| (unsigned long)cia, |
| (unsigned long)srr1_set)); |
| cia = perform_oea_interrupt(processor, cia, 0x00400, 0, 0, 0, srr1_set); |
| cpu_restart(processor, cia); |
| } |
| |
| default: |
| error("internal error - instruction_storage_interrupt - bad switch"); |
| |
| } |
| } |
| |
| |
| |
| INLINE_INTERRUPTS\ |
| (void) |
| alignment_interrupt(cpu *processor, |
| unsigned_word cia, |
| unsigned_word ra) |
| { |
| switch (CURRENT_ENVIRONMENT) { |
| |
| case USER_ENVIRONMENT: |
| case VIRTUAL_ENVIRONMENT: |
| cpu_error(processor, cia, "alignment interrupt - ra=0x%lx", (unsigned long)ra); |
| |
| case OPERATING_ENVIRONMENT: |
| DAR = (spreg)ra; |
| DSISR = 0; /* FIXME */ |
| TRACE(trace_interrupts, ("alignment interrupt - cia=0x%lx DAR=0x%lx DSISR=0x%lx\n", |
| (unsigned long)cia, |
| (unsigned long)DAR, |
| (unsigned long)DSISR)); |
| cia = perform_oea_interrupt(processor, cia, 0x00600, 0, 0, 0, 0); |
| cpu_restart(processor, cia); |
| |
| default: |
| error("internal error - alignment_interrupt - bad switch"); |
| |
| } |
| } |
| |
| |
| |
| |
| INLINE_INTERRUPTS\ |
| (void) |
| program_interrupt(cpu *processor, |
| unsigned_word cia, |
| program_interrupt_reasons reason) |
| { |
| switch (CURRENT_ENVIRONMENT) { |
| |
| case USER_ENVIRONMENT: |
| case VIRTUAL_ENVIRONMENT: |
| switch (reason) { |
| case floating_point_enabled_program_interrupt: |
| cpu_error(processor, cia, "program interrupt - %s", |
| "floating point enabled"); |
| break; |
| case illegal_instruction_program_interrupt: |
| cpu_error(processor, cia, "program interrupt - %s", |
| "illegal instruction"); |
| break; |
| case privileged_instruction_program_interrupt: |
| cpu_error(processor, cia, "program interrupt - %s", |
| "privileged instruction"); |
| break; |
| case trap_program_interrupt: |
| cpu_error(processor, cia, "program interrupt - %s", |
| "trap"); |
| break; |
| case optional_instruction_program_interrupt: |
| cpu_error(processor, cia, "program interrupt - %s", |
| "illegal instruction (optional instruction not supported)"); |
| break; |
| case mpc860c0_instruction_program_interrupt: |
| cpu_error(processor, cia, "program interrupt - %s", |
| "problematic branch detected, see MPC860 C0 errata"); |
| break; |
| default: |
| error("internal error - program_interrupt - reason %d not implemented", reason); |
| } |
| |
| case OPERATING_ENVIRONMENT: |
| { |
| msreg srr1_set; |
| switch (reason) { |
| case floating_point_enabled_program_interrupt: |
| srr1_set = srr1_floating_point_enabled; |
| break; |
| case optional_instruction_program_interrupt: |
| case illegal_instruction_program_interrupt: |
| srr1_set = srr1_illegal_instruction; |
| break; |
| case privileged_instruction_program_interrupt: |
| srr1_set = srr1_priviliged_instruction; |
| break; |
| case trap_program_interrupt: |
| srr1_set = srr1_trap; |
| break; |
| case mpc860c0_instruction_program_interrupt: |
| srr1_set = 0; |
| cpu_error(processor, cia, "program interrupt - %s", |
| "problematic branch detected, see MPC860 C0 errata"); |
| break; |
| default: |
| srr1_set = 0; |
| error("internal error - program_interrupt - reason %d not implemented", reason); |
| break; |
| } |
| TRACE(trace_interrupts, ("program interrupt - cia=0x%lx SRR1|=0x%lx\n", |
| (unsigned long)cia, |
| (unsigned long)srr1_set)); |
| cia = perform_oea_interrupt(processor, cia, 0x00700, 0, 0, 0, srr1_set); |
| cpu_restart(processor, cia); |
| } |
| |
| default: |
| error("internal error - program_interrupt - bad switch"); |
| |
| } |
| } |
| |
| |
| INLINE_INTERRUPTS\ |
| (void) |
| floating_point_unavailable_interrupt(cpu *processor, |
| unsigned_word cia) |
| { |
| switch (CURRENT_ENVIRONMENT) { |
| |
| case USER_ENVIRONMENT: |
| case VIRTUAL_ENVIRONMENT: |
| cpu_error(processor, cia, "floating-point unavailable interrupt"); |
| |
| case OPERATING_ENVIRONMENT: |
| TRACE(trace_interrupts, ("floating-point unavailable interrupt - cia=0x%lx\n", |
| (unsigned long)cia)); |
| cia = perform_oea_interrupt(processor, cia, 0x00800, 0, 0, 0, 0); |
| cpu_restart(processor, cia); |
| |
| default: |
| error("internal error - floating_point_unavailable_interrupt - bad switch"); |
| |
| } |
| } |
| |
| |
| INLINE_INTERRUPTS\ |
| (void) |
| system_call_interrupt(cpu *processor, |
| unsigned_word cia) |
| { |
| TRACE(trace_interrupts, ("system-call interrupt - cia=0x%lx\n", (unsigned long)cia)); |
| |
| switch (CURRENT_ENVIRONMENT) { |
| |
| case USER_ENVIRONMENT: |
| case VIRTUAL_ENVIRONMENT: |
| os_emul_system_call(processor, cia); |
| cpu_restart(processor, cia+4); |
| |
| case OPERATING_ENVIRONMENT: |
| cia = perform_oea_interrupt(processor, cia+4, 0x00c00, 0, 0, 0, 0); |
| cpu_restart(processor, cia); |
| |
| default: |
| error("internal error - system_call_interrupt - bad switch"); |
| |
| } |
| } |
| |
| INLINE_INTERRUPTS\ |
| (void) |
| floating_point_assist_interrupt(cpu *processor, |
| unsigned_word cia) |
| { |
| switch (CURRENT_ENVIRONMENT) { |
| |
| case USER_ENVIRONMENT: |
| case VIRTUAL_ENVIRONMENT: |
| cpu_error(processor, cia, "floating-point assist interrupt"); |
| |
| case OPERATING_ENVIRONMENT: |
| TRACE(trace_interrupts, ("floating-point assist interrupt - cia=0x%lx\n", (unsigned long)cia)); |
| cia = perform_oea_interrupt(processor, cia, 0x00e00, 0, 0, 0, 0); |
| cpu_restart(processor, cia); |
| |
| default: |
| error("internal error - floating_point_assist_interrupt - bad switch"); |
| |
| } |
| } |
| |
| |
| |
| /* handle an externally generated event or an interrupt that has just |
| been enabled through changes to the MSR. */ |
| |
| STATIC_INLINE_INTERRUPTS\ |
| (void) |
| deliver_hardware_interrupt(void *data) |
| { |
| cpu *processor = (cpu*)data; |
| interrupts *ints = cpu_interrupts(processor); |
| ints->delivery_scheduled = NULL; |
| if ((cpu_registers(processor)->msr & (msr_floating_point_exception_mode_0 |
| | msr_floating_point_exception_mode_1)) |
| && cpu_registers(processor)->fpscr & fpscr_fex) { |
| msreg srr1_set = srr1_floating_point_enabled | srr1_subsequent_instruction; |
| unsigned_word cia = cpu_get_program_counter(processor); |
| unsigned_word nia = perform_oea_interrupt(processor, |
| cia, 0x00700, 0, 0, 0, srr1_set); |
| cpu_set_program_counter(processor, nia); |
| } |
| else if (cpu_registers(processor)->msr & msr_external_interrupt_enable) { |
| /* external interrupts have a high priority and remain pending */ |
| if (ints->pending_interrupts & external_interrupt_pending) { |
| unsigned_word cia = cpu_get_program_counter(processor); |
| unsigned_word nia = perform_oea_interrupt(processor, |
| cia, 0x00500, 0, 0, 0, 0); |
| TRACE(trace_interrupts, ("external interrupt - cia=0x%lx\n", (unsigned long)cia)); |
| cpu_set_program_counter(processor, nia); |
| } |
| /* decrementer interrupts have a lower priority and are once only */ |
| else if (ints->pending_interrupts & decrementer_interrupt_pending) { |
| unsigned_word cia = cpu_get_program_counter(processor); |
| unsigned_word nia = perform_oea_interrupt(processor, |
| cia, 0x00900, 0, 0, 0, 0); |
| TRACE(trace_interrupts, ("decrementer interrupt - cia 0x%lx, time %ld\n", |
| (unsigned long)cia, |
| (unsigned long)event_queue_time(psim_event_queue(cpu_system(processor))) |
| )); |
| cpu_set_program_counter(processor, nia); |
| ints->pending_interrupts &= ~decrementer_interrupt_pending; |
| } |
| } |
| } |
| |
| STATIC_INLINE_INTERRUPTS\ |
| (void) |
| schedule_hardware_interrupt_delivery(cpu *processor) |
| { |
| interrupts *ints = cpu_interrupts(processor); |
| if (ints->delivery_scheduled == NULL) { |
| ints->delivery_scheduled = |
| event_queue_schedule(psim_event_queue(cpu_system(processor)), |
| 0, deliver_hardware_interrupt, processor); |
| } |
| } |
| |
| |
| INLINE_INTERRUPTS\ |
| (void) |
| check_masked_interrupts(cpu *processor) |
| { |
| if (((cpu_registers(processor)->msr & (msr_floating_point_exception_mode_0 |
| | msr_floating_point_exception_mode_1)) |
| && cpu_registers(processor)->fpscr & fpscr_fex) |
| || ((cpu_registers(processor)->msr & msr_external_interrupt_enable) |
| && (cpu_interrupts(processor)->pending_interrupts))) |
| schedule_hardware_interrupt_delivery(processor); |
| } |
| |
| INLINE_INTERRUPTS\ |
| (void) |
| decrementer_interrupt(cpu *processor) |
| { |
| interrupts *ints = cpu_interrupts(processor); |
| ints->pending_interrupts |= decrementer_interrupt_pending; |
| if (cpu_registers(processor)->msr & msr_external_interrupt_enable) { |
| schedule_hardware_interrupt_delivery(processor); |
| } |
| } |
| |
| INLINE_INTERRUPTS\ |
| (void) |
| external_interrupt(cpu *processor, |
| int is_asserted) |
| { |
| interrupts *ints = cpu_interrupts(processor); |
| if (is_asserted) { |
| if (!(ints->pending_interrupts & external_interrupt_pending)) { |
| ints->pending_interrupts |= external_interrupt_pending; |
| if (cpu_registers(processor)->msr & msr_external_interrupt_enable) |
| schedule_hardware_interrupt_delivery(processor); |
| } |
| else { |
| /* check that we haven't missed out on a chance to deliver an |
| interrupt */ |
| ASSERT(!(cpu_registers(processor)->msr & msr_external_interrupt_enable)); |
| } |
| } |
| else { |
| ints->pending_interrupts &= ~external_interrupt_pending; |
| } |
| } |
| |
| #endif /* _INTERRUPTS_C_ */ |