duckstation

duckstation, but archived from the revision just before upstream changed it to a proprietary software project, this version is the libre one
git clone https://git.neptards.moe/u3shit/duckstation.git
Log | Files | Refs | README | LICENSE

interrupt_controller.cpp (3423B)


      1 // SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
      2 // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
      3 
      4 #include "interrupt_controller.h"
      5 #include "cpu_core.h"
      6 
      7 #include "util/state_wrapper.h"
      8 
      9 #include "common/log.h"
     10 
     11 Log_SetChannel(InterruptController);
     12 
     13 namespace InterruptController {
     14 
     15 static constexpr u32 REGISTER_WRITE_MASK = (u32(1) << NUM_IRQS) - 1;
     16 static constexpr u32 DEFAULT_INTERRUPT_MASK = 0;
     17 
     18 static void UpdateCPUInterruptRequest();
     19 
     20 static u32 s_interrupt_status_register = 0;
     21 static u32 s_interrupt_mask_register = DEFAULT_INTERRUPT_MASK;
     22 static u32 s_interrupt_line_state = 0;
     23 
     24 [[maybe_unused]] static constexpr std::array<const char*, static_cast<size_t>(IRQ::MaxCount)> s_irq_names = {
     25   {"VBLANK", "GPU", "CDROM", "DMA", "TMR0", "TMR1", "TMR2", "PAD", "SIO", "SPU", "IRQ10"}};
     26 
     27 } // namespace InterruptController
     28 
     29 void InterruptController::Reset()
     30 {
     31   s_interrupt_status_register = 0;
     32   s_interrupt_mask_register = DEFAULT_INTERRUPT_MASK;
     33   s_interrupt_line_state = 0;
     34 }
     35 
     36 bool InterruptController::DoState(StateWrapper& sw)
     37 {
     38   sw.Do(&s_interrupt_status_register);
     39   sw.Do(&s_interrupt_mask_register);
     40   sw.DoEx(&s_interrupt_line_state, 63, s_interrupt_status_register);
     41 
     42   return !sw.HasError();
     43 }
     44 
     45 void InterruptController::SetLineState(IRQ irq, bool state)
     46 {
     47   // Interupts are edge-triggered, so only set the flag in the status register on a 0-1 transition.
     48   const u32 bit = (1u << static_cast<u32>(irq));
     49   const u32 prev_state = s_interrupt_line_state;
     50   s_interrupt_line_state = (s_interrupt_line_state & ~bit) | (state ? bit : 0u);
     51   if (s_interrupt_line_state == prev_state)
     52     return;
     53 
     54 #ifdef _DEBUG
     55   if (!(prev_state & bit) && state)
     56     DEBUG_LOG("{} IRQ triggered", s_irq_names[static_cast<size_t>(irq)]);
     57   else if ((prev_state & bit) && !state)
     58     DEBUG_LOG("{} IRQ line inactive", s_irq_names[static_cast<size_t>(irq)]);
     59 #endif
     60 
     61   s_interrupt_status_register |= (state ? (prev_state ^ s_interrupt_line_state) : 0u) & s_interrupt_line_state;
     62   UpdateCPUInterruptRequest();
     63 }
     64 
     65 u32 InterruptController::ReadRegister(u32 offset)
     66 {
     67   switch (offset)
     68   {
     69     case 0x00: // I_STATUS
     70       return s_interrupt_status_register;
     71 
     72     case 0x04: // I_MASK
     73       return s_interrupt_mask_register;
     74 
     75     [[unlikely]] default:
     76       ERROR_LOG("Invalid read at offset 0x{:08X}", offset);
     77       return UINT32_C(0xFFFFFFFF);
     78   }
     79 }
     80 
     81 void InterruptController::WriteRegister(u32 offset, u32 value)
     82 {
     83   switch (offset)
     84   {
     85     case 0x00: // I_STATUS
     86     {
     87 #ifdef _DEBUG
     88       const u32 cleared_bits = (s_interrupt_status_register & ~value);
     89       for (u32 i = 0; i < static_cast<u32>(IRQ::MaxCount); i++)
     90       {
     91         if (cleared_bits & (1u << i))
     92           DEBUG_LOG("{} IRQ cleared", s_irq_names[i]);
     93       }
     94 #endif
     95 
     96       s_interrupt_status_register = s_interrupt_status_register & (value & REGISTER_WRITE_MASK);
     97       UpdateCPUInterruptRequest();
     98     }
     99     break;
    100 
    101     case 0x04: // I_MASK
    102     {
    103       DEBUG_LOG("Interrupt mask <- 0x{:08X}", value);
    104       s_interrupt_mask_register = value & REGISTER_WRITE_MASK;
    105       UpdateCPUInterruptRequest();
    106     }
    107     break;
    108 
    109     default:
    110       [[unlikely]] ERROR_LOG("Invalid write at offset 0x{:08X}", offset);
    111       break;
    112   }
    113 }
    114 
    115 ALWAYS_INLINE_RELEASE void InterruptController::UpdateCPUInterruptRequest()
    116 {
    117   const bool state = (s_interrupt_status_register & s_interrupt_mask_register) != 0;
    118   CPU::SetIRQRequest(state);
    119 }