diff --git a/CMakeLists.txt b/CMakeLists.txt index 1c6eef67..1eb3ced4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -84,6 +84,19 @@ if (DPPC_68K_DEBUGGER) add_subdirectory(thirdparty/capstone EXCLUDE_FROM_ALL) endif() +option (DPPC_SUPPORT_PPC_LE "Enable PPC little endian mode" OFF) +option (DPPC_SUPPORT_MEM_LE "Enable memory controller little endian mode" OFF) +if (DPPC_SUPPORT_PPC_LE) + ADD_DEFINITIONS(-DSUPPORTS_PPC_LITTLE_ENDIAN_MODE=1) +else() + ADD_DEFINITIONS(-DSUPPORTS_PPC_LITTLE_ENDIAN_MODE=0) +endif() +if (DPPC_SUPPORT_MEM_LE) + ADD_DEFINITIONS(-DSUPPORTS_MEMORY_CTRL_ENDIAN_MODE=1) +else() + ADD_DEFINITIONS(-DSUPPORTS_MEMORY_CTRL_ENDIAN_MODE=0) +endif() + add_subdirectory("${PROJECT_SOURCE_DIR}/core") add_subdirectory("${PROJECT_SOURCE_DIR}/cpu/ppc/") add_subdirectory("${PROJECT_SOURCE_DIR}/debugger/") diff --git a/benchmark/bench1.cpp b/benchmark/bench1.cpp index 2aa963cf..71f81ccd 100644 --- a/benchmark/bench1.cpp +++ b/benchmark/bench1.cpp @@ -115,6 +115,30 @@ int main(int argc, char** argv) { power_on = true; ppc_exec_until(0xC4); +#if SUPPORTS_PPC_LITTLE_ENDIAN_MODE + #warning PPC endian mode enabled + LOG_F(INFO, "PPC endian mode enabled"); +#else + #warning PPC endian mode disabled + LOG_F(INFO, "PPC endian mode disabled"); +#endif + +#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE + #warning Memory endian mode enabled + LOG_F(INFO, "Memory endian mode enabled"); +#else + #warning Memory endian mode disabled + LOG_F(INFO, "Memory endian mode disabled"); +#endif + +#ifdef LOG_INSTRUCTIONS + #warning Log instructions enabled + LOG_F(INFO, "Log instructions enabled"); +#else + #warning Log instructions disabled + LOG_F(INFO, "Log instructions disabled"); +#endif + LOG_F(INFO, "Checksum: 0x%08X", ppc_state.gpr[3]); uint32_t checksum = ppc_state.gpr[3]; diff --git a/cpu/ppc/ppcemu.h b/cpu/ppc/ppcemu.h index fc0f3dae..41f46334 100644 --- a/cpu/ppc/ppcemu.h +++ b/cpu/ppc/ppcemu.h @@ -48,7 +48,7 @@ enum EXEC_MODE:uint32_t { jit = 3 }; -enum endian_switch { big_end = 0, little_end = 1 }; +typedef enum { big_end = 0, little_end = 1 } endian_switch; typedef void (*PPCOpcode)(uint32_t opcode); @@ -82,6 +82,9 @@ typedef struct struct_ppc_state { uint32_t msr; uint32_t sr[16]; bool reserve; // reserve bit used for lwarx and stcwx +#if SUPPORTS_PPC_LITTLE_ENDIAN_MODE + bool is_LE; +#endif } SetPRS; extern SetPRS ppc_state; @@ -357,7 +360,8 @@ enum Po_Cause : int { po_enter_debugger, po_entered_debugger, po_signal_interrupt, - po_benchmark_exception + po_benchmark_exception, + po_endian_switch, }; extern bool power_on; @@ -378,7 +382,13 @@ extern bool is_deterministic; extern uint32_t ppc_next_instruction_address; inline uint32_t ppc_read_instruction(const uint8_t* ptr) { +#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE + extern MemCtrlBase* mem_ctrl_instance; + bool needs_swap = mem_ctrl_instance->needs_swap_endian(false); + return needs_swap ? READ_DWORD_LE_A(ptr) : READ_DWORD_BE_A(ptr); +#else return READ_DWORD_BE_A(ptr); +#endif } // Profiling Stats @@ -689,6 +699,7 @@ extern void ppc_exec_dbg(uint32_t start_addr, uint32_t size); extern PPCOpcode* ppc_opcode_grabber; extern void ppc_msr_did_change(uint32_t old_msr_val, uint32_t new_msr_val, bool set_next_instruction_address = true); +extern void ppc_change_endian(bool newLE); /* debugging support API */ uint64_t get_reg(std::string reg_name); /* get content of the register reg_name */ diff --git a/cpu/ppc/ppcexceptions.cpp b/cpu/ppc/ppcexceptions.cpp index 11291d65..9714694e 100644 --- a/cpu/ppc/ppcexceptions.cpp +++ b/cpu/ppc/ppcexceptions.cpp @@ -119,6 +119,7 @@ void ppc_exception_handler(Except_Type exception_type, uint32_t srr1_bits) { /* copy MSR[ILE] to MSR[LE] */ if (!is_601) { new_msr_val = (new_msr_val & ~MSR::LE) | !!(new_msr_val & MSR::ILE); + ppc_change_endian((new_msr_val & MSR::LE) != 0); } // Don't clobber the ppc_next_instruction_address value ppc_msr_did_change(old_msr_val, new_msr_val, false); @@ -138,6 +139,9 @@ void ppc_exception_handler(Except_Type exception_type, uint32_t srr1_bits) { mmu_change_mode(); if (exception_type != Except_Type::EXC_EXT_INT && exception_type != Except_Type::EXC_DECR) { + if (!power_on && power_off_reason == po_endian_switch) [[unlikely]] { + power_on = true; + } longjmp(exc_env, 2); /* return to the main execution loop. */ } } diff --git a/cpu/ppc/ppcexec.cpp b/cpu/ppc/ppcexec.cpp index 039aad3d..58dbb26e 100644 --- a/cpu/ppc/ppcexec.cpp +++ b/cpu/ppc/ppcexec.cpp @@ -227,6 +227,23 @@ void ppc_msr_did_change(uint32_t old_msr_val, uint32_t new_msr_val, bool set_nex } } +void ppc_change_endian(bool newLE) { +#if SUPPORTS_PPC_LITTLE_ENDIAN_MODE + if (ppc_state.is_LE != newLE) { + LOG_F(INFO, "changed endian to %s", newLE ? "LE" : "BE"); + ppc_state.is_LE = newLE; + power_on = false; + power_off_reason = po_endian_switch; + } +#else + if (newLE) { + LOG_F(ERROR, "unsupported endian %s", newLE ? "LE" : "BE"); + power_on = false; + power_off_reason = po_enter_debugger; + } +#endif +} + PPCOpcode* ppc_opcode_grabber = OpcodeGrabberNoFPU; /** Exception helpers. */ @@ -331,7 +348,7 @@ typedef enum { } ppc_exec_type_t; // inner interpreter loop -template +template static void ppc_exec_inner(uint32_t start_addr, uint32_t size) { uint64_t max_cycles = 0; @@ -367,7 +384,10 @@ static void ppc_exec_inner(uint32_t start_addr, uint32_t size) // define next execution block eb_start = ppc_next_instruction_address; if (!(exec_flags & EXEF_RFI) && (eb_start & PPC_PAGE_MASK) == page_start) { - INCPC((int)eb_start - (int)ppc_state.pc); + if (endian == big_end) + INCPC((int)eb_start - (int)ppc_state.pc); + else + pc_real = mmu_translate_imem(eb_start ATPCP); // &pcp } else { page_start = eb_start & PPC_PAGE_MASK; eb_end = page_start + PPC_PAGE_SIZE - 1; @@ -375,9 +395,12 @@ static void ppc_exec_inner(uint32_t start_addr, uint32_t size) } ppc_state.pc = eb_start; exec_flags = 0; - } else { [[likely]] + } else [[likely]] { ppc_state.pc += 4; - INCPC(4); + if (endian == big_end) + INCPC(4); + else + pc_real = mmu_translate_imem(ppc_state.pc ATPCP); // &pcp } if (exec_type == until) @@ -389,7 +412,8 @@ static void ppc_exec_inner(uint32_t start_addr, uint32_t size) /** Execute PPC code as long as power is on. */ // inner interpreter loop -template void ppc_exec_inner
(uint32_t start_addr, uint32_t size); +template void ppc_exec_inner(uint32_t start_addr, uint32_t size); +template void ppc_exec_inner(uint32_t start_addr, uint32_t size); // outer interpreter loop void ppc_exec() @@ -401,7 +425,17 @@ void ppc_exec() } while (power_on) { - ppc_exec_inner
(0, 0); +#if SUPPORTS_PPC_LITTLE_ENDIAN_MODE + if (ppc_state.is_LE) + ppc_exec_inner(0, 0); + else +#endif + [[likely]] { + ppc_exec_inner(0, 0); + } + if (!power_on && power_off_reason == po_endian_switch) [[unlikely]] { + power_on = true; + } } } @@ -433,7 +467,8 @@ void ppc_exec_single() /** Execute PPC code until goal_addr is reached. */ // inner interpreter loop -template void ppc_exec_inner(uint32_t start_addr, uint32_t size); +template void ppc_exec_inner(uint32_t start_addr, uint32_t size); +template void ppc_exec_inner(uint32_t start_addr, uint32_t size); // outer interpreter loop void ppc_exec_until(volatile uint32_t goal_addr) { @@ -444,7 +479,17 @@ void ppc_exec_until(volatile uint32_t goal_addr) { } while (power_on) { - ppc_exec_inner(goal_addr, 0); +#if SUPPORTS_PPC_LITTLE_ENDIAN_MODE + if (ppc_state.is_LE) + ppc_exec_inner(goal_addr, 0); + else +#endif + [[likely]] { + ppc_exec_inner(goal_addr, 0); + } + if (!power_on && power_off_reason == po_endian_switch) [[unlikely]] { + power_on = true; + } if (ppc_state.pc == goal_addr) break; } @@ -453,7 +498,8 @@ void ppc_exec_until(volatile uint32_t goal_addr) { /** Execute PPC code until control is reached the specified region. */ // inner interpreter loop -template void ppc_exec_inner(uint32_t start_addr, uint32_t size); +template void ppc_exec_inner(uint32_t start_addr, uint32_t size); +template void ppc_exec_inner(uint32_t start_addr, uint32_t size); // outer interpreter loop void ppc_exec_dbg(volatile uint32_t start_addr, volatile uint32_t size) @@ -465,7 +511,17 @@ void ppc_exec_dbg(volatile uint32_t start_addr, volatile uint32_t size) } while (power_on && (ppc_state.pc < start_addr || ppc_state.pc >= start_addr + size)) { - ppc_exec_inner(start_addr, size); +#if SUPPORTS_PPC_LITTLE_ENDIAN_MODE + if (ppc_state.is_LE) + ppc_exec_inner(start_addr, size); + else +#endif + [[likely]] { + ppc_exec_inner(start_addr, size); + } + if (!power_on && power_off_reason == po_endian_switch) [[unlikely]] { + power_on = true; + } } } @@ -906,6 +962,7 @@ void ppc_cpu_init(MemCtrlBase* mem_ctrl, uint32_t cpu_version, bool do_include_6 } else { new_msr_val = MSR::IP; ppc_state.spr[SPR::DEC_S] = 0xFFFFFFFFUL; + ppc_change_endian((new_msr_val & MSR::LE) != 0); } ppc_msr_did_change(new_msr_val, new_msr_val, false); @@ -968,6 +1025,9 @@ static uint64_t reg_op(string& reg_name, uint64_t val, bool is_write) { if (is_write) { uint32_t old_msr_val = ppc_state.msr; uint32_t new_msr_val = (uint32_t)val; + if (!is_601) { + ppc_change_endian((new_msr_val & MSR::LE) != 0); + } ppc_msr_did_change(old_msr_val, new_msr_val, false); } return ppc_state.msr; @@ -1023,8 +1083,13 @@ static uint64_t reg_op(string& reg_name, uint64_t val, bool is_write) { case SPR::TBL_U : reg_num = SPR::TBL_S ; break; case SPR::TBU_U : reg_num = SPR::TBU_S ; break; } - if (is_write) + if (is_write) { ppc_state.spr[reg_num] = (uint32_t)val; + if (reg_num == SPR::HID0) + if (is_601) { + ppc_change_endian((val & 0x10000000) != 0); + } + } return ppc_state.spr[reg_num]; } } diff --git a/cpu/ppc/ppcmmu.cpp b/cpu/ppc/ppcmmu.cpp index a5113112..9ab3ac5e 100644 --- a/cpu/ppc/ppcmmu.cpp +++ b/cpu/ppc/ppcmmu.cpp @@ -85,6 +85,37 @@ AddressMapEntry last_ptab_area; /** Dummy pages for catching writes to physical read-only pages */ static std::array dummy_page; +#if SUPPORTS_PPC_LITTLE_ENDIAN_MODE || SUPPORTS_MEMORY_CTRL_ENDIAN_MODE +template +static inline uint32_t mem_munge_constant() { + switch (sizeof(T)) { + case 1: return 7; + case 2: return 6; + case 4: return 4; + case 8: return 0; + } +} + +template +static uint32_t mem_munge_address(uint32_t guest_va) { + if (sizeof(T) == sizeof(uint64_t)) { + // 64 bit access. do nothing, handle later. + return guest_va; + } + + uint32_t align_offset = guest_va & (sizeof(T) - 1); + if (align_offset == 0) + return guest_va ^ mem_munge_constant(); + + // align the address + guest_va &= ~(sizeof(T) - 1); + // munge it + guest_va ^= mem_munge_constant(); + // and subtract the offset + return guest_va - align_offset; +} +#endif + /** 601-style block address translation. */ static BATResult mpc601_block_address_translation(uint32_t la) { @@ -174,6 +205,10 @@ static inline uint8_t* calc_pteg_addr(uint32_t hash) pteg_addr = (sdr1_val & 0xFFFF0000) | (((sdr1_val & 0x1FF) << 16) & ((hash & 0x7FC00) << 6)) | ((hash & 0x3FF) << 6); +#if 0 && SUPPORTS_PPC_LITTLE_ENDIAN_MODE + if (ppc_state.is_LE) + pteg_addr ^= mem_munge_constant(); +#endif if (pteg_addr >= last_ptab_area.start && pteg_addr <= last_ptab_area.end) { return last_ptab_area.mem_ptr + (pteg_addr - last_ptab_area.start); @@ -195,6 +230,9 @@ static bool search_pteg(uint8_t* pteg_addr, uint8_t** ret_pte_addr, uint32_t vsi { /* construct PTE matching word */ uint32_t pte_check = 0x80000000 | (vsid << 7) | (pteg_num << 6) | (page_index >> 10); +#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE + bool swap = mem_ctrl_instance->needs_swap_endian(false); +#endif #ifdef MMU_INTEGRITY_CHECKS /* PTEG integrity check that ensures that all matching PTEs have @@ -217,7 +255,12 @@ static bool search_pteg(uint8_t* pteg_addr, uint8_t** ret_pte_addr, uint32_t vsi } #else for (int i = 0; i < 8; i++, pteg_addr += 8) { - if (pte_check == READ_DWORD_BE_A(pteg_addr)) { + if (pte_check == ( + #if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE + swap ? (READ_DWORD_LE_A(pteg_addr)) : + #endif + (READ_DWORD_BE_A(pteg_addr)) + )) { *ret_pte_addr = pteg_addr; return true; } @@ -269,7 +312,25 @@ static PATResult page_address_translation(uint32_t la, bool is_instr_fetch, } } +#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE + size_t pte_addr2S = (size_t)pte_addr; + #if 0 && SUPPORTS_PPC_LITTLE_ENDIAN_MODE + if (ppc_state.is_LE) { + pte_addr2S ^= mem_munge_constant(); + pte_addr2S += 4; + pte_addr2S ^= mem_munge_constant(); + } else + #endif + { + pte_addr2S += 4; + } + + uint8_t* pte_addr2 = (uint8_t*)pte_addr2S; + bool swap = mem_ctrl_instance->needs_swap_endian(false); + pte_word2 = swap ? (READ_DWORD_LE_A(pte_addr2)) : (READ_DWORD_BE_A(pte_addr2)); +#else pte_word2 = READ_DWORD_BE_A(pte_addr + 4); +#endif key = (((sr_val >> 29) & 1) & msr_pr) | (((sr_val >> 30) & 1) & (msr_pr ^ 1)); @@ -292,10 +353,23 @@ static PATResult page_address_translation(uint32_t la, bool is_instr_fetch, /* update R and C bits */ /* For simplicity, R is set on each access, C is set only for writes */ - pte_addr[6] |= 0x01; - if (is_write) { - pte_addr[7] |= 0x80; +#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE + if (swap) { + pte_addr2[1] |= 0x01; + if (is_write) { + pte_addr2[0] |= 0x80; + } + } else { + pte_addr2[2] |= 0x01; + if (is_write) { + pte_addr2[3] |= 0x80; + } } +#else + pte_addr[6] |= 0x01; + if (is_write) + pte_addr[7] |= 0x80; +#endif /* return physical address, access protection and C status */ return PATResult{ @@ -733,6 +807,12 @@ static inline TLBEntry* lookup_secondary_tlb(uint32_t guest_va, uint32_t tag) { uint8_t *mmu_translate_imem(uint32_t vaddr, uint32_t *paddr) { +#if SUPPORTS_PPC_LITTLE_ENDIAN_MODE + if (ppc_state.is_LE) { + vaddr ^= mem_munge_constant(); + } +#endif + TLBEntry *tlb1_entry, *tlb2_entry; uint8_t *host_va; @@ -1018,26 +1098,66 @@ void mmu_pat_ctx_changed() } } +#if SUPPORTS_PPC_LITTLE_ENDIAN_MODE + #if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE + #define ARGS_SWAP_MUNGED , bool needs_swap, bool munged + #define SWAP_MUNGED , needs_swap, munged + #else + #define ARGS_SWAP_MUNGED , bool munged + #define SWAP_MUNGED , munged + #endif +#else + #if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE + #define ARGS_SWAP_MUNGED , bool needs_swap, bool munged + #define SWAP_MUNGED , needs_swap, munged + #else + #define ARGS_SWAP_MUNGED + #define SWAP_MUNGED + #endif +#endif + // Forward declarations. template -static T read_unaligned(uint32_t opcode, uint32_t guest_va, uint8_t *host_va); +static T read_unaligned(uint32_t opcode, uint32_t guest_va, uint8_t *host_va ARGS_SWAP_MUNGED); template -static void write_unaligned(uint32_t opcode, uint32_t guest_va, uint8_t *host_va, T value); +static void write_unaligned(uint32_t opcode, uint32_t guest_va, uint8_t *host_va, T value ARGS_SWAP_MUNGED); template inline T mmu_read_vmem(uint32_t opcode, uint32_t guest_va) { +#if SUPPORTS_PPC_LITTLE_ENDIAN_MODE || SUPPORTS_MEMORY_CTRL_ENDIAN_MODE + bool munged = false; +#endif +#if SUPPORTS_PPC_LITTLE_ENDIAN_MODE + if (ppc_state.is_LE) { + guest_va = mem_munge_address(guest_va); + munged = true; + } +#endif + TLBEntry *tlb1_entry, *tlb2_entry; uint8_t *host_va; const uint32_t tag = guest_va & ~0xFFFUL; // look up guest virtual address in the primary TLB +#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE + bool needs_swap = false; +#endif tlb1_entry = &pCurDTLB1[(guest_va >> PPC_PAGE_SIZE_BITS) & tlb_size_mask]; if (tlb1_entry->tag == tag) { // primary TLB hit -> fast path #ifdef TLB_PROFILING num_primary_dtlb_hits++; #endif + +#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE + needs_swap = mem_ctrl_instance->needs_swap_endian(false); + if (needs_swap) { + guest_va = mem_munge_address(guest_va); + munged ^= 1; + } +#endif + host_va = (uint8_t *)(tlb1_entry->host_va_offs_r + guest_va); } else { // primary TLB miss -> look up address in the secondary TLB @@ -1062,30 +1182,69 @@ inline T mmu_read_vmem(uint32_t opcode, uint32_t guest_va) if (tlb2_entry->flags & TLBFlags::PAGE_MEM) { // is it a real memory region? // refill the primary TLB *tlb1_entry = *tlb2_entry; + +#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE + needs_swap = mem_ctrl_instance->needs_swap_endian(false); + if (needs_swap) { + guest_va = mem_munge_address(guest_va); + munged ^= 1; + } +#endif + host_va = (uint8_t *)(tlb1_entry->host_va_offs_r + guest_va); } else { // otherwise, it's an access to a memory-mapped device #ifdef MMU_PROFILING iomem_reads_total++; #endif + +#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE + needs_swap = mem_ctrl_instance->needs_swap_endian(tlb2_entry->rgn_desc); + if (needs_swap) { + guest_va = mem_munge_address(guest_va); + munged ^= 1; + } +#endif + if (sizeof(T) == 8) { if (guest_va & 3) ppc_alignment_exception(opcode, guest_va); + uint32_t valueLow = tlb2_entry->rgn_desc->devobj->read( + tlb2_entry->rgn_desc->start, + static_cast(guest_va - tlb2_entry->dev_base_va), + 4); + + uint32_t valueHigh = tlb2_entry->rgn_desc->devobj->read( + tlb2_entry->rgn_desc->start, + static_cast(guest_va + 4 - tlb2_entry->dev_base_va), + 4); + +#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE + if (needs_swap) { + uint32_t temp = valueHigh; + valueHigh = BYTESWAP_32(valueLow); + valueLow = BYTESWAP_32(temp); + } +#endif + return ( - ((T)tlb2_entry->rgn_desc->devobj->read(tlb2_entry->rgn_desc->start, - static_cast(guest_va - tlb2_entry->dev_base_va), - 4) << 32) | - tlb2_entry->rgn_desc->devobj->read(tlb2_entry->rgn_desc->start, - static_cast(guest_va + 4 - tlb2_entry->dev_base_va), - 4) + ((T)(valueLow) << 32) | valueHigh ); } else { - return ( + T value = ( tlb2_entry->rgn_desc->devobj->read(tlb2_entry->rgn_desc->start, - static_cast(guest_va - tlb2_entry->dev_base_va), - sizeof(T)) + static_cast(guest_va - tlb2_entry->dev_base_va), + sizeof(T)) ); + +#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE + if (needs_swap && sizeof(T) > 1) { + value = BYTESWAP_SIZED(value, sizeof(T)); + } +#endif + + return value; } } } @@ -1096,7 +1255,11 @@ inline T mmu_read_vmem(uint32_t opcode, uint32_t guest_va) // handle unaligned memory accesses if (sizeof(T) > 1 && (guest_va & (sizeof(T) - 1))) { - return read_unaligned(opcode, guest_va, host_va); +#if SUPPORTS_PPC_LITTLE_ENDIAN_MODE || SUPPORTS_MEMORY_CTRL_ENDIAN_MODE + if (munged) + guest_va = mem_munge_address(guest_va); +#endif + return read_unaligned(opcode, guest_va, host_va SWAP_MUNGED); } // handle aligned memory accesses @@ -1104,11 +1267,23 @@ inline T mmu_read_vmem(uint32_t opcode, uint32_t guest_va) case 1: return *host_va; case 2: - return READ_WORD_BE_A(host_va); + return +#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE + needs_swap ? (READ_WORD_LE_A(host_va)) : +#endif + (READ_WORD_BE_A(host_va)); case 4: - return READ_DWORD_BE_A(host_va); + return +#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE + needs_swap ? (READ_DWORD_LE_A(host_va)) : +#endif + (READ_DWORD_BE_A(host_va)); case 8: - return READ_QWORD_BE_A(host_va); + return +#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE + needs_swap ? (READ_QWORD_LE_A(host_va)) : +#endif + (READ_QWORD_BE_A(host_va)); } } @@ -1121,12 +1296,25 @@ template uint64_t mmu_read_vmem(uint32_t opcode, uint32_t guest_va); template inline void mmu_write_vmem(uint32_t opcode, uint32_t guest_va, T value) { +#if SUPPORTS_PPC_LITTLE_ENDIAN_MODE || SUPPORTS_MEMORY_CTRL_ENDIAN_MODE + bool munged = false; +#endif +#if SUPPORTS_PPC_LITTLE_ENDIAN_MODE + if (ppc_state.is_LE) { + guest_va = mem_munge_address(guest_va); + munged = true; + } +#endif + TLBEntry *tlb1_entry, *tlb2_entry; uint8_t *host_va; const uint32_t tag = guest_va & ~0xFFFUL; // look up guest virtual address in the primary TLB +#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE + bool needs_swap = false; +#endif tlb1_entry = &pCurDTLB1[(guest_va >> PPC_PAGE_SIZE_BITS) & tlb_size_mask]; if (tlb1_entry->tag == tag) { // primary TLB hit -> fast path #ifdef TLB_PROFILING @@ -1148,6 +1336,13 @@ inline void mmu_write_vmem(uint32_t opcode, uint32_t guest_va, T value) tlb2_entry->flags |= TLBFlags::PTE_SET_C; } } +#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE + needs_swap = mem_ctrl_instance->needs_swap_endian(false); + if (needs_swap) { + guest_va = mem_munge_address(guest_va); + munged ^= 1; + } +#endif host_va = (uint8_t *)(tlb1_entry->host_va_offs_w + guest_va); } else { // primary TLB miss -> look up address in the secondary TLB @@ -1184,25 +1379,61 @@ inline void mmu_write_vmem(uint32_t opcode, uint32_t guest_va, T value) if (tlb2_entry->flags & TLBFlags::PAGE_MEM) { // is it a real memory region? // refill the primary TLB *tlb1_entry = *tlb2_entry; + +#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE + needs_swap = mem_ctrl_instance->needs_swap_endian(false); + if (needs_swap) { + guest_va = mem_munge_address(guest_va); + munged ^= 1; + } +#endif + host_va = (uint8_t *)(tlb1_entry->host_va_offs_w + guest_va); } else { // otherwise, it's an access to a memory-mapped device #ifdef MMU_PROFILING iomem_writes_total++; #endif + +#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE + needs_swap = mem_ctrl_instance->needs_swap_endian(tlb2_entry->rgn_desc); + if (needs_swap) { + guest_va = mem_munge_address(guest_va); + munged ^= 1; + } +#endif + if (sizeof(T) == 8) { if (guest_va & 3) ppc_alignment_exception(opcode, guest_va); + uint32_t valueLow, valueHigh; + valueLow = value >> 32; + valueHigh = (uint32_t)value; +#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE + if (needs_swap) { + valueHigh = BYTESWAP_32(valueLow); + valueLow = (uint32_t)value; + valueLow = BYTESWAP_32(valueLow); + } +#endif + tlb2_entry->rgn_desc->devobj->write(tlb2_entry->rgn_desc->start, - static_cast(guest_va - tlb2_entry->dev_base_va), - value >> 32, 4); + static_cast(guest_va - tlb2_entry->dev_base_va), + valueLow, 4); tlb2_entry->rgn_desc->devobj->write(tlb2_entry->rgn_desc->start, - static_cast(guest_va + 4 - tlb2_entry->dev_base_va), - (uint32_t)value, 4); + static_cast(guest_va + 4 - tlb2_entry->dev_base_va), + valueHigh, 4); } else { + +#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE + if (needs_swap && sizeof(T) > 1) { + value = BYTESWAP_SIZED(value, sizeof(T)); + } +#endif + tlb2_entry->rgn_desc->devobj->write(tlb2_entry->rgn_desc->start, - static_cast(guest_va - tlb2_entry->dev_base_va), - value, sizeof(T)); + static_cast(guest_va - tlb2_entry->dev_base_va), + value, sizeof(T)); } return; } @@ -1212,9 +1443,21 @@ inline void mmu_write_vmem(uint32_t opcode, uint32_t guest_va, T value) dmem_writes_total++; #endif +#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE + // swap now if needed + if (needs_swap && sizeof(T) > 1) { + value = BYTESWAP_SIZED(value, sizeof(T)); + } +#endif + // handle unaligned memory accesses if (sizeof(T) > 1 && (guest_va & (sizeof(T) - 1))) { - write_unaligned(opcode, guest_va, host_va, value); + // unmunge the guest_va if it was munged +#if SUPPORTS_PPC_LITTLE_ENDIAN_MODE || SUPPORTS_MEMORY_CTRL_ENDIAN_MODE + if (munged) + guest_va = mem_munge_address(guest_va); +#endif + write_unaligned(opcode, guest_va, host_va, value SWAP_MUNGED); return; } @@ -1242,7 +1485,7 @@ template void mmu_write_vmem(uint32_t opcode, uint32_t guest_va, uint3 template void mmu_write_vmem(uint32_t opcode, uint32_t guest_va, uint64_t value); template -static T read_unaligned(uint32_t opcode, uint32_t guest_va, uint8_t *host_va) +static T read_unaligned(uint32_t opcode, uint32_t guest_va, uint8_t *host_va ARGS_SWAP_MUNGED) { if ((sizeof(T) == 8) && (guest_va & 3)) { #ifndef PPC_TESTS @@ -1261,8 +1504,38 @@ static T read_unaligned(uint32_t opcode, uint32_t guest_va, uint8_t *host_va) // Because such accesses suffer a performance penalty, they will be // presumably very rare so don't waste time optimizing the code below. for (int i = 0; i < sizeof(T); guest_va++, i++) { +#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE + if (needs_swap) { + T value = mmu_read_vmem(opcode, guest_va); + value <<= (i * 8); + result |= value; + } else +#endif + { result = (result << 8) | mmu_read_vmem(opcode, guest_va); } + } +#if SUPPORTS_PPC_LITTLE_ENDIAN_MODE || SUPPORTS_MEMORY_CTRL_ENDIAN_MODE + } else if (sizeof(T) == sizeof(uint64_t) && munged) { + // Munged host address for an unaligned 64-bit read. + // Check for cross-page read, to read the upper 32 bits correctly. + if (((guest_va & 0xFFF) + 12) > 0x1000) { + // Add the pre-munged address, as munging is a no-op for uint64_t, but not for uint32_t. + result = mmu_read_vmem(opcode, guest_va + mem_munge_address(8)); + } else { + result = + #if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE + needs_swap ? (READ_DWORD_LE_U(host_va + 8)) : + #endif + (READ_DWORD_BE_U(host_va + 8)); + } + result <<= 32; + result |= + #if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE + needs_swap ? (READ_DWORD_LE_U(host_va)) : + #endif + (READ_DWORD_BE_U(host_va)); +#endif } else { #ifdef MMU_PROFILING unaligned_reads++; @@ -1271,23 +1544,35 @@ static T read_unaligned(uint32_t opcode, uint32_t guest_va, uint8_t *host_va) case 1: return *host_va; case 2: - return READ_WORD_BE_U(host_va); + return +#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE + needs_swap ? (READ_WORD_LE_U(host_va)) : +#endif + (READ_WORD_BE_U(host_va)); case 4: - return READ_DWORD_BE_U(host_va); + return +#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE + needs_swap ? (READ_DWORD_LE_U(host_va)) : +#endif + (READ_DWORD_BE_U(host_va)); case 8: - return READ_QWORD_BE_U(host_va); + return +#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE + needs_swap ? (READ_QWORD_LE_U(host_va)) : +#endif + (READ_QWORD_BE_U(host_va)); } } return result; } // explicitely instantiate all required read_unaligned variants -template uint16_t read_unaligned(uint32_t opcode, uint32_t guest_va, uint8_t *host_va); -template uint32_t read_unaligned(uint32_t opcode, uint32_t guest_va, uint8_t *host_va); -template uint64_t read_unaligned(uint32_t opcode, uint32_t guest_va, uint8_t *host_va); +template uint16_t read_unaligned(uint32_t opcode, uint32_t guest_va, uint8_t* host_va ARGS_SWAP_MUNGED); +template uint32_t read_unaligned(uint32_t opcode, uint32_t guest_va, uint8_t* host_va ARGS_SWAP_MUNGED); +template uint64_t read_unaligned(uint32_t opcode, uint32_t guest_va, uint8_t* host_va ARGS_SWAP_MUNGED); template -static void write_unaligned(uint32_t opcode, uint32_t guest_va, uint8_t *host_va, T value) +static void write_unaligned(uint32_t opcode, uint32_t guest_va, uint8_t *host_va, T value ARGS_SWAP_MUNGED) { if ((sizeof(T) == 8) && (guest_va & 3)) { #ifndef PPC_TESTS @@ -1309,6 +1594,36 @@ static void write_unaligned(uint32_t opcode, uint32_t guest_va, uint8_t *host_va for (int i = 0; i < sizeof(T); shift -= 8, guest_va++, i++) { mmu_write_vmem(opcode, guest_va, (value >> shift) & 0xFF); } +#if SUPPORTS_PPC_LITTLE_ENDIAN_MODE || SUPPORTS_MEMORY_CTRL_ENDIAN_MODE + } else if (sizeof(T) == sizeof(uint64_t) && munged) { + // Munged host address for an unaligned 64-bit write. + // Check for cross-page write, to write the upper 32 bits correctly. + uint32_t value32 = (value >> 32); + if (((guest_va & 0xFFF) + 12) > 0x1000) { + #if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE + // The value is endianness swapped already if needs_swap is true, so swap it back in that case. + if (needs_swap) { + value32 = (uint32_t)value; + value32 = BYTESWAP_32(value32); + } + #endif + // Add the pre-munged address, as munging is a no-op for uint64_t, but not for uint32_t. + mmu_write_vmem(opcode, guest_va + mem_munge_address(8), value32); + } else { + // Not cross-page, so just write via host address. + WRITE_DWORD_BE_U(host_va + 8, value32); + } + // Write the lower 32 bits. + #if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE + if (needs_swap) { + value32 = (value >> 32); + } else + #endif + { + value32 = (uint32_t)value; + } + WRITE_DWORD_BE_U(host_va, value32); +#endif } else { #ifdef MMU_PROFILING unaligned_writes++; @@ -1331,9 +1646,9 @@ static void write_unaligned(uint32_t opcode, uint32_t guest_va, uint8_t *host_va } // explicitely instantiate all required write_unaligned variants -template void write_unaligned(uint32_t opcode, uint32_t guest_va, uint8_t *host_va, uint16_t value); -template void write_unaligned(uint32_t opcode, uint32_t guest_va, uint8_t *host_va, uint32_t value); -template void write_unaligned(uint32_t opcode, uint32_t guest_va, uint8_t *host_va, uint64_t value); +template void write_unaligned(uint32_t opcode, uint32_t guest_va, uint8_t *host_va, uint16_t value ARGS_SWAP_MUNGED); +template void write_unaligned(uint32_t opcode, uint32_t guest_va, uint8_t *host_va, uint32_t value ARGS_SWAP_MUNGED); +template void write_unaligned(uint32_t opcode, uint32_t guest_va, uint8_t *host_va, uint64_t value ARGS_SWAP_MUNGED); /* MMU profiling. */ diff --git a/cpu/ppc/ppcopcodes.cpp b/cpu/ppc/ppcopcodes.cpp index 4da24371..ae22502c 100644 --- a/cpu/ppc/ppcopcodes.cpp +++ b/cpu/ppc/ppcopcodes.cpp @@ -799,6 +799,9 @@ void dppc_interpreter::ppc_mtmsr(uint32_t opcode) { uint32_t reg_s = (opcode >> 21) & 0x1F; uint32_t old_msr_val = ppc_state.msr; uint32_t new_msr_val = ppc_state.gpr[reg_s]; + if (!is_601) { + ppc_change_endian((new_msr_val & MSR::LE) != 0); + } ppc_msr_did_change(old_msr_val, new_msr_val); // generate External Interrupt Exception @@ -1113,6 +1116,12 @@ void dppc_interpreter::ppc_mtspr(uint32_t opcode) { ppc_state.spr[ref_spr] = val; dbat_update(ref_spr); break; + case SPR::HID0: + ppc_state.spr[ref_spr] = val; + if (is_601) { + ppc_change_endian((val & 0x10000000) != 0); + } + break; default: // FIXME: Unknown SPR should be noop or illegal instruction. ppc_state.spr[ref_spr] = val; @@ -1468,7 +1477,9 @@ void dppc_interpreter::ppc_rfi(uint32_t opcode) { (msr_bits_to_replace & ~msr_bits_to_clear); uint32_t new_msr_val = (ppc_state.msr & ~(msr_bits_to_replace | msr_bits_to_clear)) | bits_from_srr1; - + if (!is_601) { + ppc_change_endian((new_msr_val & MSR::LE) != 0); + } ppc_msr_did_change(ppc_state.msr, new_msr_val); // setting MSR[EE] may enable pending exceptions diff --git a/debugger/debugger.cpp b/debugger/debugger.cpp index a9864c40..1ce338ec 100644 --- a/debugger/debugger.cpp +++ b/debugger/debugger.cpp @@ -430,6 +430,21 @@ static uint32_t disasm(PPCDisasmContext &ctx) { cout << COUT08X << ctx.instr_addr; cout << ": " << COUT08X << ctx.instr_code; cout << " " << disassemble_single(&ctx) << setfill(' ') << right << dec; + +#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE + bool needs_swap = false; + if (mem_ctrl_instance != nullptr) + needs_swap = mem_ctrl_instance->needs_swap_endian(false); +#endif + + uint32_t phys_addr; + uint8_t* real_addr = mmu_translate_imem(ctx.instr_addr, &phys_addr); + ctx.instr_code = +#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE + needs_swap ? READ_DWORD_LE_A(real_addr) : +#endif + READ_DWORD_BE_A(real_addr); + return ctx.instr_addr; } diff --git a/devices/memctrl/memctrlbase.cpp b/devices/memctrl/memctrlbase.cpp index 177166f4..6ae3d80c 100644 --- a/devices/memctrl/memctrlbase.cpp +++ b/devices/memctrl/memctrlbase.cpp @@ -433,6 +433,12 @@ AddressMapEntry* MemCtrlBase::remove_region(AddressMapEntry* entry) } +#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE +bool MemCtrlBase::needs_swap_endian(bool is_mmio) { + return false; +} +#endif + AddressMapEntry* MemCtrlBase::find_rom_region() { for (auto& entry : address_map) { diff --git a/devices/memctrl/memctrlbase.h b/devices/memctrl/memctrlbase.h index f17f3727..718912b0 100644 --- a/devices/memctrl/memctrlbase.h +++ b/devices/memctrl/memctrlbase.h @@ -80,6 +80,13 @@ public: virtual AddressMapEntry* set_data(uint32_t reg_addr, const uint8_t* data, uint32_t size); +#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE + virtual bool needs_swap_endian(bool is_mmio); + bool needs_swap_endian(AddressMapEntry* entry) { + return needs_swap_endian((entry->type & RT_MMIO) != 0); + } +#endif + AddressMapEntry* find_range(uint32_t addr); AddressMapEntry* find_range_exact(uint32_t addr, uint32_t size, MMIODevice* dev_instance); diff --git a/devices/memctrl/mpc106.cpp b/devices/memctrl/mpc106.cpp index 6a8f057a..890106b6 100644 --- a/devices/memctrl/mpc106.cpp +++ b/devices/memctrl/mpc106.cpp @@ -59,6 +59,12 @@ int MPC106::device_postinit() return this->pcihost_device_postinit(); } +#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE +bool MPC106::needs_swap_endian(bool is_mmio) { + return is_mmio && needs_swap_endian_pci(); +} +#endif + uint32_t MPC106::read(uint32_t rgn_start, uint32_t offset, int size) { if (rgn_start == 0xFE000000) { return pci_io_read_broadcast(offset, size); diff --git a/devices/memctrl/mpc106.h b/devices/memctrl/mpc106.h index 1a7de290..38a81218 100644 --- a/devices/memctrl/mpc106.h +++ b/devices/memctrl/mpc106.h @@ -174,6 +174,10 @@ public: uint32_t read(uint32_t rgn_start, uint32_t offset, int size) override; void write(uint32_t rgn_start, uint32_t offset, uint32_t value, int size) override; +#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE + bool needs_swap_endian(bool is_mmio) override; +#endif + int device_postinit() override; protected: @@ -188,6 +192,12 @@ private: int &fun_num, uint8_t ®_offs, AccessDetails &details, PCIBase *&device); +#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE + inline bool needs_swap_endian_pci() { + return (picr1 & LE_MODE) != 0; + } +#endif + uint32_t config_addr; uint16_t pmcr1 = 0; // power management config 1 diff --git a/devices/video/pdmonboard.h b/devices/video/pdmonboard.h index 5848f997..b507b06a 100644 --- a/devices/video/pdmonboard.h +++ b/devices/video/pdmonboard.h @@ -69,6 +69,11 @@ public: } protected: +#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE + bool framebuffer_in_main_memory(void) override { + return true; + } +#endif void set_depth_internal(int width); void enable_video_internal(); void disable_video_internal(); diff --git a/devices/video/videoctrl.cpp b/devices/video/videoctrl.cpp index 0eb1e20d..64e03ee4 100644 --- a/devices/video/videoctrl.cpp +++ b/devices/video/videoctrl.cpp @@ -24,6 +24,7 @@ along with this program. If not, see . #include #include #include +#include #include #include @@ -316,10 +317,24 @@ void VideoCtrlBase::convert_frame_8bpp(uint8_t *dst_buf, int dst_pitch) } } +#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE +bool VideoCtrlBase::needs_swap_endian() { + extern MemCtrlBase* mem_ctrl_instance; + return mem_ctrl_instance->needs_swap_endian(!this->framebuffer_in_main_memory()); +} +#endif + // RGB555 template void VideoCtrlBase::convert_frame_15bpp(uint8_t *dst_buf, int dst_pitch) { +#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE + if (needs_swap_endian()) { + convert_frame_15bpp(dst_buf, dst_pitch); + return; + } +#endif + uint8_t *src_row, *dst_row; int src_pitch; @@ -349,6 +364,13 @@ template void VideoCtrlBase::convert_frame_15bpp(uint8_t *dst template void VideoCtrlBase::convert_frame_16bpp(uint8_t *dst_buf, int dst_pitch) { +#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE + if (needs_swap_endian()) { + convert_frame_16bpp(dst_buf, dst_pitch); + return; + } +#endif + uint8_t *src_row, *dst_row; int src_pitch; @@ -402,6 +424,13 @@ void VideoCtrlBase::convert_frame_24bpp(uint8_t *dst_buf, int dst_pitch) template void VideoCtrlBase::convert_frame_32bpp(uint8_t *dst_buf, int dst_pitch) { +#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE + if (needs_swap_endian()) { + convert_frame_32bpp(dst_buf, dst_pitch); + return; + } +#endif + uint32_t *src_row, *dst_row; int src_pitch; diff --git a/devices/video/videoctrl.h b/devices/video/videoctrl.h index 246f6cd2..0b7dedac 100644 --- a/devices/video/videoctrl.h +++ b/devices/video/videoctrl.h @@ -79,6 +79,13 @@ public: void convert_frame_32bpp(uint8_t *dst_buf, int dst_pitch); protected: +#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE + virtual bool framebuffer_in_main_memory(void) { + return false; + } + virtual bool needs_swap_endian(); +#endif + // CRT controller parameters bool crtc_on = false; bool blank_on = true;