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" 11*efe25c26SRichard Henderson #include "accel/tcg/cpu-mmu-index.h" 1227edd504SSong Gao #include "internals.h" 1327edd504SSong Gao #include "cpu-csr.h" 1427edd504SSong Gao 156f703a48SBibo Mao #ifdef CONFIG_TCG 1627edd504SSong Gao static int loongarch_map_tlb_entry(CPULoongArchState *env, hwaddr *physical, 1727edd504SSong Gao int *prot, target_ulong address, 1827edd504SSong Gao int access_type, int index, int mmu_idx) 1927edd504SSong Gao { 2027edd504SSong Gao LoongArchTLB *tlb = &env->tlb[index]; 2127edd504SSong Gao uint64_t plv = mmu_idx; 2227edd504SSong Gao uint64_t tlb_entry, tlb_ppn; 2327edd504SSong Gao uint8_t tlb_ps, n, tlb_v, tlb_d, tlb_plv, tlb_nx, tlb_nr, tlb_rplv; 2427edd504SSong Gao 2527edd504SSong Gao if (index >= LOONGARCH_STLB) { 2627edd504SSong Gao tlb_ps = FIELD_EX64(tlb->tlb_misc, TLB_MISC, PS); 2727edd504SSong Gao } else { 2827edd504SSong Gao tlb_ps = FIELD_EX64(env->CSR_STLBPS, CSR_STLBPS, PS); 2927edd504SSong Gao } 3027edd504SSong Gao n = (address >> tlb_ps) & 0x1;/* Odd or even */ 3127edd504SSong Gao 3227edd504SSong Gao tlb_entry = n ? tlb->tlb_entry1 : tlb->tlb_entry0; 3327edd504SSong Gao tlb_v = FIELD_EX64(tlb_entry, TLBENTRY, V); 3427edd504SSong Gao tlb_d = FIELD_EX64(tlb_entry, TLBENTRY, D); 3527edd504SSong Gao tlb_plv = FIELD_EX64(tlb_entry, TLBENTRY, PLV); 3627edd504SSong Gao if (is_la64(env)) { 3727edd504SSong Gao tlb_ppn = FIELD_EX64(tlb_entry, TLBENTRY_64, PPN); 3827edd504SSong Gao tlb_nx = FIELD_EX64(tlb_entry, TLBENTRY_64, NX); 3927edd504SSong Gao tlb_nr = FIELD_EX64(tlb_entry, TLBENTRY_64, NR); 4027edd504SSong Gao tlb_rplv = FIELD_EX64(tlb_entry, TLBENTRY_64, RPLV); 4127edd504SSong Gao } else { 4227edd504SSong Gao tlb_ppn = FIELD_EX64(tlb_entry, TLBENTRY_32, PPN); 4327edd504SSong Gao tlb_nx = 0; 4427edd504SSong Gao tlb_nr = 0; 4527edd504SSong Gao tlb_rplv = 0; 4627edd504SSong Gao } 4727edd504SSong Gao 4827edd504SSong Gao /* Remove sw bit between bit12 -- bit PS*/ 4927edd504SSong Gao tlb_ppn = tlb_ppn & ~(((0x1UL << (tlb_ps - 12)) -1)); 5027edd504SSong Gao 5127edd504SSong Gao /* Check access rights */ 5227edd504SSong Gao if (!tlb_v) { 5327edd504SSong Gao return TLBRET_INVALID; 5427edd504SSong Gao } 5527edd504SSong Gao 5627edd504SSong Gao if (access_type == MMU_INST_FETCH && tlb_nx) { 5727edd504SSong Gao return TLBRET_XI; 5827edd504SSong Gao } 5927edd504SSong Gao 6027edd504SSong Gao if (access_type == MMU_DATA_LOAD && tlb_nr) { 6127edd504SSong Gao return TLBRET_RI; 6227edd504SSong Gao } 6327edd504SSong Gao 6427edd504SSong Gao if (((tlb_rplv == 0) && (plv > tlb_plv)) || 6527edd504SSong Gao ((tlb_rplv == 1) && (plv != tlb_plv))) { 6627edd504SSong Gao return TLBRET_PE; 6727edd504SSong Gao } 6827edd504SSong Gao 6927edd504SSong Gao if ((access_type == MMU_DATA_STORE) && !tlb_d) { 7027edd504SSong Gao return TLBRET_DIRTY; 7127edd504SSong Gao } 7227edd504SSong Gao 7327edd504SSong Gao *physical = (tlb_ppn << R_TLBENTRY_64_PPN_SHIFT) | 7427edd504SSong Gao (address & MAKE_64BIT_MASK(0, tlb_ps)); 7527edd504SSong Gao *prot = PAGE_READ; 7627edd504SSong Gao if (tlb_d) { 7727edd504SSong Gao *prot |= PAGE_WRITE; 7827edd504SSong Gao } 7927edd504SSong Gao if (!tlb_nx) { 8027edd504SSong Gao *prot |= PAGE_EXEC; 8127edd504SSong Gao } 8227edd504SSong Gao return TLBRET_MATCH; 8327edd504SSong Gao } 8427edd504SSong Gao 8527edd504SSong Gao /* 8627edd504SSong Gao * One tlb entry holds an adjacent odd/even pair, the vpn is the 8727edd504SSong Gao * content of the virtual page number divided by 2. So the 8827edd504SSong Gao * compare vpn is bit[47:15] for 16KiB page. while the vppn 8927edd504SSong Gao * field in tlb entry contains bit[47:13], so need adjust. 9027edd504SSong Gao * virt_vpn = vaddr[47:13] 9127edd504SSong Gao */ 9227edd504SSong Gao bool loongarch_tlb_search(CPULoongArchState *env, target_ulong vaddr, 9327edd504SSong Gao int *index) 9427edd504SSong Gao { 9527edd504SSong Gao LoongArchTLB *tlb; 9627edd504SSong Gao uint16_t csr_asid, tlb_asid, stlb_idx; 9727edd504SSong Gao uint8_t tlb_e, tlb_ps, tlb_g, stlb_ps; 9827edd504SSong Gao int i, compare_shift; 9927edd504SSong Gao uint64_t vpn, tlb_vppn; 10027edd504SSong Gao 10127edd504SSong Gao csr_asid = FIELD_EX64(env->CSR_ASID, CSR_ASID, ASID); 10227edd504SSong Gao stlb_ps = FIELD_EX64(env->CSR_STLBPS, CSR_STLBPS, PS); 10327edd504SSong Gao vpn = (vaddr & TARGET_VIRT_MASK) >> (stlb_ps + 1); 10427edd504SSong Gao stlb_idx = vpn & 0xff; /* VA[25:15] <==> TLBIDX.index for 16KiB Page */ 10527edd504SSong Gao compare_shift = stlb_ps + 1 - R_TLB_MISC_VPPN_SHIFT; 10627edd504SSong Gao 10727edd504SSong Gao /* Search STLB */ 10827edd504SSong Gao for (i = 0; i < 8; ++i) { 10927edd504SSong Gao tlb = &env->tlb[i * 256 + stlb_idx]; 11027edd504SSong Gao tlb_e = FIELD_EX64(tlb->tlb_misc, TLB_MISC, E); 11127edd504SSong Gao if (tlb_e) { 11227edd504SSong Gao tlb_vppn = FIELD_EX64(tlb->tlb_misc, TLB_MISC, VPPN); 11327edd504SSong Gao tlb_asid = FIELD_EX64(tlb->tlb_misc, TLB_MISC, ASID); 11427edd504SSong Gao tlb_g = FIELD_EX64(tlb->tlb_entry0, TLBENTRY, G); 11527edd504SSong Gao 11627edd504SSong Gao if ((tlb_g == 1 || tlb_asid == csr_asid) && 11727edd504SSong Gao (vpn == (tlb_vppn >> compare_shift))) { 11827edd504SSong Gao *index = i * 256 + stlb_idx; 11927edd504SSong Gao return true; 12027edd504SSong Gao } 12127edd504SSong Gao } 12227edd504SSong Gao } 12327edd504SSong Gao 12427edd504SSong Gao /* Search MTLB */ 12527edd504SSong Gao for (i = LOONGARCH_STLB; i < LOONGARCH_TLB_MAX; ++i) { 12627edd504SSong Gao tlb = &env->tlb[i]; 12727edd504SSong Gao tlb_e = FIELD_EX64(tlb->tlb_misc, TLB_MISC, E); 12827edd504SSong Gao if (tlb_e) { 12927edd504SSong Gao tlb_vppn = FIELD_EX64(tlb->tlb_misc, TLB_MISC, VPPN); 13027edd504SSong Gao tlb_ps = FIELD_EX64(tlb->tlb_misc, TLB_MISC, PS); 13127edd504SSong Gao tlb_asid = FIELD_EX64(tlb->tlb_misc, TLB_MISC, ASID); 13227edd504SSong Gao tlb_g = FIELD_EX64(tlb->tlb_entry0, TLBENTRY, G); 13327edd504SSong Gao compare_shift = tlb_ps + 1 - R_TLB_MISC_VPPN_SHIFT; 13427edd504SSong Gao vpn = (vaddr & TARGET_VIRT_MASK) >> (tlb_ps + 1); 13527edd504SSong Gao if ((tlb_g == 1 || tlb_asid == csr_asid) && 13627edd504SSong Gao (vpn == (tlb_vppn >> compare_shift))) { 13727edd504SSong Gao *index = i; 13827edd504SSong Gao return true; 13927edd504SSong Gao } 14027edd504SSong Gao } 14127edd504SSong Gao } 14227edd504SSong Gao return false; 14327edd504SSong Gao } 14427edd504SSong Gao 145dd291171SMiao Hao static int loongarch_page_table_walker(CPULoongArchState *env, hwaddr *physical, 146dd291171SMiao Hao int *prot, target_ulong address) 147dd291171SMiao Hao { 148dd291171SMiao Hao CPUState *cs = env_cpu(env); 149dd291171SMiao Hao target_ulong index, phys; 150dd291171SMiao Hao uint64_t dir_base, dir_width; 151dd291171SMiao Hao uint64_t base; 152dd291171SMiao Hao int level; 153dd291171SMiao Hao 154dd291171SMiao Hao if ((address >> 63) & 0x1) { 155dd291171SMiao Hao base = env->CSR_PGDH; 156dd291171SMiao Hao } else { 157dd291171SMiao Hao base = env->CSR_PGDL; 158dd291171SMiao Hao } 159dd291171SMiao Hao base &= TARGET_PHYS_MASK; 160dd291171SMiao Hao 161dd291171SMiao Hao for (level = 4; level > 0; level--) { 162dd291171SMiao Hao get_dir_base_width(env, &dir_base, &dir_width, level); 163dd291171SMiao Hao 164dd291171SMiao Hao if (dir_width == 0) { 165dd291171SMiao Hao continue; 166dd291171SMiao Hao } 167dd291171SMiao Hao 168dd291171SMiao Hao /* get next level page directory */ 169dd291171SMiao Hao index = (address >> dir_base) & ((1 << dir_width) - 1); 170dd291171SMiao Hao phys = base | index << 3; 171dd291171SMiao Hao base = ldq_phys(cs->as, phys) & TARGET_PHYS_MASK; 172dd291171SMiao Hao if (FIELD_EX64(base, TLBENTRY, HUGE)) { 173dd291171SMiao Hao /* base is a huge pte */ 174dd291171SMiao Hao break; 175dd291171SMiao Hao } 176dd291171SMiao Hao } 177dd291171SMiao Hao 178dd291171SMiao Hao /* pte */ 179dd291171SMiao Hao if (FIELD_EX64(base, TLBENTRY, HUGE)) { 180dd291171SMiao Hao /* Huge Page. base is pte */ 181dd291171SMiao Hao base = FIELD_DP64(base, TLBENTRY, LEVEL, 0); 182dd291171SMiao Hao base = FIELD_DP64(base, TLBENTRY, HUGE, 0); 183dd291171SMiao Hao if (FIELD_EX64(base, TLBENTRY, HGLOBAL)) { 184dd291171SMiao Hao base = FIELD_DP64(base, TLBENTRY, HGLOBAL, 0); 185dd291171SMiao Hao base = FIELD_DP64(base, TLBENTRY, G, 1); 186dd291171SMiao Hao } 187dd291171SMiao Hao } else { 188dd291171SMiao Hao /* Normal Page. base points to pte */ 189dd291171SMiao Hao get_dir_base_width(env, &dir_base, &dir_width, 0); 190dd291171SMiao Hao index = (address >> dir_base) & ((1 << dir_width) - 1); 191dd291171SMiao Hao phys = base | index << 3; 192dd291171SMiao Hao base = ldq_phys(cs->as, phys); 193dd291171SMiao Hao } 194dd291171SMiao Hao 195dd291171SMiao Hao /* TODO: check plv and other bits? */ 196dd291171SMiao Hao 197dd291171SMiao Hao /* base is pte, in normal pte format */ 198dd291171SMiao Hao if (!FIELD_EX64(base, TLBENTRY, V)) { 199dd291171SMiao Hao return TLBRET_NOMATCH; 200dd291171SMiao Hao } 201dd291171SMiao Hao 202dd291171SMiao Hao if (!FIELD_EX64(base, TLBENTRY, D)) { 203dd291171SMiao Hao *prot = PAGE_READ; 204dd291171SMiao Hao } else { 205dd291171SMiao Hao *prot = PAGE_READ | PAGE_WRITE; 206dd291171SMiao Hao } 207dd291171SMiao Hao 208dd291171SMiao Hao /* get TARGET_PAGE_SIZE aligned physical address */ 209dd291171SMiao Hao base += (address & TARGET_PHYS_MASK) & ((1 << dir_base) - 1); 210dd291171SMiao Hao /* mask RPLV, NX, NR bits */ 211dd291171SMiao Hao base = FIELD_DP64(base, TLBENTRY_64, RPLV, 0); 212dd291171SMiao Hao base = FIELD_DP64(base, TLBENTRY_64, NX, 0); 213dd291171SMiao Hao base = FIELD_DP64(base, TLBENTRY_64, NR, 0); 214dd291171SMiao Hao /* mask other attribute bits */ 215dd291171SMiao Hao *physical = base & TARGET_PAGE_MASK; 216dd291171SMiao Hao 217dd291171SMiao Hao return 0; 218dd291171SMiao Hao } 219dd291171SMiao Hao 22027edd504SSong Gao static int loongarch_map_address(CPULoongArchState *env, hwaddr *physical, 22127edd504SSong Gao int *prot, target_ulong address, 222dd291171SMiao Hao MMUAccessType access_type, int mmu_idx, 223dd291171SMiao Hao int is_debug) 22427edd504SSong Gao { 22527edd504SSong Gao int index, match; 22627edd504SSong Gao 22727edd504SSong Gao match = loongarch_tlb_search(env, address, &index); 22827edd504SSong Gao if (match) { 22927edd504SSong Gao return loongarch_map_tlb_entry(env, physical, prot, 23027edd504SSong Gao address, access_type, index, mmu_idx); 231dd291171SMiao Hao } else if (is_debug) { 232dd291171SMiao Hao /* 233dd291171SMiao Hao * For debugger memory access, we want to do the map when there is a 234dd291171SMiao Hao * legal mapping, even if the mapping is not yet in TLB. return 0 if 235dd291171SMiao Hao * there is a valid map, else none zero. 236dd291171SMiao Hao */ 237dd291171SMiao Hao return loongarch_page_table_walker(env, physical, prot, address); 23827edd504SSong Gao } 23927edd504SSong Gao 24027edd504SSong Gao return TLBRET_NOMATCH; 24127edd504SSong Gao } 2426f703a48SBibo Mao #else 2436f703a48SBibo Mao static int loongarch_map_address(CPULoongArchState *env, hwaddr *physical, 2446f703a48SBibo Mao int *prot, target_ulong address, 245dd291171SMiao Hao MMUAccessType access_type, int mmu_idx, 246dd291171SMiao Hao int is_debug) 2476f703a48SBibo Mao { 2486f703a48SBibo Mao return TLBRET_NOMATCH; 2496f703a48SBibo Mao } 2506f703a48SBibo Mao #endif 25127edd504SSong Gao 25227edd504SSong Gao static hwaddr dmw_va2pa(CPULoongArchState *env, target_ulong va, 25327edd504SSong Gao target_ulong dmw) 25427edd504SSong Gao { 25527edd504SSong Gao if (is_la64(env)) { 25627edd504SSong Gao return va & TARGET_VIRT_MASK; 25727edd504SSong Gao } else { 25827edd504SSong Gao uint32_t pseg = FIELD_EX32(dmw, CSR_DMW_32, PSEG); 25927edd504SSong Gao return (va & MAKE_64BIT_MASK(0, R_CSR_DMW_32_VSEG_SHIFT)) | \ 26027edd504SSong Gao (pseg << R_CSR_DMW_32_VSEG_SHIFT); 26127edd504SSong Gao } 26227edd504SSong Gao } 26327edd504SSong Gao 26427edd504SSong Gao int get_physical_address(CPULoongArchState *env, hwaddr *physical, 26527edd504SSong Gao int *prot, target_ulong address, 266dd291171SMiao Hao MMUAccessType access_type, int mmu_idx, int is_debug) 26727edd504SSong Gao { 2683f262d25SRichard Henderson int user_mode = mmu_idx == MMU_USER_IDX; 2693f262d25SRichard Henderson int kernel_mode = mmu_idx == MMU_KERNEL_IDX; 27027edd504SSong Gao uint32_t plv, base_c, base_v; 27127edd504SSong Gao int64_t addr_high; 27227edd504SSong Gao uint8_t da = FIELD_EX64(env->CSR_CRMD, CSR_CRMD, DA); 27327edd504SSong Gao uint8_t pg = FIELD_EX64(env->CSR_CRMD, CSR_CRMD, PG); 27427edd504SSong Gao 27527edd504SSong Gao /* Check PG and DA */ 27627edd504SSong Gao if (da & !pg) { 27727edd504SSong Gao *physical = address & TARGET_PHYS_MASK; 27827edd504SSong Gao *prot = PAGE_READ | PAGE_WRITE | PAGE_EXEC; 27927edd504SSong Gao return TLBRET_MATCH; 28027edd504SSong Gao } 28127edd504SSong Gao 28227edd504SSong Gao plv = kernel_mode | (user_mode << R_CSR_DMW_PLV3_SHIFT); 28327edd504SSong Gao if (is_la64(env)) { 28427edd504SSong Gao base_v = address >> R_CSR_DMW_64_VSEG_SHIFT; 28527edd504SSong Gao } else { 28627edd504SSong Gao base_v = address >> R_CSR_DMW_32_VSEG_SHIFT; 28727edd504SSong Gao } 28827edd504SSong Gao /* Check direct map window */ 28927edd504SSong Gao for (int i = 0; i < 4; i++) { 29027edd504SSong Gao if (is_la64(env)) { 29127edd504SSong Gao base_c = FIELD_EX64(env->CSR_DMW[i], CSR_DMW_64, VSEG); 29227edd504SSong Gao } else { 29327edd504SSong Gao base_c = FIELD_EX64(env->CSR_DMW[i], CSR_DMW_32, VSEG); 29427edd504SSong Gao } 29527edd504SSong Gao if ((plv & env->CSR_DMW[i]) && (base_c == base_v)) { 29627edd504SSong Gao *physical = dmw_va2pa(env, address, env->CSR_DMW[i]); 29727edd504SSong Gao *prot = PAGE_READ | PAGE_WRITE | PAGE_EXEC; 29827edd504SSong Gao return TLBRET_MATCH; 29927edd504SSong Gao } 30027edd504SSong Gao } 30127edd504SSong Gao 30227edd504SSong Gao /* Check valid extension */ 30327edd504SSong Gao addr_high = sextract64(address, TARGET_VIRT_ADDR_SPACE_BITS, 16); 30427edd504SSong Gao if (!(addr_high == 0 || addr_high == -1)) { 30527edd504SSong Gao return TLBRET_BADADDR; 30627edd504SSong Gao } 30727edd504SSong Gao 30827edd504SSong Gao /* Mapped address */ 30927edd504SSong Gao return loongarch_map_address(env, physical, prot, address, 310dd291171SMiao Hao access_type, mmu_idx, is_debug); 31127edd504SSong Gao } 31227edd504SSong Gao 31327edd504SSong Gao hwaddr loongarch_cpu_get_phys_page_debug(CPUState *cs, vaddr addr) 31427edd504SSong Gao { 315f3b603b9SPhilippe Mathieu-Daudé CPULoongArchState *env = cpu_env(cs); 31627edd504SSong Gao hwaddr phys_addr; 31727edd504SSong Gao int prot; 31827edd504SSong Gao 31927edd504SSong Gao if (get_physical_address(env, &phys_addr, &prot, addr, MMU_DATA_LOAD, 320dd291171SMiao Hao cpu_mmu_index(cs, false), 1) != 0) { 32127edd504SSong Gao return -1; 32227edd504SSong Gao } 32327edd504SSong Gao return phys_addr; 32427edd504SSong Gao } 325