xref: /openbmc/qemu/target/ppc/mmu-radix64.c (revision fe7f9b8e)
1 /*
2  *  PowerPC Radix MMU mulation helpers for QEMU.
3  *
4  *  Copyright (c) 2016 Suraj Jitindar Singh, IBM Corporation
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include "qemu/osdep.h"
21 #include "cpu.h"
22 #include "exec/exec-all.h"
23 #include "exec/helper-proto.h"
24 #include "qemu/error-report.h"
25 #include "sysemu/kvm.h"
26 #include "kvm_ppc.h"
27 #include "exec/log.h"
28 #include "mmu-radix64.h"
29 #include "mmu-book3s-v3.h"
30 
31 static bool ppc_radix64_get_fully_qualified_addr(CPUPPCState *env, vaddr eaddr,
32                                                  uint64_t *lpid, uint64_t *pid)
33 {
34     /* We don't have HV support yet and shouldn't get here with it set anyway */
35     assert(!msr_hv);
36 
37     if (!msr_hv) { /* !MSR[HV] -> Guest */
38         switch (eaddr & R_EADDR_QUADRANT) {
39         case R_EADDR_QUADRANT0: /* Guest application */
40             *lpid = env->spr[SPR_LPIDR];
41             *pid = env->spr[SPR_BOOKS_PID];
42             break;
43         case R_EADDR_QUADRANT1: /* Illegal */
44         case R_EADDR_QUADRANT2:
45             return false;
46         case R_EADDR_QUADRANT3: /* Guest OS */
47             *lpid = env->spr[SPR_LPIDR];
48             *pid = 0; /* pid set to 0 -> addresses guest operating system */
49             break;
50         }
51     }
52 
53     return true;
54 }
55 
56 static void ppc_radix64_raise_segi(PowerPCCPU *cpu, int rwx, vaddr eaddr)
57 {
58     CPUState *cs = CPU(cpu);
59     CPUPPCState *env = &cpu->env;
60 
61     if (rwx == 2) { /* Instruction Segment Interrupt */
62         cs->exception_index = POWERPC_EXCP_ISEG;
63     } else { /* Data Segment Interrupt */
64         cs->exception_index = POWERPC_EXCP_DSEG;
65         env->spr[SPR_DAR] = eaddr;
66     }
67     env->error_code = 0;
68 }
69 
70 static void ppc_radix64_raise_si(PowerPCCPU *cpu, int rwx, vaddr eaddr,
71                                 uint32_t cause)
72 {
73     CPUState *cs = CPU(cpu);
74     CPUPPCState *env = &cpu->env;
75 
76     if (rwx == 2) { /* Instruction Storage Interrupt */
77         cs->exception_index = POWERPC_EXCP_ISI;
78         env->error_code = cause;
79     } else { /* Data Storage Interrupt */
80         cs->exception_index = POWERPC_EXCP_DSI;
81         if (rwx == 1) { /* Write -> Store */
82             cause |= DSISR_ISSTORE;
83         }
84         env->spr[SPR_DSISR] = cause;
85         env->spr[SPR_DAR] = eaddr;
86         env->error_code = 0;
87     }
88 }
89 
90 
91 static bool ppc_radix64_check_prot(PowerPCCPU *cpu, int rwx, uint64_t pte,
92                                    int *fault_cause, int *prot)
93 {
94     CPUPPCState *env = &cpu->env;
95     const int need_prot[] = { PAGE_READ, PAGE_WRITE, PAGE_EXEC };
96 
97     /* Check Page Attributes (pte58:59) */
98     if (((pte & R_PTE_ATT) == R_PTE_ATT_NI_IO) && (rwx == 2)) {
99         /*
100          * Radix PTE entries with the non-idempotent I/O attribute are treated
101          * as guarded storage
102          */
103         *fault_cause |= SRR1_NOEXEC_GUARD;
104         return true;
105     }
106 
107     /* Determine permissions allowed by Encoded Access Authority */
108     if ((pte & R_PTE_EAA_PRIV) && msr_pr) { /* Insufficient Privilege */
109         *prot = 0;
110     } else if (msr_pr || (pte & R_PTE_EAA_PRIV)) {
111         *prot = ppc_radix64_get_prot_eaa(pte);
112     } else { /* !msr_pr && !(pte & R_PTE_EAA_PRIV) */
113         *prot = ppc_radix64_get_prot_eaa(pte);
114         *prot &= ppc_radix64_get_prot_amr(cpu); /* Least combined permissions */
115     }
116 
117     /* Check if requested access type is allowed */
118     if (need_prot[rwx] & ~(*prot)) { /* Page Protected for that Access */
119         *fault_cause |= DSISR_PROTFAULT;
120         return true;
121     }
122 
123     return false;
124 }
125 
126 static void ppc_radix64_set_rc(PowerPCCPU *cpu, int rwx, uint64_t pte,
127                                hwaddr pte_addr, int *prot)
128 {
129     CPUState *cs = CPU(cpu);
130     uint64_t npte;
131 
132     npte = pte | R_PTE_R; /* Always set reference bit */
133 
134     if (rwx == 1) { /* Store/Write */
135         npte |= R_PTE_C; /* Set change bit */
136     } else {
137         /*
138          * Treat the page as read-only for now, so that a later write
139          * will pass through this function again to set the C bit.
140          */
141         *prot &= ~PAGE_WRITE;
142     }
143 
144     if (pte ^ npte) { /* If pte has changed then write it back */
145         stq_phys(cs->as, pte_addr, npte);
146     }
147 }
148 
149 static uint64_t ppc_radix64_walk_tree(PowerPCCPU *cpu, vaddr eaddr,
150                                       uint64_t base_addr, uint64_t nls,
151                                       hwaddr *raddr, int *psize,
152                                       int *fault_cause, hwaddr *pte_addr)
153 {
154     CPUState *cs = CPU(cpu);
155     uint64_t index, pde;
156 
157     if (nls < 5) { /* Directory maps less than 2**5 entries */
158         *fault_cause |= DSISR_R_BADCONFIG;
159         return 0;
160     }
161 
162     /* Read page <directory/table> entry from guest address space */
163     index = eaddr >> (*psize - nls); /* Shift */
164     index &= ((1UL << nls) - 1); /* Mask */
165     pde = ldq_phys(cs->as, base_addr + (index * sizeof(pde)));
166     if (!(pde & R_PTE_VALID)) { /* Invalid Entry */
167         *fault_cause |= DSISR_NOPTE;
168         return 0;
169     }
170 
171     *psize -= nls;
172 
173     /* Check if Leaf Entry -> Page Table Entry -> Stop the Search */
174     if (pde & R_PTE_LEAF) {
175         uint64_t rpn = pde & R_PTE_RPN;
176         uint64_t mask = (1UL << *psize) - 1;
177 
178         /* Or high bits of rpn and low bits to ea to form whole real addr */
179         *raddr = (rpn & ~mask) | (eaddr & mask);
180         *pte_addr = base_addr + (index * sizeof(pde));
181         return pde;
182     }
183 
184     /* Next Level of Radix Tree */
185     return ppc_radix64_walk_tree(cpu, eaddr, pde & R_PDE_NLB, pde & R_PDE_NLS,
186                                  raddr, psize, fault_cause, pte_addr);
187 }
188 
189 int ppc_radix64_handle_mmu_fault(PowerPCCPU *cpu, vaddr eaddr, int rwx,
190                                  int mmu_idx)
191 {
192     CPUState *cs = CPU(cpu);
193     CPUPPCState *env = &cpu->env;
194     PPCVirtualHypervisorClass *vhc =
195         PPC_VIRTUAL_HYPERVISOR_GET_CLASS(cpu->vhyp);
196     hwaddr raddr, pte_addr;
197     uint64_t lpid = 0, pid = 0, offset, size, patbe, prtbe0, pte;
198     int page_size, prot, fault_cause = 0;
199 
200     assert((rwx == 0) || (rwx == 1) || (rwx == 2));
201     assert(!msr_hv); /* For now there is no Radix PowerNV Support */
202     assert(cpu->vhyp);
203     assert(ppc64_use_proc_tbl(cpu));
204 
205     /* Real Mode Access */
206     if (((rwx == 2) && (msr_ir == 0)) || ((rwx != 2) && (msr_dr == 0))) {
207         /* In real mode top 4 effective addr bits (mostly) ignored */
208         raddr = eaddr & 0x0FFFFFFFFFFFFFFFULL;
209 
210         tlb_set_page(cs, eaddr & TARGET_PAGE_MASK, raddr & TARGET_PAGE_MASK,
211                      PAGE_READ | PAGE_WRITE | PAGE_EXEC, mmu_idx,
212                      TARGET_PAGE_SIZE);
213         return 0;
214     }
215 
216     /* Virtual Mode Access - get the fully qualified address */
217     if (!ppc_radix64_get_fully_qualified_addr(env, eaddr, &lpid, &pid)) {
218         ppc_radix64_raise_segi(cpu, rwx, eaddr);
219         return 1;
220     }
221 
222     /* Get Process Table */
223     patbe = vhc->get_patbe(cpu->vhyp);
224 
225     /* Index Process Table by PID to Find Corresponding Process Table Entry */
226     offset = pid * sizeof(struct prtb_entry);
227     size = 1ULL << ((patbe & PATBE1_R_PRTS) + 12);
228     if (offset >= size) {
229         /* offset exceeds size of the process table */
230         ppc_radix64_raise_si(cpu, rwx, eaddr, DSISR_NOPTE);
231         return 1;
232     }
233     prtbe0 = ldq_phys(cs->as, (patbe & PATBE1_R_PRTB) + offset);
234 
235     /* Walk Radix Tree from Process Table Entry to Convert EA to RA */
236     page_size = PRTBE_R_GET_RTS(prtbe0);
237     pte = ppc_radix64_walk_tree(cpu, eaddr & R_EADDR_MASK,
238                                 prtbe0 & PRTBE_R_RPDB, prtbe0 & PRTBE_R_RPDS,
239                                 &raddr, &page_size, &fault_cause, &pte_addr);
240     if (!pte || ppc_radix64_check_prot(cpu, rwx, pte, &fault_cause, &prot)) {
241         /* Couldn't get pte or access denied due to protection */
242         ppc_radix64_raise_si(cpu, rwx, eaddr, fault_cause);
243         return 1;
244     }
245 
246     /* Update Reference and Change Bits */
247     ppc_radix64_set_rc(cpu, rwx, pte, pte_addr, &prot);
248 
249     tlb_set_page(cs, eaddr & TARGET_PAGE_MASK, raddr & TARGET_PAGE_MASK,
250                  prot, mmu_idx, 1UL << page_size);
251     return 0;
252 }
253 
254 hwaddr ppc_radix64_get_phys_page_debug(PowerPCCPU *cpu, target_ulong eaddr)
255 {
256     CPUState *cs = CPU(cpu);
257     CPUPPCState *env = &cpu->env;
258     PPCVirtualHypervisorClass *vhc =
259         PPC_VIRTUAL_HYPERVISOR_GET_CLASS(cpu->vhyp);
260     hwaddr raddr, pte_addr;
261     uint64_t lpid = 0, pid = 0, offset, size, patbe, prtbe0, pte;
262     int page_size, fault_cause = 0;
263 
264     /* Handle Real Mode */
265     if (msr_dr == 0) {
266         /* In real mode top 4 effective addr bits (mostly) ignored */
267         return eaddr & 0x0FFFFFFFFFFFFFFFULL;
268     }
269 
270     /* Virtual Mode Access - get the fully qualified address */
271     if (!ppc_radix64_get_fully_qualified_addr(env, eaddr, &lpid, &pid)) {
272         return -1;
273     }
274 
275     /* Get Process Table */
276     patbe = vhc->get_patbe(cpu->vhyp);
277 
278     /* Index Process Table by PID to Find Corresponding Process Table Entry */
279     offset = pid * sizeof(struct prtb_entry);
280     size = 1ULL << ((patbe & PATBE1_R_PRTS) + 12);
281     if (offset >= size) {
282         /* offset exceeds size of the process table */
283         return -1;
284     }
285     prtbe0 = ldq_phys(cs->as, (patbe & PATBE1_R_PRTB) + offset);
286 
287     /* Walk Radix Tree from Process Table Entry to Convert EA to RA */
288     page_size = PRTBE_R_GET_RTS(prtbe0);
289     pte = ppc_radix64_walk_tree(cpu, eaddr & R_EADDR_MASK,
290                                 prtbe0 & PRTBE_R_RPDB, prtbe0 & PRTBE_R_RPDS,
291                                 &raddr, &page_size, &fault_cause, &pte_addr);
292     if (!pte) {
293         return -1;
294     }
295 
296     return raddr & TARGET_PAGE_MASK;
297 }
298