127edd504SSong Gao /* SPDX-License-Identifier: GPL-2.0-or-later */ 227edd504SSong Gao /* 327edd504SSong Gao * LoongArch CPU helpers for qemu 427edd504SSong Gao * 527edd504SSong Gao * Copyright (c) 2024 Loongson Technology Corporation Limited 627edd504SSong Gao * 727edd504SSong Gao */ 827edd504SSong Gao 927edd504SSong Gao #include "qemu/osdep.h" 1027edd504SSong Gao #include "cpu.h" 1127edd504SSong Gao #include "internals.h" 1227edd504SSong Gao #include "cpu-csr.h" 1327edd504SSong Gao 1427edd504SSong Gao static int loongarch_map_tlb_entry(CPULoongArchState *env, hwaddr *physical, 1527edd504SSong Gao int *prot, target_ulong address, 1627edd504SSong Gao int access_type, int index, int mmu_idx) 1727edd504SSong Gao { 1827edd504SSong Gao LoongArchTLB *tlb = &env->tlb[index]; 1927edd504SSong Gao uint64_t plv = mmu_idx; 2027edd504SSong Gao uint64_t tlb_entry, tlb_ppn; 2127edd504SSong Gao uint8_t tlb_ps, n, tlb_v, tlb_d, tlb_plv, tlb_nx, tlb_nr, tlb_rplv; 2227edd504SSong Gao 2327edd504SSong Gao if (index >= LOONGARCH_STLB) { 2427edd504SSong Gao tlb_ps = FIELD_EX64(tlb->tlb_misc, TLB_MISC, PS); 2527edd504SSong Gao } else { 2627edd504SSong Gao tlb_ps = FIELD_EX64(env->CSR_STLBPS, CSR_STLBPS, PS); 2727edd504SSong Gao } 2827edd504SSong Gao n = (address >> tlb_ps) & 0x1;/* Odd or even */ 2927edd504SSong Gao 3027edd504SSong Gao tlb_entry = n ? tlb->tlb_entry1 : tlb->tlb_entry0; 3127edd504SSong Gao tlb_v = FIELD_EX64(tlb_entry, TLBENTRY, V); 3227edd504SSong Gao tlb_d = FIELD_EX64(tlb_entry, TLBENTRY, D); 3327edd504SSong Gao tlb_plv = FIELD_EX64(tlb_entry, TLBENTRY, PLV); 3427edd504SSong Gao if (is_la64(env)) { 3527edd504SSong Gao tlb_ppn = FIELD_EX64(tlb_entry, TLBENTRY_64, PPN); 3627edd504SSong Gao tlb_nx = FIELD_EX64(tlb_entry, TLBENTRY_64, NX); 3727edd504SSong Gao tlb_nr = FIELD_EX64(tlb_entry, TLBENTRY_64, NR); 3827edd504SSong Gao tlb_rplv = FIELD_EX64(tlb_entry, TLBENTRY_64, RPLV); 3927edd504SSong Gao } else { 4027edd504SSong Gao tlb_ppn = FIELD_EX64(tlb_entry, TLBENTRY_32, PPN); 4127edd504SSong Gao tlb_nx = 0; 4227edd504SSong Gao tlb_nr = 0; 4327edd504SSong Gao tlb_rplv = 0; 4427edd504SSong Gao } 4527edd504SSong Gao 4627edd504SSong Gao /* Remove sw bit between bit12 -- bit PS*/ 4727edd504SSong Gao tlb_ppn = tlb_ppn & ~(((0x1UL << (tlb_ps - 12)) -1)); 4827edd504SSong Gao 4927edd504SSong Gao /* Check access rights */ 5027edd504SSong Gao if (!tlb_v) { 5127edd504SSong Gao return TLBRET_INVALID; 5227edd504SSong Gao } 5327edd504SSong Gao 5427edd504SSong Gao if (access_type == MMU_INST_FETCH && tlb_nx) { 5527edd504SSong Gao return TLBRET_XI; 5627edd504SSong Gao } 5727edd504SSong Gao 5827edd504SSong Gao if (access_type == MMU_DATA_LOAD && tlb_nr) { 5927edd504SSong Gao return TLBRET_RI; 6027edd504SSong Gao } 6127edd504SSong Gao 6227edd504SSong Gao if (((tlb_rplv == 0) && (plv > tlb_plv)) || 6327edd504SSong Gao ((tlb_rplv == 1) && (plv != tlb_plv))) { 6427edd504SSong Gao return TLBRET_PE; 6527edd504SSong Gao } 6627edd504SSong Gao 6727edd504SSong Gao if ((access_type == MMU_DATA_STORE) && !tlb_d) { 6827edd504SSong Gao return TLBRET_DIRTY; 6927edd504SSong Gao } 7027edd504SSong Gao 7127edd504SSong Gao *physical = (tlb_ppn << R_TLBENTRY_64_PPN_SHIFT) | 7227edd504SSong Gao (address & MAKE_64BIT_MASK(0, tlb_ps)); 7327edd504SSong Gao *prot = PAGE_READ; 7427edd504SSong Gao if (tlb_d) { 7527edd504SSong Gao *prot |= PAGE_WRITE; 7627edd504SSong Gao } 7727edd504SSong Gao if (!tlb_nx) { 7827edd504SSong Gao *prot |= PAGE_EXEC; 7927edd504SSong Gao } 8027edd504SSong Gao return TLBRET_MATCH; 8127edd504SSong Gao } 8227edd504SSong Gao 8327edd504SSong Gao /* 8427edd504SSong Gao * One tlb entry holds an adjacent odd/even pair, the vpn is the 8527edd504SSong Gao * content of the virtual page number divided by 2. So the 8627edd504SSong Gao * compare vpn is bit[47:15] for 16KiB page. while the vppn 8727edd504SSong Gao * field in tlb entry contains bit[47:13], so need adjust. 8827edd504SSong Gao * virt_vpn = vaddr[47:13] 8927edd504SSong Gao */ 9027edd504SSong Gao bool loongarch_tlb_search(CPULoongArchState *env, target_ulong vaddr, 9127edd504SSong Gao int *index) 9227edd504SSong Gao { 9327edd504SSong Gao LoongArchTLB *tlb; 9427edd504SSong Gao uint16_t csr_asid, tlb_asid, stlb_idx; 9527edd504SSong Gao uint8_t tlb_e, tlb_ps, tlb_g, stlb_ps; 9627edd504SSong Gao int i, compare_shift; 9727edd504SSong Gao uint64_t vpn, tlb_vppn; 9827edd504SSong Gao 9927edd504SSong Gao csr_asid = FIELD_EX64(env->CSR_ASID, CSR_ASID, ASID); 10027edd504SSong Gao stlb_ps = FIELD_EX64(env->CSR_STLBPS, CSR_STLBPS, PS); 10127edd504SSong Gao vpn = (vaddr & TARGET_VIRT_MASK) >> (stlb_ps + 1); 10227edd504SSong Gao stlb_idx = vpn & 0xff; /* VA[25:15] <==> TLBIDX.index for 16KiB Page */ 10327edd504SSong Gao compare_shift = stlb_ps + 1 - R_TLB_MISC_VPPN_SHIFT; 10427edd504SSong Gao 10527edd504SSong Gao /* Search STLB */ 10627edd504SSong Gao for (i = 0; i < 8; ++i) { 10727edd504SSong Gao tlb = &env->tlb[i * 256 + stlb_idx]; 10827edd504SSong Gao tlb_e = FIELD_EX64(tlb->tlb_misc, TLB_MISC, E); 10927edd504SSong Gao if (tlb_e) { 11027edd504SSong Gao tlb_vppn = FIELD_EX64(tlb->tlb_misc, TLB_MISC, VPPN); 11127edd504SSong Gao tlb_asid = FIELD_EX64(tlb->tlb_misc, TLB_MISC, ASID); 11227edd504SSong Gao tlb_g = FIELD_EX64(tlb->tlb_entry0, TLBENTRY, G); 11327edd504SSong Gao 11427edd504SSong Gao if ((tlb_g == 1 || tlb_asid == csr_asid) && 11527edd504SSong Gao (vpn == (tlb_vppn >> compare_shift))) { 11627edd504SSong Gao *index = i * 256 + stlb_idx; 11727edd504SSong Gao return true; 11827edd504SSong Gao } 11927edd504SSong Gao } 12027edd504SSong Gao } 12127edd504SSong Gao 12227edd504SSong Gao /* Search MTLB */ 12327edd504SSong Gao for (i = LOONGARCH_STLB; i < LOONGARCH_TLB_MAX; ++i) { 12427edd504SSong Gao tlb = &env->tlb[i]; 12527edd504SSong Gao tlb_e = FIELD_EX64(tlb->tlb_misc, TLB_MISC, E); 12627edd504SSong Gao if (tlb_e) { 12727edd504SSong Gao tlb_vppn = FIELD_EX64(tlb->tlb_misc, TLB_MISC, VPPN); 12827edd504SSong Gao tlb_ps = FIELD_EX64(tlb->tlb_misc, TLB_MISC, PS); 12927edd504SSong Gao tlb_asid = FIELD_EX64(tlb->tlb_misc, TLB_MISC, ASID); 13027edd504SSong Gao tlb_g = FIELD_EX64(tlb->tlb_entry0, TLBENTRY, G); 13127edd504SSong Gao compare_shift = tlb_ps + 1 - R_TLB_MISC_VPPN_SHIFT; 13227edd504SSong Gao vpn = (vaddr & TARGET_VIRT_MASK) >> (tlb_ps + 1); 13327edd504SSong Gao if ((tlb_g == 1 || tlb_asid == csr_asid) && 13427edd504SSong Gao (vpn == (tlb_vppn >> compare_shift))) { 13527edd504SSong Gao *index = i; 13627edd504SSong Gao return true; 13727edd504SSong Gao } 13827edd504SSong Gao } 13927edd504SSong Gao } 14027edd504SSong Gao return false; 14127edd504SSong Gao } 14227edd504SSong Gao 14327edd504SSong Gao static int loongarch_map_address(CPULoongArchState *env, hwaddr *physical, 14427edd504SSong Gao int *prot, target_ulong address, 14527edd504SSong Gao MMUAccessType access_type, int mmu_idx) 14627edd504SSong Gao { 14727edd504SSong Gao int index, match; 14827edd504SSong Gao 14927edd504SSong Gao match = loongarch_tlb_search(env, address, &index); 15027edd504SSong Gao if (match) { 15127edd504SSong Gao return loongarch_map_tlb_entry(env, physical, prot, 15227edd504SSong Gao address, access_type, index, mmu_idx); 15327edd504SSong Gao } 15427edd504SSong Gao 15527edd504SSong Gao return TLBRET_NOMATCH; 15627edd504SSong Gao } 15727edd504SSong Gao 15827edd504SSong Gao static hwaddr dmw_va2pa(CPULoongArchState *env, target_ulong va, 15927edd504SSong Gao target_ulong dmw) 16027edd504SSong Gao { 16127edd504SSong Gao if (is_la64(env)) { 16227edd504SSong Gao return va & TARGET_VIRT_MASK; 16327edd504SSong Gao } else { 16427edd504SSong Gao uint32_t pseg = FIELD_EX32(dmw, CSR_DMW_32, PSEG); 16527edd504SSong Gao return (va & MAKE_64BIT_MASK(0, R_CSR_DMW_32_VSEG_SHIFT)) | \ 16627edd504SSong Gao (pseg << R_CSR_DMW_32_VSEG_SHIFT); 16727edd504SSong Gao } 16827edd504SSong Gao } 16927edd504SSong Gao 17027edd504SSong Gao int get_physical_address(CPULoongArchState *env, hwaddr *physical, 17127edd504SSong Gao int *prot, target_ulong address, 17227edd504SSong Gao MMUAccessType access_type, int mmu_idx) 17327edd504SSong Gao { 1743f262d25SRichard Henderson int user_mode = mmu_idx == MMU_USER_IDX; 1753f262d25SRichard Henderson int kernel_mode = mmu_idx == MMU_KERNEL_IDX; 17627edd504SSong Gao uint32_t plv, base_c, base_v; 17727edd504SSong Gao int64_t addr_high; 17827edd504SSong Gao uint8_t da = FIELD_EX64(env->CSR_CRMD, CSR_CRMD, DA); 17927edd504SSong Gao uint8_t pg = FIELD_EX64(env->CSR_CRMD, CSR_CRMD, PG); 18027edd504SSong Gao 18127edd504SSong Gao /* Check PG and DA */ 18227edd504SSong Gao if (da & !pg) { 18327edd504SSong Gao *physical = address & TARGET_PHYS_MASK; 18427edd504SSong Gao *prot = PAGE_READ | PAGE_WRITE | PAGE_EXEC; 18527edd504SSong Gao return TLBRET_MATCH; 18627edd504SSong Gao } 18727edd504SSong Gao 18827edd504SSong Gao plv = kernel_mode | (user_mode << R_CSR_DMW_PLV3_SHIFT); 18927edd504SSong Gao if (is_la64(env)) { 19027edd504SSong Gao base_v = address >> R_CSR_DMW_64_VSEG_SHIFT; 19127edd504SSong Gao } else { 19227edd504SSong Gao base_v = address >> R_CSR_DMW_32_VSEG_SHIFT; 19327edd504SSong Gao } 19427edd504SSong Gao /* Check direct map window */ 19527edd504SSong Gao for (int i = 0; i < 4; i++) { 19627edd504SSong Gao if (is_la64(env)) { 19727edd504SSong Gao base_c = FIELD_EX64(env->CSR_DMW[i], CSR_DMW_64, VSEG); 19827edd504SSong Gao } else { 19927edd504SSong Gao base_c = FIELD_EX64(env->CSR_DMW[i], CSR_DMW_32, VSEG); 20027edd504SSong Gao } 20127edd504SSong Gao if ((plv & env->CSR_DMW[i]) && (base_c == base_v)) { 20227edd504SSong Gao *physical = dmw_va2pa(env, address, env->CSR_DMW[i]); 20327edd504SSong Gao *prot = PAGE_READ | PAGE_WRITE | PAGE_EXEC; 20427edd504SSong Gao return TLBRET_MATCH; 20527edd504SSong Gao } 20627edd504SSong Gao } 20727edd504SSong Gao 20827edd504SSong Gao /* Check valid extension */ 20927edd504SSong Gao addr_high = sextract64(address, TARGET_VIRT_ADDR_SPACE_BITS, 16); 21027edd504SSong Gao if (!(addr_high == 0 || addr_high == -1)) { 21127edd504SSong Gao return TLBRET_BADADDR; 21227edd504SSong Gao } 21327edd504SSong Gao 21427edd504SSong Gao /* Mapped address */ 21527edd504SSong Gao return loongarch_map_address(env, physical, prot, address, 21627edd504SSong Gao access_type, mmu_idx); 21727edd504SSong Gao } 21827edd504SSong Gao 21927edd504SSong Gao hwaddr loongarch_cpu_get_phys_page_debug(CPUState *cs, vaddr addr) 22027edd504SSong Gao { 221*f3b603b9SPhilippe Mathieu-Daudé CPULoongArchState *env = cpu_env(cs); 22227edd504SSong Gao hwaddr phys_addr; 22327edd504SSong Gao int prot; 22427edd504SSong Gao 22527edd504SSong Gao if (get_physical_address(env, &phys_addr, &prot, addr, MMU_DATA_LOAD, 2263b916140SRichard Henderson cpu_mmu_index(cs, false)) != 0) { 22727edd504SSong Gao return -1; 22827edd504SSong Gao } 22927edd504SSong Gao return phys_addr; 23027edd504SSong Gao } 231