2 * MIPS emulation helpers for qemu.
4 * Copyright (c) 2004-2005 Jocelyn Mayer
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.
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.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
39 /* MIPS32 4K MMU emulation */
40 #ifdef MIPS_USES_R4K_TLB
41 static int map_address (CPUState
*env
, target_ulong
*physical
, int *prot
,
42 target_ulong address
, int rw
, int access_type
)
44 target_ulong tag
= address
& (TARGET_PAGE_MASK
<< 1);
45 uint8_t ASID
= env
->CP0_EntryHi
& 0xFF;
49 for (i
= 0; i
< MIPS_TLB_NB
; i
++) {
51 /* Check ASID, virtual page number & size */
52 if ((tlb
->G
== 1 || tlb
->ASID
== ASID
) &&
53 tlb
->VPN
== tag
&& address
< tlb
->end2
) {
55 n
= (address
>> TARGET_PAGE_BITS
) & 1;
56 /* Check access rights */
57 if (!(n
? tlb
->V1
: tlb
->V0
))
58 return TLBRET_INVALID
;
59 if (rw
== 0 || (n
? tlb
->D1
: tlb
->D0
)) {
60 *physical
= tlb
->PFN
[n
] | (address
& ~TARGET_PAGE_MASK
);
62 if (n
? tlb
->D1
: tlb
->D0
)
69 return TLBRET_NOMATCH
;
73 static int get_physical_address (CPUState
*env
, target_ulong
*physical
,
74 int *prot
, target_ulong address
,
75 int rw
, int access_type
)
77 /* User mode can only access useg */
78 int user_mode
= (env
->hflags
& MIPS_HFLAG_MODE
) == MIPS_HFLAG_UM
;
79 int ret
= TLBRET_MATCH
;
83 fprintf(logfile
, "user mode %d h %08x\n",
84 user_mode
, env
->hflags
);
87 if (user_mode
&& address
> 0x7FFFFFFFUL
)
88 return TLBRET_BADADDR
;
89 if (address
< 0x80000000UL
) {
90 if (!(env
->hflags
& MIPS_HFLAG_ERL
)) {
91 #ifdef MIPS_USES_R4K_TLB
92 ret
= map_address(env
, physical
, prot
, address
, rw
, access_type
);
94 *physical
= address
+ 0x40000000UL
;
95 *prot
= PAGE_READ
| PAGE_WRITE
;
99 *prot
= PAGE_READ
| PAGE_WRITE
;
101 } else if (address
< 0xA0000000UL
) {
103 /* XXX: check supervisor mode */
104 *physical
= address
- 0x80000000UL
;
105 *prot
= PAGE_READ
| PAGE_WRITE
;
106 } else if (address
< 0xC0000000UL
) {
108 /* XXX: check supervisor mode */
109 *physical
= address
- 0xA0000000UL
;
110 *prot
= PAGE_READ
| PAGE_WRITE
;
111 } else if (address
< 0xE0000000UL
) {
113 #ifdef MIPS_USES_R4K_TLB
114 ret
= map_address(env
, physical
, prot
, address
, rw
, access_type
);
117 *prot
= PAGE_READ
| PAGE_WRITE
;
121 /* XXX: check supervisor mode */
122 /* XXX: debug segment is not emulated */
123 #ifdef MIPS_USES_R4K_TLB
124 ret
= map_address(env
, physical
, prot
, address
, rw
, access_type
);
127 *prot
= PAGE_READ
| PAGE_WRITE
;
132 fprintf(logfile
, "%08x %d %d => %08x %d (%d)\n", address
, rw
,
133 access_type
, *physical
, *prot
, ret
);
140 #if defined(CONFIG_USER_ONLY)
141 target_ulong
cpu_get_phys_page_debug(CPUState
*env
, target_ulong addr
)
146 target_ulong
cpu_get_phys_page_debug(CPUState
*env
, target_ulong addr
)
148 target_ulong phys_addr
;
151 if (get_physical_address(env
, &phys_addr
, &prot
, addr
, 0, ACCESS_INT
) != 0)
156 void cpu_mips_init_mmu (CPUState
*env
)
159 #endif /* !defined(CONFIG_USER_ONLY) */
161 int cpu_mips_handle_mmu_fault (CPUState
*env
, target_ulong address
, int rw
,
162 int is_user
, int is_softmmu
)
164 target_ulong physical
;
166 int exception
= 0, error_code
= 0;
172 cpu_dump_state(env
, logfile
, fprintf
, 0);
174 fprintf(logfile
, "%s pc %08x ad %08x rw %d is_user %d smmu %d\n",
175 __func__
, env
->PC
, address
, rw
, is_user
, is_softmmu
);
181 /* XXX: put correct access by using cpu_restore_state()
183 access_type
= ACCESS_INT
;
184 if (env
->user_mode_only
) {
185 /* user mode only emulation */
186 ret
= TLBRET_NOMATCH
;
189 ret
= get_physical_address(env
, &physical
, &prot
,
190 address
, rw
, access_type
);
192 fprintf(logfile
, "%s address=%08x ret %d physical %08x prot %d\n",
193 __func__
, address
, ret
, physical
, prot
);
195 if (ret
== TLBRET_MATCH
) {
196 ret
= tlb_set_page(env
, address
& TARGET_PAGE_MASK
,
197 physical
& TARGET_PAGE_MASK
, prot
,
198 is_user
, is_softmmu
);
199 } else if (ret
< 0) {
204 /* Reference to kernel address from user mode or supervisor mode */
205 /* Reference to supervisor address from user mode */
207 exception
= EXCP_AdES
;
209 exception
= EXCP_AdEL
;
212 /* No TLB match for a mapped address */
214 exception
= EXCP_TLBS
;
216 exception
= EXCP_TLBL
;
220 /* TLB match with no valid bit */
222 exception
= EXCP_TLBS
;
224 exception
= EXCP_TLBL
;
227 /* TLB match but 'D' bit is cleared */
228 exception
= EXCP_LTLBL
;
232 /* Raise exception */
233 env
->CP0_BadVAddr
= address
;
234 env
->CP0_Context
= (env
->CP0_Context
& 0xff800000) |
235 ((address
>> 9) & 0x007ffff0);
237 (env
->CP0_EntryHi
& 0xFF) | (address
& (TARGET_PAGE_MASK
<< 1));
238 env
->exception_index
= exception
;
239 env
->error_code
= error_code
;
246 void do_interrupt (CPUState
*env
)
248 target_ulong pc
, offset
;
251 if (logfile
&& env
->exception_index
!= EXCP_EXT_INTERRUPT
) {
252 fprintf(logfile
, "%s enter: PC %08x EPC %08x cause %d excp %d\n",
253 __func__
, env
->PC
, env
->CP0_EPC
, cause
, env
->exception_index
);
255 if (env
->exception_index
== EXCP_EXT_INTERRUPT
&&
256 (env
->hflags
& MIPS_HFLAG_DM
))
257 env
->exception_index
= EXCP_DINT
;
259 switch (env
->exception_index
) {
261 env
->CP0_Debug
|= 1 << CP0DB_DSS
;
262 /* Debug single step cannot be raised inside a delay slot and
263 * resume will always occur on the next instruction
264 * (but we assume the pc has always been updated during
267 env
->CP0_DEPC
= env
->PC
;
268 goto enter_debug_mode
;
270 env
->CP0_Debug
|= 1 << CP0DB_DINT
;
273 env
->CP0_Debug
|= 1 << CP0DB_DIB
;
276 env
->CP0_Debug
|= 1 << CP0DB_DBp
;
279 env
->CP0_Debug
|= 1 << CP0DB_DDBS
;
282 env
->CP0_Debug
|= 1 << CP0DB_DDBL
;
285 if (env
->hflags
& MIPS_HFLAG_BMASK
) {
286 /* If the exception was raised from a delay slot,
287 * come back to the jump
289 env
->CP0_DEPC
= env
->PC
- 4;
290 env
->hflags
&= ~MIPS_HFLAG_BMASK
;
292 env
->CP0_DEPC
= env
->PC
;
295 env
->hflags
|= MIPS_HFLAG_DM
;
296 /* EJTAG probe trap enable is not implemented... */
300 #ifdef MIPS_USES_R4K_TLB
301 env
->CP0_random
= MIPS_TLB_NB
- 1;
304 env
->CP0_Config0
= MIPS_CONFIG0
;
305 #if defined (MIPS_CONFIG1)
306 env
->CP0_Config1
= MIPS_CONFIG1
;
308 #if defined (MIPS_CONFIG2)
309 env
->CP0_Config2
= MIPS_CONFIG2
;
311 #if defined (MIPS_CONFIG3)
312 env
->CP0_Config3
= MIPS_CONFIG3
;
314 env
->CP0_WatchLo
= 0;
315 env
->CP0_Status
= (1 << CP0St_CU0
) | (1 << CP0St_BEV
);
318 env
->CP0_Status
= (1 << CP0St_CU0
) | (1 << CP0St_BEV
) |
320 env
->CP0_WatchLo
= 0;
323 env
->CP0_Status
= (1 << CP0St_CU0
) | (1 << CP0St_BEV
) |
326 if (env
->hflags
& MIPS_HFLAG_BMASK
) {
327 /* If the exception was raised from a delay slot,
328 * come back to the jump
330 env
->CP0_ErrorEPC
= env
->PC
- 4;
331 env
->hflags
&= ~MIPS_HFLAG_BMASK
;
333 env
->CP0_ErrorEPC
= env
->PC
;
335 env
->hflags
|= MIPS_HFLAG_ERL
;
336 env
->CP0_Status
|= (1 << CP0St_ERL
);
342 case EXCP_EXT_INTERRUPT
:
344 if (env
->CP0_Cause
& (1 << CP0Ca_IV
))
349 /* XXX: TODO: manage defered watch exceptions */
357 if (env
->error_code
== 1 && !(env
->hflags
& MIPS_HFLAG_EXL
))
377 env
->CP0_Cause
= (env
->CP0_Cause
& ~0x03000000) | (env
->error_code
<< 28);
390 if (env
->error_code
== 1 && !(env
->hflags
& MIPS_HFLAG_EXL
))
394 if (env
->CP0_Status
& (1 << CP0St_BEV
)) {
399 env
->hflags
|= MIPS_HFLAG_EXL
;
400 env
->CP0_Status
|= (1 << CP0St_EXL
);
402 env
->CP0_Cause
= (env
->CP0_Cause
& ~0x7C) | (cause
<< 2);
403 if (env
->hflags
& MIPS_HFLAG_BMASK
) {
404 /* If the exception was raised from a delay slot,
405 * come back to the jump
407 env
->CP0_EPC
= env
->PC
- 4;
408 env
->CP0_Cause
|= 0x80000000;
409 env
->hflags
&= ~MIPS_HFLAG_BMASK
;
411 env
->CP0_EPC
= env
->PC
;
412 env
->CP0_Cause
&= ~0x80000000;
417 fprintf(logfile
, "Invalid MIPS exception %d. Exiting\n",
418 env
->exception_index
);
420 printf("Invalid MIPS exception %d. Exiting\n", env
->exception_index
);
424 if (logfile
&& env
->exception_index
!= EXCP_EXT_INTERRUPT
) {
425 fprintf(logfile
, "%s: PC %08x EPC %08x cause %d excp %d\n"
426 " S %08x C %08x A %08x D %08x\n",
427 __func__
, env
->PC
, env
->CP0_EPC
, cause
, env
->exception_index
,
428 env
->CP0_Status
, env
->CP0_Cause
, env
->CP0_BadVAddr
,
431 env
->exception_index
= EXCP_NONE
;