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" 10a8d1b5bcSBibo Mao #include "system/tcg.h" 1127edd504SSong Gao #include "cpu.h" 1227edd504SSong Gao #include "internals.h" 1327edd504SSong Gao #include "cpu-csr.h" 14*9fd0cc4dSBibo Mao #include "tcg/tcg_loongarch.h" 1527edd504SSong Gao 166f703a48SBibo Mao #ifdef CONFIG_TCG 1727edd504SSong Gao static int loongarch_map_tlb_entry(CPULoongArchState *env, hwaddr *physical, 1827edd504SSong Gao int *prot, target_ulong address, 1927edd504SSong Gao int access_type, int index, int mmu_idx) 2027edd504SSong Gao { 2127edd504SSong Gao LoongArchTLB *tlb = &env->tlb[index]; 2227edd504SSong Gao uint64_t plv = mmu_idx; 2327edd504SSong Gao uint64_t tlb_entry, tlb_ppn; 2427edd504SSong Gao uint8_t tlb_ps, n, tlb_v, tlb_d, tlb_plv, tlb_nx, tlb_nr, tlb_rplv; 2527edd504SSong Gao 2627edd504SSong Gao if (index >= LOONGARCH_STLB) { 2727edd504SSong Gao tlb_ps = FIELD_EX64(tlb->tlb_misc, TLB_MISC, PS); 2827edd504SSong Gao } else { 2927edd504SSong Gao tlb_ps = FIELD_EX64(env->CSR_STLBPS, CSR_STLBPS, PS); 3027edd504SSong Gao } 3127edd504SSong Gao n = (address >> tlb_ps) & 0x1;/* Odd or even */ 3227edd504SSong Gao 3327edd504SSong Gao tlb_entry = n ? tlb->tlb_entry1 : tlb->tlb_entry0; 3427edd504SSong Gao tlb_v = FIELD_EX64(tlb_entry, TLBENTRY, V); 3527edd504SSong Gao tlb_d = FIELD_EX64(tlb_entry, TLBENTRY, D); 3627edd504SSong Gao tlb_plv = FIELD_EX64(tlb_entry, TLBENTRY, PLV); 3727edd504SSong Gao if (is_la64(env)) { 3827edd504SSong Gao tlb_ppn = FIELD_EX64(tlb_entry, TLBENTRY_64, PPN); 3927edd504SSong Gao tlb_nx = FIELD_EX64(tlb_entry, TLBENTRY_64, NX); 4027edd504SSong Gao tlb_nr = FIELD_EX64(tlb_entry, TLBENTRY_64, NR); 4127edd504SSong Gao tlb_rplv = FIELD_EX64(tlb_entry, TLBENTRY_64, RPLV); 4227edd504SSong Gao } else { 4327edd504SSong Gao tlb_ppn = FIELD_EX64(tlb_entry, TLBENTRY_32, PPN); 4427edd504SSong Gao tlb_nx = 0; 4527edd504SSong Gao tlb_nr = 0; 4627edd504SSong Gao tlb_rplv = 0; 4727edd504SSong Gao } 4827edd504SSong Gao 4927edd504SSong Gao /* Remove sw bit between bit12 -- bit PS*/ 5027edd504SSong Gao tlb_ppn = tlb_ppn & ~(((0x1UL << (tlb_ps - 12)) -1)); 5127edd504SSong Gao 5227edd504SSong Gao /* Check access rights */ 5327edd504SSong Gao if (!tlb_v) { 5427edd504SSong Gao return TLBRET_INVALID; 5527edd504SSong Gao } 5627edd504SSong Gao 5727edd504SSong Gao if (access_type == MMU_INST_FETCH && tlb_nx) { 5827edd504SSong Gao return TLBRET_XI; 5927edd504SSong Gao } 6027edd504SSong Gao 6127edd504SSong Gao if (access_type == MMU_DATA_LOAD && tlb_nr) { 6227edd504SSong Gao return TLBRET_RI; 6327edd504SSong Gao } 6427edd504SSong Gao 6527edd504SSong Gao if (((tlb_rplv == 0) && (plv > tlb_plv)) || 6627edd504SSong Gao ((tlb_rplv == 1) && (plv != tlb_plv))) { 6727edd504SSong Gao return TLBRET_PE; 6827edd504SSong Gao } 6927edd504SSong Gao 7027edd504SSong Gao if ((access_type == MMU_DATA_STORE) && !tlb_d) { 7127edd504SSong Gao return TLBRET_DIRTY; 7227edd504SSong Gao } 7327edd504SSong Gao 7427edd504SSong Gao *physical = (tlb_ppn << R_TLBENTRY_64_PPN_SHIFT) | 7527edd504SSong Gao (address & MAKE_64BIT_MASK(0, tlb_ps)); 7627edd504SSong Gao *prot = PAGE_READ; 7727edd504SSong Gao if (tlb_d) { 7827edd504SSong Gao *prot |= PAGE_WRITE; 7927edd504SSong Gao } 8027edd504SSong Gao if (!tlb_nx) { 8127edd504SSong Gao *prot |= PAGE_EXEC; 8227edd504SSong Gao } 8327edd504SSong Gao return TLBRET_MATCH; 8427edd504SSong Gao } 8527edd504SSong Gao 8627edd504SSong Gao /* 8727edd504SSong Gao * One tlb entry holds an adjacent odd/even pair, the vpn is the 8827edd504SSong Gao * content of the virtual page number divided by 2. So the 8927edd504SSong Gao * compare vpn is bit[47:15] for 16KiB page. while the vppn 9027edd504SSong Gao * field in tlb entry contains bit[47:13], so need adjust. 9127edd504SSong Gao * virt_vpn = vaddr[47:13] 9227edd504SSong Gao */ 9327edd504SSong Gao bool loongarch_tlb_search(CPULoongArchState *env, target_ulong vaddr, 9427edd504SSong Gao int *index) 9527edd504SSong Gao { 9627edd504SSong Gao LoongArchTLB *tlb; 9727edd504SSong Gao uint16_t csr_asid, tlb_asid, stlb_idx; 9827edd504SSong Gao uint8_t tlb_e, tlb_ps, tlb_g, stlb_ps; 9927edd504SSong Gao int i, compare_shift; 10027edd504SSong Gao uint64_t vpn, tlb_vppn; 10127edd504SSong Gao 10227edd504SSong Gao csr_asid = FIELD_EX64(env->CSR_ASID, CSR_ASID, ASID); 10327edd504SSong Gao stlb_ps = FIELD_EX64(env->CSR_STLBPS, CSR_STLBPS, PS); 10427edd504SSong Gao vpn = (vaddr & TARGET_VIRT_MASK) >> (stlb_ps + 1); 10527edd504SSong Gao stlb_idx = vpn & 0xff; /* VA[25:15] <==> TLBIDX.index for 16KiB Page */ 10627edd504SSong Gao compare_shift = stlb_ps + 1 - R_TLB_MISC_VPPN_SHIFT; 10727edd504SSong Gao 10827edd504SSong Gao /* Search STLB */ 10927edd504SSong Gao for (i = 0; i < 8; ++i) { 11027edd504SSong Gao tlb = &env->tlb[i * 256 + stlb_idx]; 11127edd504SSong Gao tlb_e = FIELD_EX64(tlb->tlb_misc, TLB_MISC, E); 11227edd504SSong Gao if (tlb_e) { 11327edd504SSong Gao tlb_vppn = FIELD_EX64(tlb->tlb_misc, TLB_MISC, VPPN); 11427edd504SSong Gao tlb_asid = FIELD_EX64(tlb->tlb_misc, TLB_MISC, ASID); 11527edd504SSong Gao tlb_g = FIELD_EX64(tlb->tlb_entry0, TLBENTRY, G); 11627edd504SSong Gao 11727edd504SSong Gao if ((tlb_g == 1 || tlb_asid == csr_asid) && 11827edd504SSong Gao (vpn == (tlb_vppn >> compare_shift))) { 11927edd504SSong Gao *index = i * 256 + stlb_idx; 12027edd504SSong Gao return true; 12127edd504SSong Gao } 12227edd504SSong Gao } 12327edd504SSong Gao } 12427edd504SSong Gao 12527edd504SSong Gao /* Search MTLB */ 12627edd504SSong Gao for (i = LOONGARCH_STLB; i < LOONGARCH_TLB_MAX; ++i) { 12727edd504SSong Gao tlb = &env->tlb[i]; 12827edd504SSong Gao tlb_e = FIELD_EX64(tlb->tlb_misc, TLB_MISC, E); 12927edd504SSong Gao if (tlb_e) { 13027edd504SSong Gao tlb_vppn = FIELD_EX64(tlb->tlb_misc, TLB_MISC, VPPN); 13127edd504SSong Gao tlb_ps = FIELD_EX64(tlb->tlb_misc, TLB_MISC, PS); 13227edd504SSong Gao tlb_asid = FIELD_EX64(tlb->tlb_misc, TLB_MISC, ASID); 13327edd504SSong Gao tlb_g = FIELD_EX64(tlb->tlb_entry0, TLBENTRY, G); 13427edd504SSong Gao compare_shift = tlb_ps + 1 - R_TLB_MISC_VPPN_SHIFT; 13527edd504SSong Gao vpn = (vaddr & TARGET_VIRT_MASK) >> (tlb_ps + 1); 13627edd504SSong Gao if ((tlb_g == 1 || tlb_asid == csr_asid) && 13727edd504SSong Gao (vpn == (tlb_vppn >> compare_shift))) { 13827edd504SSong Gao *index = i; 13927edd504SSong Gao return true; 14027edd504SSong Gao } 14127edd504SSong Gao } 14227edd504SSong Gao } 14327edd504SSong Gao return false; 14427edd504SSong Gao } 14527edd504SSong Gao 146*9fd0cc4dSBibo Mao int loongarch_get_addr_from_tlb(CPULoongArchState *env, hwaddr *physical, 147a8d1b5bcSBibo Mao int *prot, target_ulong address, 148a8d1b5bcSBibo Mao MMUAccessType access_type, int mmu_idx) 149a8d1b5bcSBibo Mao { 150a8d1b5bcSBibo Mao int index, match; 151a8d1b5bcSBibo Mao 152a8d1b5bcSBibo Mao match = loongarch_tlb_search(env, address, &index); 153a8d1b5bcSBibo Mao if (match) { 154a8d1b5bcSBibo Mao return loongarch_map_tlb_entry(env, physical, prot, 155a8d1b5bcSBibo Mao address, access_type, index, mmu_idx); 156a8d1b5bcSBibo Mao } 157a8d1b5bcSBibo Mao 158a8d1b5bcSBibo Mao return TLBRET_NOMATCH; 159a8d1b5bcSBibo Mao } 160566bf2deSBibo Mao #endif 161a8d1b5bcSBibo Mao 162885398eeSBibo Mao void get_dir_base_width(CPULoongArchState *env, uint64_t *dir_base, 163885398eeSBibo Mao uint64_t *dir_width, target_ulong level) 164885398eeSBibo Mao { 165885398eeSBibo Mao switch (level) { 166885398eeSBibo Mao case 1: 167885398eeSBibo Mao *dir_base = FIELD_EX64(env->CSR_PWCL, CSR_PWCL, DIR1_BASE); 168885398eeSBibo Mao *dir_width = FIELD_EX64(env->CSR_PWCL, CSR_PWCL, DIR1_WIDTH); 169885398eeSBibo Mao break; 170885398eeSBibo Mao case 2: 171885398eeSBibo Mao *dir_base = FIELD_EX64(env->CSR_PWCL, CSR_PWCL, DIR2_BASE); 172885398eeSBibo Mao *dir_width = FIELD_EX64(env->CSR_PWCL, CSR_PWCL, DIR2_WIDTH); 173885398eeSBibo Mao break; 174885398eeSBibo Mao case 3: 175885398eeSBibo Mao *dir_base = FIELD_EX64(env->CSR_PWCH, CSR_PWCH, DIR3_BASE); 176885398eeSBibo Mao *dir_width = FIELD_EX64(env->CSR_PWCH, CSR_PWCH, DIR3_WIDTH); 177885398eeSBibo Mao break; 178885398eeSBibo Mao case 4: 179885398eeSBibo Mao *dir_base = FIELD_EX64(env->CSR_PWCH, CSR_PWCH, DIR4_BASE); 180885398eeSBibo Mao *dir_width = FIELD_EX64(env->CSR_PWCH, CSR_PWCH, DIR4_WIDTH); 181885398eeSBibo Mao break; 182885398eeSBibo Mao default: 183885398eeSBibo Mao /* level may be zero for ldpte */ 184885398eeSBibo Mao *dir_base = FIELD_EX64(env->CSR_PWCL, CSR_PWCL, PTBASE); 185885398eeSBibo Mao *dir_width = FIELD_EX64(env->CSR_PWCL, CSR_PWCL, PTWIDTH); 186885398eeSBibo Mao break; 187885398eeSBibo Mao } 188885398eeSBibo Mao } 189885398eeSBibo Mao 190dd291171SMiao Hao static int loongarch_page_table_walker(CPULoongArchState *env, hwaddr *physical, 191dd291171SMiao Hao int *prot, target_ulong address) 192dd291171SMiao Hao { 193dd291171SMiao Hao CPUState *cs = env_cpu(env); 194dd291171SMiao Hao target_ulong index, phys; 195dd291171SMiao Hao uint64_t dir_base, dir_width; 196dd291171SMiao Hao uint64_t base; 197dd291171SMiao Hao int level; 198dd291171SMiao Hao 199dd291171SMiao Hao if ((address >> 63) & 0x1) { 200dd291171SMiao Hao base = env->CSR_PGDH; 201dd291171SMiao Hao } else { 202dd291171SMiao Hao base = env->CSR_PGDL; 203dd291171SMiao Hao } 204dd291171SMiao Hao base &= TARGET_PHYS_MASK; 205dd291171SMiao Hao 206dd291171SMiao Hao for (level = 4; level > 0; level--) { 207dd291171SMiao Hao get_dir_base_width(env, &dir_base, &dir_width, level); 208dd291171SMiao Hao 209dd291171SMiao Hao if (dir_width == 0) { 210dd291171SMiao Hao continue; 211dd291171SMiao Hao } 212dd291171SMiao Hao 213dd291171SMiao Hao /* get next level page directory */ 214dd291171SMiao Hao index = (address >> dir_base) & ((1 << dir_width) - 1); 215dd291171SMiao Hao phys = base | index << 3; 216dd291171SMiao Hao base = ldq_phys(cs->as, phys) & TARGET_PHYS_MASK; 217dd291171SMiao Hao if (FIELD_EX64(base, TLBENTRY, HUGE)) { 218dd291171SMiao Hao /* base is a huge pte */ 219dd291171SMiao Hao break; 220dd291171SMiao Hao } 221dd291171SMiao Hao } 222dd291171SMiao Hao 223dd291171SMiao Hao /* pte */ 224dd291171SMiao Hao if (FIELD_EX64(base, TLBENTRY, HUGE)) { 225dd291171SMiao Hao /* Huge Page. base is pte */ 226dd291171SMiao Hao base = FIELD_DP64(base, TLBENTRY, LEVEL, 0); 227dd291171SMiao Hao base = FIELD_DP64(base, TLBENTRY, HUGE, 0); 228dd291171SMiao Hao if (FIELD_EX64(base, TLBENTRY, HGLOBAL)) { 229dd291171SMiao Hao base = FIELD_DP64(base, TLBENTRY, HGLOBAL, 0); 230dd291171SMiao Hao base = FIELD_DP64(base, TLBENTRY, G, 1); 231dd291171SMiao Hao } 232dd291171SMiao Hao } else { 233dd291171SMiao Hao /* Normal Page. base points to pte */ 234dd291171SMiao Hao get_dir_base_width(env, &dir_base, &dir_width, 0); 235dd291171SMiao Hao index = (address >> dir_base) & ((1 << dir_width) - 1); 236dd291171SMiao Hao phys = base | index << 3; 237dd291171SMiao Hao base = ldq_phys(cs->as, phys); 238dd291171SMiao Hao } 239dd291171SMiao Hao 240dd291171SMiao Hao /* TODO: check plv and other bits? */ 241dd291171SMiao Hao 242dd291171SMiao Hao /* base is pte, in normal pte format */ 243dd291171SMiao Hao if (!FIELD_EX64(base, TLBENTRY, V)) { 244dd291171SMiao Hao return TLBRET_NOMATCH; 245dd291171SMiao Hao } 246dd291171SMiao Hao 247dd291171SMiao Hao if (!FIELD_EX64(base, TLBENTRY, D)) { 248dd291171SMiao Hao *prot = PAGE_READ; 249dd291171SMiao Hao } else { 250dd291171SMiao Hao *prot = PAGE_READ | PAGE_WRITE; 251dd291171SMiao Hao } 252dd291171SMiao Hao 253dd291171SMiao Hao /* get TARGET_PAGE_SIZE aligned physical address */ 254dd291171SMiao Hao base += (address & TARGET_PHYS_MASK) & ((1 << dir_base) - 1); 255dd291171SMiao Hao /* mask RPLV, NX, NR bits */ 256dd291171SMiao Hao base = FIELD_DP64(base, TLBENTRY_64, RPLV, 0); 257dd291171SMiao Hao base = FIELD_DP64(base, TLBENTRY_64, NX, 0); 258dd291171SMiao Hao base = FIELD_DP64(base, TLBENTRY_64, NR, 0); 259dd291171SMiao Hao /* mask other attribute bits */ 260dd291171SMiao Hao *physical = base & TARGET_PAGE_MASK; 261dd291171SMiao Hao 262dd291171SMiao Hao return 0; 263dd291171SMiao Hao } 264dd291171SMiao Hao 26527edd504SSong Gao static int loongarch_map_address(CPULoongArchState *env, hwaddr *physical, 26627edd504SSong Gao int *prot, target_ulong address, 267dd291171SMiao Hao MMUAccessType access_type, int mmu_idx, 268dd291171SMiao Hao int is_debug) 26927edd504SSong Gao { 270a8d1b5bcSBibo Mao int ret; 27127edd504SSong Gao 272a8d1b5bcSBibo Mao if (tcg_enabled()) { 273a8d1b5bcSBibo Mao ret = loongarch_get_addr_from_tlb(env, physical, prot, address, 274a8d1b5bcSBibo Mao access_type, mmu_idx); 275a8d1b5bcSBibo Mao if (ret != TLBRET_NOMATCH) { 276a8d1b5bcSBibo Mao return ret; 277a8d1b5bcSBibo Mao } 278a8d1b5bcSBibo Mao } 279a8d1b5bcSBibo Mao 280a8d1b5bcSBibo Mao if (is_debug) { 281dd291171SMiao Hao /* 282dd291171SMiao Hao * For debugger memory access, we want to do the map when there is a 283dd291171SMiao Hao * legal mapping, even if the mapping is not yet in TLB. return 0 if 284dd291171SMiao Hao * there is a valid map, else none zero. 285dd291171SMiao Hao */ 286dd291171SMiao Hao return loongarch_page_table_walker(env, physical, prot, address); 28727edd504SSong Gao } 28827edd504SSong Gao 28927edd504SSong Gao return TLBRET_NOMATCH; 29027edd504SSong Gao } 29127edd504SSong Gao 29227edd504SSong Gao static hwaddr dmw_va2pa(CPULoongArchState *env, target_ulong va, 29327edd504SSong Gao target_ulong dmw) 29427edd504SSong Gao { 29527edd504SSong Gao if (is_la64(env)) { 29627edd504SSong Gao return va & TARGET_VIRT_MASK; 29727edd504SSong Gao } else { 29827edd504SSong Gao uint32_t pseg = FIELD_EX32(dmw, CSR_DMW_32, PSEG); 29927edd504SSong Gao return (va & MAKE_64BIT_MASK(0, R_CSR_DMW_32_VSEG_SHIFT)) | \ 30027edd504SSong Gao (pseg << R_CSR_DMW_32_VSEG_SHIFT); 30127edd504SSong Gao } 30227edd504SSong Gao } 30327edd504SSong Gao 30427edd504SSong Gao int get_physical_address(CPULoongArchState *env, hwaddr *physical, 30527edd504SSong Gao int *prot, target_ulong address, 306dd291171SMiao Hao MMUAccessType access_type, int mmu_idx, int is_debug) 30727edd504SSong Gao { 3083f262d25SRichard Henderson int user_mode = mmu_idx == MMU_USER_IDX; 3093f262d25SRichard Henderson int kernel_mode = mmu_idx == MMU_KERNEL_IDX; 31027edd504SSong Gao uint32_t plv, base_c, base_v; 31127edd504SSong Gao int64_t addr_high; 31227edd504SSong Gao uint8_t da = FIELD_EX64(env->CSR_CRMD, CSR_CRMD, DA); 31327edd504SSong Gao uint8_t pg = FIELD_EX64(env->CSR_CRMD, CSR_CRMD, PG); 31427edd504SSong Gao 31527edd504SSong Gao /* Check PG and DA */ 31627edd504SSong Gao if (da & !pg) { 31727edd504SSong Gao *physical = address & TARGET_PHYS_MASK; 31827edd504SSong Gao *prot = PAGE_READ | PAGE_WRITE | PAGE_EXEC; 31927edd504SSong Gao return TLBRET_MATCH; 32027edd504SSong Gao } 32127edd504SSong Gao 32227edd504SSong Gao plv = kernel_mode | (user_mode << R_CSR_DMW_PLV3_SHIFT); 32327edd504SSong Gao if (is_la64(env)) { 32427edd504SSong Gao base_v = address >> R_CSR_DMW_64_VSEG_SHIFT; 32527edd504SSong Gao } else { 32627edd504SSong Gao base_v = address >> R_CSR_DMW_32_VSEG_SHIFT; 32727edd504SSong Gao } 32827edd504SSong Gao /* Check direct map window */ 32927edd504SSong Gao for (int i = 0; i < 4; i++) { 33027edd504SSong Gao if (is_la64(env)) { 33127edd504SSong Gao base_c = FIELD_EX64(env->CSR_DMW[i], CSR_DMW_64, VSEG); 33227edd504SSong Gao } else { 33327edd504SSong Gao base_c = FIELD_EX64(env->CSR_DMW[i], CSR_DMW_32, VSEG); 33427edd504SSong Gao } 33527edd504SSong Gao if ((plv & env->CSR_DMW[i]) && (base_c == base_v)) { 33627edd504SSong Gao *physical = dmw_va2pa(env, address, env->CSR_DMW[i]); 33727edd504SSong Gao *prot = PAGE_READ | PAGE_WRITE | PAGE_EXEC; 33827edd504SSong Gao return TLBRET_MATCH; 33927edd504SSong Gao } 34027edd504SSong Gao } 34127edd504SSong Gao 34227edd504SSong Gao /* Check valid extension */ 34327edd504SSong Gao addr_high = sextract64(address, TARGET_VIRT_ADDR_SPACE_BITS, 16); 34427edd504SSong Gao if (!(addr_high == 0 || addr_high == -1)) { 34527edd504SSong Gao return TLBRET_BADADDR; 34627edd504SSong Gao } 34727edd504SSong Gao 34827edd504SSong Gao /* Mapped address */ 34927edd504SSong Gao return loongarch_map_address(env, physical, prot, address, 350dd291171SMiao Hao access_type, mmu_idx, is_debug); 35127edd504SSong Gao } 35227edd504SSong Gao 35327edd504SSong Gao hwaddr loongarch_cpu_get_phys_page_debug(CPUState *cs, vaddr addr) 35427edd504SSong Gao { 355f3b603b9SPhilippe Mathieu-Daudé CPULoongArchState *env = cpu_env(cs); 35627edd504SSong Gao hwaddr phys_addr; 35727edd504SSong Gao int prot; 35827edd504SSong Gao 35927edd504SSong Gao if (get_physical_address(env, &phys_addr, &prot, addr, MMU_DATA_LOAD, 360dd291171SMiao Hao cpu_mmu_index(cs, false), 1) != 0) { 36127edd504SSong Gao return -1; 36227edd504SSong Gao } 36327edd504SSong Gao return phys_addr; 36427edd504SSong Gao } 365