]>
Commit | Line | Data |
---|---|---|
27edd504 SG |
1 | /* SPDX-License-Identifier: GPL-2.0-or-later */ |
2 | /* | |
3 | * LoongArch CPU helpers for qemu | |
4 | * | |
5 | * Copyright (c) 2024 Loongson Technology Corporation Limited | |
6 | * | |
7 | */ | |
8 | ||
9 | #include "qemu/osdep.h" | |
10 | #include "cpu.h" | |
11 | #include "internals.h" | |
12 | #include "cpu-csr.h" | |
13 | ||
14 | static int loongarch_map_tlb_entry(CPULoongArchState *env, hwaddr *physical, | |
15 | int *prot, target_ulong address, | |
16 | int access_type, int index, int mmu_idx) | |
17 | { | |
18 | LoongArchTLB *tlb = &env->tlb[index]; | |
19 | uint64_t plv = mmu_idx; | |
20 | uint64_t tlb_entry, tlb_ppn; | |
21 | uint8_t tlb_ps, n, tlb_v, tlb_d, tlb_plv, tlb_nx, tlb_nr, tlb_rplv; | |
22 | ||
23 | if (index >= LOONGARCH_STLB) { | |
24 | tlb_ps = FIELD_EX64(tlb->tlb_misc, TLB_MISC, PS); | |
25 | } else { | |
26 | tlb_ps = FIELD_EX64(env->CSR_STLBPS, CSR_STLBPS, PS); | |
27 | } | |
28 | n = (address >> tlb_ps) & 0x1;/* Odd or even */ | |
29 | ||
30 | tlb_entry = n ? tlb->tlb_entry1 : tlb->tlb_entry0; | |
31 | tlb_v = FIELD_EX64(tlb_entry, TLBENTRY, V); | |
32 | tlb_d = FIELD_EX64(tlb_entry, TLBENTRY, D); | |
33 | tlb_plv = FIELD_EX64(tlb_entry, TLBENTRY, PLV); | |
34 | if (is_la64(env)) { | |
35 | tlb_ppn = FIELD_EX64(tlb_entry, TLBENTRY_64, PPN); | |
36 | tlb_nx = FIELD_EX64(tlb_entry, TLBENTRY_64, NX); | |
37 | tlb_nr = FIELD_EX64(tlb_entry, TLBENTRY_64, NR); | |
38 | tlb_rplv = FIELD_EX64(tlb_entry, TLBENTRY_64, RPLV); | |
39 | } else { | |
40 | tlb_ppn = FIELD_EX64(tlb_entry, TLBENTRY_32, PPN); | |
41 | tlb_nx = 0; | |
42 | tlb_nr = 0; | |
43 | tlb_rplv = 0; | |
44 | } | |
45 | ||
46 | /* Remove sw bit between bit12 -- bit PS*/ | |
47 | tlb_ppn = tlb_ppn & ~(((0x1UL << (tlb_ps - 12)) -1)); | |
48 | ||
49 | /* Check access rights */ | |
50 | if (!tlb_v) { | |
51 | return TLBRET_INVALID; | |
52 | } | |
53 | ||
54 | if (access_type == MMU_INST_FETCH && tlb_nx) { | |
55 | return TLBRET_XI; | |
56 | } | |
57 | ||
58 | if (access_type == MMU_DATA_LOAD && tlb_nr) { | |
59 | return TLBRET_RI; | |
60 | } | |
61 | ||
62 | if (((tlb_rplv == 0) && (plv > tlb_plv)) || | |
63 | ((tlb_rplv == 1) && (plv != tlb_plv))) { | |
64 | return TLBRET_PE; | |
65 | } | |
66 | ||
67 | if ((access_type == MMU_DATA_STORE) && !tlb_d) { | |
68 | return TLBRET_DIRTY; | |
69 | } | |
70 | ||
71 | *physical = (tlb_ppn << R_TLBENTRY_64_PPN_SHIFT) | | |
72 | (address & MAKE_64BIT_MASK(0, tlb_ps)); | |
73 | *prot = PAGE_READ; | |
74 | if (tlb_d) { | |
75 | *prot |= PAGE_WRITE; | |
76 | } | |
77 | if (!tlb_nx) { | |
78 | *prot |= PAGE_EXEC; | |
79 | } | |
80 | return TLBRET_MATCH; | |
81 | } | |
82 | ||
83 | /* | |
84 | * One tlb entry holds an adjacent odd/even pair, the vpn is the | |
85 | * content of the virtual page number divided by 2. So the | |
86 | * compare vpn is bit[47:15] for 16KiB page. while the vppn | |
87 | * field in tlb entry contains bit[47:13], so need adjust. | |
88 | * virt_vpn = vaddr[47:13] | |
89 | */ | |
90 | bool loongarch_tlb_search(CPULoongArchState *env, target_ulong vaddr, | |
91 | int *index) | |
92 | { | |
93 | LoongArchTLB *tlb; | |
94 | uint16_t csr_asid, tlb_asid, stlb_idx; | |
95 | uint8_t tlb_e, tlb_ps, tlb_g, stlb_ps; | |
96 | int i, compare_shift; | |
97 | uint64_t vpn, tlb_vppn; | |
98 | ||
99 | csr_asid = FIELD_EX64(env->CSR_ASID, CSR_ASID, ASID); | |
100 | stlb_ps = FIELD_EX64(env->CSR_STLBPS, CSR_STLBPS, PS); | |
101 | vpn = (vaddr & TARGET_VIRT_MASK) >> (stlb_ps + 1); | |
102 | stlb_idx = vpn & 0xff; /* VA[25:15] <==> TLBIDX.index for 16KiB Page */ | |
103 | compare_shift = stlb_ps + 1 - R_TLB_MISC_VPPN_SHIFT; | |
104 | ||
105 | /* Search STLB */ | |
106 | for (i = 0; i < 8; ++i) { | |
107 | tlb = &env->tlb[i * 256 + stlb_idx]; | |
108 | tlb_e = FIELD_EX64(tlb->tlb_misc, TLB_MISC, E); | |
109 | if (tlb_e) { | |
110 | tlb_vppn = FIELD_EX64(tlb->tlb_misc, TLB_MISC, VPPN); | |
111 | tlb_asid = FIELD_EX64(tlb->tlb_misc, TLB_MISC, ASID); | |
112 | tlb_g = FIELD_EX64(tlb->tlb_entry0, TLBENTRY, G); | |
113 | ||
114 | if ((tlb_g == 1 || tlb_asid == csr_asid) && | |
115 | (vpn == (tlb_vppn >> compare_shift))) { | |
116 | *index = i * 256 + stlb_idx; | |
117 | return true; | |
118 | } | |
119 | } | |
120 | } | |
121 | ||
122 | /* Search MTLB */ | |
123 | for (i = LOONGARCH_STLB; i < LOONGARCH_TLB_MAX; ++i) { | |
124 | tlb = &env->tlb[i]; | |
125 | tlb_e = FIELD_EX64(tlb->tlb_misc, TLB_MISC, E); | |
126 | if (tlb_e) { | |
127 | tlb_vppn = FIELD_EX64(tlb->tlb_misc, TLB_MISC, VPPN); | |
128 | tlb_ps = FIELD_EX64(tlb->tlb_misc, TLB_MISC, PS); | |
129 | tlb_asid = FIELD_EX64(tlb->tlb_misc, TLB_MISC, ASID); | |
130 | tlb_g = FIELD_EX64(tlb->tlb_entry0, TLBENTRY, G); | |
131 | compare_shift = tlb_ps + 1 - R_TLB_MISC_VPPN_SHIFT; | |
132 | vpn = (vaddr & TARGET_VIRT_MASK) >> (tlb_ps + 1); | |
133 | if ((tlb_g == 1 || tlb_asid == csr_asid) && | |
134 | (vpn == (tlb_vppn >> compare_shift))) { | |
135 | *index = i; | |
136 | return true; | |
137 | } | |
138 | } | |
139 | } | |
140 | return false; | |
141 | } | |
142 | ||
143 | static int loongarch_map_address(CPULoongArchState *env, hwaddr *physical, | |
144 | int *prot, target_ulong address, | |
145 | MMUAccessType access_type, int mmu_idx) | |
146 | { | |
147 | int index, match; | |
148 | ||
149 | match = loongarch_tlb_search(env, address, &index); | |
150 | if (match) { | |
151 | return loongarch_map_tlb_entry(env, physical, prot, | |
152 | address, access_type, index, mmu_idx); | |
153 | } | |
154 | ||
155 | return TLBRET_NOMATCH; | |
156 | } | |
157 | ||
158 | static hwaddr dmw_va2pa(CPULoongArchState *env, target_ulong va, | |
159 | target_ulong dmw) | |
160 | { | |
161 | if (is_la64(env)) { | |
162 | return va & TARGET_VIRT_MASK; | |
163 | } else { | |
164 | uint32_t pseg = FIELD_EX32(dmw, CSR_DMW_32, PSEG); | |
165 | return (va & MAKE_64BIT_MASK(0, R_CSR_DMW_32_VSEG_SHIFT)) | \ | |
166 | (pseg << R_CSR_DMW_32_VSEG_SHIFT); | |
167 | } | |
168 | } | |
169 | ||
170 | int get_physical_address(CPULoongArchState *env, hwaddr *physical, | |
171 | int *prot, target_ulong address, | |
172 | MMUAccessType access_type, int mmu_idx) | |
173 | { | |
174 | int user_mode = mmu_idx == MMU_IDX_USER; | |
175 | int kernel_mode = mmu_idx == MMU_IDX_KERNEL; | |
176 | uint32_t plv, base_c, base_v; | |
177 | int64_t addr_high; | |
178 | uint8_t da = FIELD_EX64(env->CSR_CRMD, CSR_CRMD, DA); | |
179 | uint8_t pg = FIELD_EX64(env->CSR_CRMD, CSR_CRMD, PG); | |
180 | ||
181 | /* Check PG and DA */ | |
182 | if (da & !pg) { | |
183 | *physical = address & TARGET_PHYS_MASK; | |
184 | *prot = PAGE_READ | PAGE_WRITE | PAGE_EXEC; | |
185 | return TLBRET_MATCH; | |
186 | } | |
187 | ||
188 | plv = kernel_mode | (user_mode << R_CSR_DMW_PLV3_SHIFT); | |
189 | if (is_la64(env)) { | |
190 | base_v = address >> R_CSR_DMW_64_VSEG_SHIFT; | |
191 | } else { | |
192 | base_v = address >> R_CSR_DMW_32_VSEG_SHIFT; | |
193 | } | |
194 | /* Check direct map window */ | |
195 | for (int i = 0; i < 4; i++) { | |
196 | if (is_la64(env)) { | |
197 | base_c = FIELD_EX64(env->CSR_DMW[i], CSR_DMW_64, VSEG); | |
198 | } else { | |
199 | base_c = FIELD_EX64(env->CSR_DMW[i], CSR_DMW_32, VSEG); | |
200 | } | |
201 | if ((plv & env->CSR_DMW[i]) && (base_c == base_v)) { | |
202 | *physical = dmw_va2pa(env, address, env->CSR_DMW[i]); | |
203 | *prot = PAGE_READ | PAGE_WRITE | PAGE_EXEC; | |
204 | return TLBRET_MATCH; | |
205 | } | |
206 | } | |
207 | ||
208 | /* Check valid extension */ | |
209 | addr_high = sextract64(address, TARGET_VIRT_ADDR_SPACE_BITS, 16); | |
210 | if (!(addr_high == 0 || addr_high == -1)) { | |
211 | return TLBRET_BADADDR; | |
212 | } | |
213 | ||
214 | /* Mapped address */ | |
215 | return loongarch_map_address(env, physical, prot, address, | |
216 | access_type, mmu_idx); | |
217 | } | |
218 | ||
219 | hwaddr loongarch_cpu_get_phys_page_debug(CPUState *cs, vaddr addr) | |
220 | { | |
221 | LoongArchCPU *cpu = LOONGARCH_CPU(cs); | |
222 | CPULoongArchState *env = &cpu->env; | |
223 | hwaddr phys_addr; | |
224 | int prot; | |
225 | ||
226 | if (get_physical_address(env, &phys_addr, &prot, addr, MMU_DATA_LOAD, | |
227 | cpu_mmu_index(env, false)) != 0) { | |
228 | return -1; | |
229 | } | |
230 | return phys_addr; | |
231 | } |