/* Copyright 2016-2024 melonDS team This file is part of melonDS. melonDS 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. melonDS 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 melonDS. If not, see http://www.gnu.org/licenses/. */ #include #include "NDS.h" #include "ARMInterpreter.h" #include "ARMInterpreter_ALU.h" #include "ARMInterpreter_Branch.h" #include "ARMInterpreter_LoadStore.h" #include "Platform.h" #ifdef GDBSTUB_ENABLED #include "debug/GdbStub.h" #endif namespace melonDS::ARMInterpreter { using Platform::Log; using Platform::LogLevel; void A_UNK(ARM* cpu) { cpu->AddCycles_C(); Log(LogLevel::Warn, "undefined ARM%d instruction %08X @ %08X\n", cpu->Num?7:9, cpu->CurInstr, cpu->R[15]-8); #ifdef GDBSTUB_ENABLED cpu->GdbStub.Enter(cpu->GdbStub.IsConnected(), Gdb::TgtStatus::FaultInsn, cpu->R[15]-8); #endif //for (int i = 0; i < 16; i++) printf("R%d: %08X\n", i, cpu->R[i]); //NDS::Halt(); u32 oldcpsr = cpu->CPSR; cpu->CPSR &= ~0xBF; cpu->CPSR |= 0x9B; cpu->UpdateMode(oldcpsr, cpu->CPSR); cpu->R_UND[2] = oldcpsr; cpu->R[14] = cpu->R[15] - 4; cpu->JumpTo(cpu->ExceptionBase + 0x04); } void T_UNK(ARM* cpu) { cpu->AddCycles_C(); Log(LogLevel::Warn, "undefined THUMB%d instruction %04X @ %08X\n", cpu->Num?7:9, cpu->CurInstr, cpu->R[15]-4); #ifdef GDBSTUB_ENABLED cpu->GdbStub.Enter(cpu->GdbStub.IsConnected(), Gdb::TgtStatus::FaultInsn, cpu->R[15]-4); #endif //NDS::Halt(); u32 oldcpsr = cpu->CPSR; cpu->CPSR &= ~0xBF; cpu->CPSR |= 0x9B; cpu->UpdateMode(oldcpsr, cpu->CPSR); cpu->R_UND[2] = oldcpsr; cpu->R[14] = cpu->R[15] - 2; cpu->JumpTo(cpu->ExceptionBase + 0x04); } void A_BKPT(ARM* cpu) { if (cpu->Num == 1) return A_UNK(cpu); // checkme Log(LogLevel::Warn, "BKPT: "); // combine with the prefetch abort warning message ((ARMv5*)cpu)->PrefetchAbort(); } void A_MSR_IMM(ARM* cpu) { if ((cpu->Num != 1) && (cpu->CurInstr & ((0x7<<16)|(1<<22)))) cpu->AddCycles_CI(2); // arm9 cpsr_sxc & spsr else cpu->AddCycles_C(); u32* psr; if (cpu->CurInstr & (1<<22)) { switch (cpu->CPSR & 0x1F) { case 0x11: psr = &cpu->R_FIQ[7]; break; case 0x12: psr = &cpu->R_IRQ[2]; break; case 0x13: psr = &cpu->R_SVC[2]; break; case 0x14: case 0x15: case 0x16: case 0x17: psr = &cpu->R_ABT[2]; break; case 0x18: case 0x19: case 0x1A: case 0x1B: psr = &cpu->R_UND[2]; break; default: return; } } else psr = &cpu->CPSR; u32 oldpsr = *psr; u32 mask = 0; if (cpu->CurInstr & (1<<16)) mask |= 0x000000FF; //if (cpu->CurInstr & (1<<17)) mask |= 0x0000FF00; // unused by arm 7 & 9 //if (cpu->CurInstr & (1<<18)) mask |= 0x00FF0000; // unused by arm 7 & 9 if (cpu->CurInstr & (1<<19)) mask |= ((cpu->Num==1) ? 0xF0000000 : 0xF8000000); if ((cpu->CPSR & 0x1F) == 0x10) mask &= 0xFFFFFF00; u32 val = ROR((cpu->CurInstr & 0xFF), ((cpu->CurInstr >> 7) & 0x1E)); // bit4 is forced to 1 val |= 0x00000010; *psr &= ~mask; *psr |= (val & mask); if (!(cpu->CurInstr & (1<<22))) cpu->UpdateMode(oldpsr, cpu->CPSR); if (cpu->CPSR & 0x20) [[unlikely]] { if (cpu->Num == 0) { cpu->R[15] += 2; // pc should actually increment by 4 one more time after switching to thumb mode without a pipeline flush, this gets the same effect. ((ARMv5*)cpu)->StartExec = &ARMv5::StartExecTHUMB; if (cpu->MRTrack.Type == MainRAMType::Null) ((ARMv5*)cpu)->FuncQueue[0] = ((ARMv5*)cpu)->StartExec; } else { Platform::Log(Platform::LogLevel::Warn, "UNIMPLEMENTED: MSR REG T bit change on ARM7\n"); cpu->CPSR &= ~0x20; // keep it from crashing the emulator at least } } } void A_MSR_REG(ARM* cpu) { if (cpu->Num == 0) ((ARMv5*)cpu)->HandleInterlocksExecute(cpu->CurInstr & 0xF); if ((cpu->Num != 1) && (cpu->CurInstr & ((0x7<<16)|(1<<22)))) cpu->AddCycles_CI(2); // arm9 cpsr_sxc & spsr else cpu->AddCycles_C(); u32* psr; if (cpu->CurInstr & (1<<22)) { switch (cpu->CPSR & 0x1F) { case 0x11: psr = &cpu->R_FIQ[7]; break; case 0x12: psr = &cpu->R_IRQ[2]; break; case 0x13: psr = &cpu->R_SVC[2]; break; case 0x14: case 0x15: case 0x16: case 0x17: psr = &cpu->R_ABT[2]; break; case 0x18: case 0x19: case 0x1A: case 0x1B: psr = &cpu->R_UND[2]; break; default: return; } } else psr = &cpu->CPSR; u32 oldpsr = *psr; u32 mask = 0; if (cpu->CurInstr & (1<<16)) mask |= 0x000000FF; //if (cpu->CurInstr & (1<<17)) mask |= 0x0000FF00; // unused by arm 7 & 9 //if (cpu->CurInstr & (1<<18)) mask |= 0x00FF0000; // unused by arm 7 & 9 if (cpu->CurInstr & (1<<19)) mask |= ((cpu->Num==1) ? 0xF0000000 : 0xF8000000); if ((cpu->CPSR & 0x1F) == 0x10) mask &= 0xFFFFFF00; u32 val = cpu->R[cpu->CurInstr & 0xF]; // bit4 is forced to 1 val |= 0x00000010; *psr &= ~mask; *psr |= (val & mask); if (!(cpu->CurInstr & (1<<22))) cpu->UpdateMode(oldpsr, cpu->CPSR); if (cpu->CPSR & 0x20) [[unlikely]] { if (cpu->Num == 0) { cpu->R[15] += 2; // pc should actually increment by 4 one more time after switching to thumb mode without a pipeline flush, this gets the same effect. ((ARMv5*)cpu)->StartExec = &ARMv5::StartExecTHUMB; if (cpu->MRTrack.Type == MainRAMType::Null) ((ARMv5*)cpu)->FuncQueue[0] = ((ARMv5*)cpu)->StartExec; } else { Platform::Log(Platform::LogLevel::Warn, "UNIMPLEMENTED: MSR REG T bit change on ARM7\n"); cpu->CPSR &= ~0x20; // keep it from crashing the emulator at least } } } void A_MRS(ARM* cpu) { u32 psr; if (cpu->CurInstr & (1<<22)) { switch (cpu->CPSR & 0x1F) { case 0x11: psr = cpu->R_FIQ[7]; break; case 0x12: psr = cpu->R_IRQ[2]; break; case 0x13: psr = cpu->R_SVC[2]; break; case 0x14: case 0x15: case 0x16: case 0x17: psr = cpu->R_ABT[2]; break; case 0x18: case 0x19: case 0x1A: case 0x1B: psr = cpu->R_UND[2]; break; default: psr = cpu->CPSR; break; } } else psr = cpu->CPSR; if (cpu->Num != 1) // arm9 { cpu->AddCycles_CI(2); // 1 X ((ARMv5*)cpu)->AddCycles_MW(2); // 2 M } else cpu->AddCycles_C(); // arm7 if (((cpu->CurInstr>>12) & 0xF) == 15) { if (cpu->Num == 1) // doesn't seem to jump on the arm9? checkme cpu->JumpTo(psr & ~0x1); // checkme: this shouldn't be able to switch to thumb? } else cpu->R[(cpu->CurInstr>>12) & 0xF] = psr; } void A_MCR(ARM* cpu) { if ((cpu->CPSR & 0x1F) == 0x10) return A_UNK(cpu); u32 cp = (cpu->CurInstr >> 8) & 0xF; u32 op = (cpu->CurInstr >> 21) & 0x7; u32 cn = (cpu->CurInstr >> 16) & 0xF; u32 cm = cpu->CurInstr & 0xF; u32 cpinfo = (cpu->CurInstr >> 5) & 0x7; u32 val = cpu->R[(cpu->CurInstr>>12)&0xF]; if (((cpu->CurInstr>>12) & 0xF) == 15) val += 4; if (cpu->Num == 0) ((ARMv5*)cpu)->HandleInterlocksExecute((cpu->CurInstr>>12)&0xF); if (cpu->Num==0 && cp==15) { ((ARMv5*)cpu)->CP15Write((cn<<8)|(cm<<4)|cpinfo|(op<<12), val); // TODO: IF THIS RAISES AN EXCEPTION WE DO A DOUBLE CODE FETCH; FIX THAT } else if (cpu->Num==1 && cp==14) { Log(LogLevel::Debug, "MCR p14,%d,%d,%d on ARM7\n", cn, cm, cpinfo); } else { Log(LogLevel::Warn, "bad MCR opcode p%d, %d, reg, c%d, c%d, %d on ARM%d\n", cp, op, cn, cm, cpinfo, cpu->Num?7:9); return A_UNK(cpu); // TODO: check what kind of exception it really is } // TODO: SINCE THIS DOES A CODE FETCH WE NEED TO DELAY ANY MPU UPDATES UNTIL *AFTER* THE CODE FETCH if (cpu->Num==0) cpu->AddCycles_CI(5); // checkme else /* ARM7 */ cpu->AddCycles_CI(1 + 1); // TODO: checkme } void A_MRC(ARM* cpu) { if ((cpu->CPSR & 0x1F) == 0x10) return A_UNK(cpu); u32 cp = (cpu->CurInstr >> 8) & 0xF; u32 op = (cpu->CurInstr >> 21) & 0x7; u32 cn = (cpu->CurInstr >> 16) & 0xF; u32 cm = cpu->CurInstr & 0xF; u32 cpinfo = (cpu->CurInstr >> 5) & 0x7; u32 rd = (cpu->CurInstr>>12) & 0xF; if (cpu->Num==0 && cp==15) { if (rd != 15) cpu->R[rd] = ((ARMv5*)cpu)->CP15Read((cn<<8)|(cm<<4)|cpinfo|(op<<12)); else { // r15 updates the top 4 bits of the cpsr, done to "allow for conditional branching based on coprocessor status" u32 flags = ((ARMv5*)cpu)->CP15Read((cn<<8)|(cm<<4)|cpinfo|(op<<12)) & 0xF0000000; // TODO: IF THIS RAISES AN EXCEPTION WE DO A DOUBLE CODE FETCH; FIX THAT cpu->CPSR = (cpu->CPSR & ~0xF0000000) | flags; } } else if (cpu->Num==1 && cp==14) { Log(LogLevel::Debug, "MRC p14,%d,%d,%d on ARM7\n", cn, cm, cpinfo); } else { Log(LogLevel::Warn, "bad MRC opcode p%d, %d, reg, c%d, c%d, %d on ARM%d\n", cp, op, cn, cm, cpinfo, cpu->Num?7:9); return A_UNK(cpu); // TODO: check what kind of exception it really is } if (cpu->Num != 1) { cpu->AddCycles_CI(2); // 1 Execute cycle ((ARMv5*)cpu)->AddCycles_MW(2); // 2 Memory cycles ((ARMv5*)cpu)->SetupInterlock((cpu->CurInstr >> 12) & 0xF); } else cpu->AddCycles_CI(2 + 1); // TODO: checkme } void A_SVC(ARM* cpu) // A_SWI { cpu->AddCycles_C(); u32 oldcpsr = cpu->CPSR; cpu->CPSR &= ~0xBF; cpu->CPSR |= 0x93; cpu->UpdateMode(oldcpsr, cpu->CPSR); cpu->R_SVC[2] = oldcpsr; cpu->R[14] = cpu->R[15] - 4; cpu->JumpTo(cpu->ExceptionBase + 0x08); } void T_SVC(ARM* cpu) // T_SWI { cpu->AddCycles_C(); u32 oldcpsr = cpu->CPSR; cpu->CPSR &= ~0xBF; cpu->CPSR |= 0x93; cpu->UpdateMode(oldcpsr, cpu->CPSR); cpu->R_SVC[2] = oldcpsr; cpu->R[14] = cpu->R[15] - 2; cpu->JumpTo(cpu->ExceptionBase + 0x08); } #define INSTRFUNC_PROTO(x) void (*x)(ARM* cpu) #include "ARM_InstrTable.h" #undef INSTRFUNC_PROTO }