]>
git.proxmox.com Git - mirror_qemu.git/blob - target-mips/helper.c
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
)
46 for (i
= 0; i
< env
->tlb_in_use
; i
++) {
47 tlb_t
*tlb
= &env
->tlb
[i
];
48 /* 1k pages are not supported. */
49 uint8_t ASID
= env
->CP0_EntryHi
& 0xFF;
50 target_ulong mask
= tlb
->PageMask
| 0x1FFF;
51 target_ulong tag
= address
& ~mask
;
54 /* Check ASID, virtual page number & size */
55 if ((tlb
->G
== 1 || tlb
->ASID
== ASID
) &&
58 n
= !!(address
& mask
& ~(mask
>> 1));
59 /* Check access rights */
60 if (!(n
? tlb
->V1
: tlb
->V0
))
61 return TLBRET_INVALID
;
62 if (rw
== 0 || (n
? tlb
->D1
: tlb
->D0
)) {
63 *physical
= tlb
->PFN
[n
] | (address
& (mask
>> 1));
65 if (n
? tlb
->D1
: tlb
->D0
)
72 return TLBRET_NOMATCH
;
76 static int get_physical_address (CPUState
*env
, target_ulong
*physical
,
77 int *prot
, target_ulong address
,
78 int rw
, int access_type
)
80 /* User mode can only access useg */
81 int user_mode
= (env
->hflags
& MIPS_HFLAG_MODE
) == MIPS_HFLAG_UM
;
82 int ret
= TLBRET_MATCH
;
86 fprintf(logfile
, "user mode %d h %08x\n",
87 user_mode
, env
->hflags
);
90 if (user_mode
&& address
> 0x7FFFFFFFUL
)
91 return TLBRET_BADADDR
;
92 if (address
< (int32_t)0x80000000UL
) {
93 if (!(env
->hflags
& MIPS_HFLAG_ERL
)) {
94 #ifdef MIPS_USES_R4K_TLB
95 ret
= map_address(env
, physical
, prot
, address
, rw
, access_type
);
97 *physical
= address
+ 0x40000000UL
;
98 *prot
= PAGE_READ
| PAGE_WRITE
;
102 *prot
= PAGE_READ
| PAGE_WRITE
;
104 } else if (address
< (int32_t)0xA0000000UL
) {
106 /* XXX: check supervisor mode */
107 *physical
= address
- (int32_t)0x80000000UL
;
108 *prot
= PAGE_READ
| PAGE_WRITE
;
109 } else if (address
< (int32_t)0xC0000000UL
) {
111 /* XXX: check supervisor mode */
112 *physical
= address
- (int32_t)0xA0000000UL
;
113 *prot
= PAGE_READ
| PAGE_WRITE
;
114 } else if (address
< (int32_t)0xE0000000UL
) {
116 #ifdef MIPS_USES_R4K_TLB
117 ret
= map_address(env
, physical
, prot
, address
, rw
, access_type
);
120 *prot
= PAGE_READ
| PAGE_WRITE
;
124 /* XXX: check supervisor mode */
125 /* XXX: debug segment is not emulated */
126 #ifdef MIPS_USES_R4K_TLB
127 ret
= map_address(env
, physical
, prot
, address
, rw
, access_type
);
130 *prot
= PAGE_READ
| PAGE_WRITE
;
135 fprintf(logfile
, TLSZ
" %d %d => " TLSZ
" %d (%d)\n",
136 address
, rw
, access_type
, *physical
, *prot
, ret
);
143 #if defined(CONFIG_USER_ONLY)
144 target_ulong
cpu_get_phys_page_debug(CPUState
*env
, target_ulong addr
)
149 target_ulong
cpu_get_phys_page_debug(CPUState
*env
, target_ulong addr
)
151 target_ulong phys_addr
;
154 if (get_physical_address(env
, &phys_addr
, &prot
, addr
, 0, ACCESS_INT
) != 0)
159 void cpu_mips_init_mmu (CPUState
*env
)
162 #endif /* !defined(CONFIG_USER_ONLY) */
164 int cpu_mips_handle_mmu_fault (CPUState
*env
, target_ulong address
, int rw
,
165 int is_user
, int is_softmmu
)
167 target_ulong physical
;
169 int exception
= 0, error_code
= 0;
175 cpu_dump_state(env
, logfile
, fprintf
, 0);
177 fprintf(logfile
, "%s pc " TLSZ
" ad " TLSZ
" rw %d is_user %d smmu %d\n",
178 __func__
, env
->PC
, address
, rw
, is_user
, is_softmmu
);
184 /* XXX: put correct access by using cpu_restore_state()
186 access_type
= ACCESS_INT
;
187 if (env
->user_mode_only
) {
188 /* user mode only emulation */
189 ret
= TLBRET_NOMATCH
;
192 ret
= get_physical_address(env
, &physical
, &prot
,
193 address
, rw
, access_type
);
195 fprintf(logfile
, "%s address=" TLSZ
" ret %d physical " TLSZ
" prot %d\n",
196 __func__
, address
, ret
, physical
, prot
);
198 if (ret
== TLBRET_MATCH
) {
199 ret
= tlb_set_page(env
, address
& TARGET_PAGE_MASK
,
200 physical
& TARGET_PAGE_MASK
, prot
,
201 is_user
, is_softmmu
);
202 } else if (ret
< 0) {
207 /* Reference to kernel address from user mode or supervisor mode */
208 /* Reference to supervisor address from user mode */
210 exception
= EXCP_AdES
;
212 exception
= EXCP_AdEL
;
215 /* No TLB match for a mapped address */
217 exception
= EXCP_TLBS
;
219 exception
= EXCP_TLBL
;
223 /* TLB match with no valid bit */
225 exception
= EXCP_TLBS
;
227 exception
= EXCP_TLBL
;
230 /* TLB match but 'D' bit is cleared */
231 exception
= EXCP_LTLBL
;
235 /* Raise exception */
236 env
->CP0_BadVAddr
= address
;
237 env
->CP0_Context
= (env
->CP0_Context
& 0xff800000) |
238 ((address
>> 9) & 0x007ffff0);
240 (env
->CP0_EntryHi
& 0xFF) | (address
& (TARGET_PAGE_MASK
<< 1));
241 env
->exception_index
= exception
;
242 env
->error_code
= error_code
;
249 #if defined(CONFIG_USER_ONLY)
250 void do_interrupt (CPUState
*env
)
252 env
->exception_index
= EXCP_NONE
;
255 void do_interrupt (CPUState
*env
)
260 if (logfile
&& env
->exception_index
!= EXCP_EXT_INTERRUPT
) {
261 fprintf(logfile
, "%s enter: PC " TLSZ
" EPC " TLSZ
" cause %d excp %d\n",
262 __func__
, env
->PC
, env
->CP0_EPC
, cause
, env
->exception_index
);
264 if (env
->exception_index
== EXCP_EXT_INTERRUPT
&&
265 (env
->hflags
& MIPS_HFLAG_DM
))
266 env
->exception_index
= EXCP_DINT
;
268 switch (env
->exception_index
) {
270 env
->CP0_Debug
|= 1 << CP0DB_DSS
;
271 /* Debug single step cannot be raised inside a delay slot and
272 * resume will always occur on the next instruction
273 * (but we assume the pc has always been updated during
276 env
->CP0_DEPC
= env
->PC
;
277 goto enter_debug_mode
;
279 env
->CP0_Debug
|= 1 << CP0DB_DINT
;
282 env
->CP0_Debug
|= 1 << CP0DB_DIB
;
285 env
->CP0_Debug
|= 1 << CP0DB_DBp
;
288 env
->CP0_Debug
|= 1 << CP0DB_DDBS
;
291 env
->CP0_Debug
|= 1 << CP0DB_DDBL
;
294 if (env
->hflags
& MIPS_HFLAG_BMASK
) {
295 /* If the exception was raised from a delay slot,
296 come back to the jump. */
297 env
->CP0_DEPC
= env
->PC
- 4;
298 env
->hflags
&= ~MIPS_HFLAG_BMASK
;
300 env
->CP0_DEPC
= env
->PC
;
303 env
->hflags
|= MIPS_HFLAG_DM
;
304 /* EJTAG probe trap enable is not implemented... */
305 env
->PC
= (int32_t)0xBFC00480;
311 env
->CP0_Status
= (1 << CP0St_SR
);
312 env
->CP0_WatchLo
= 0;
315 env
->CP0_Status
= (1 << CP0St_NMI
);
317 if (env
->hflags
& MIPS_HFLAG_BMASK
) {
318 /* If the exception was raised from a delay slot,
319 come back to the jump. */
320 env
->CP0_ErrorEPC
= env
->PC
- 4;
321 env
->hflags
&= ~MIPS_HFLAG_BMASK
;
323 env
->CP0_ErrorEPC
= env
->PC
;
325 env
->hflags
|= MIPS_HFLAG_ERL
;
326 env
->CP0_Status
|= (1 << CP0St_ERL
) | (1 << CP0St_BEV
);
327 env
->PC
= (int32_t)0xBFC00000;
332 case EXCP_EXT_INTERRUPT
:
334 if (env
->CP0_Cause
& (1 << CP0Ca_IV
))
339 /* XXX: TODO: manage defered watch exceptions */
347 if (env
->error_code
== 1 && !(env
->hflags
& MIPS_HFLAG_EXL
))
367 env
->CP0_Cause
= (env
->CP0_Cause
& ~0x03000000) | (env
->error_code
<< 28);
380 if (env
->error_code
== 1 && !(env
->hflags
& MIPS_HFLAG_EXL
))
384 if (env
->hflags
& MIPS_HFLAG_BMASK
) {
385 /* If the exception was raised from a delay slot,
386 come back to the jump. */
387 env
->CP0_EPC
= env
->PC
- 4;
388 env
->CP0_Cause
|= 0x80000000;
389 env
->hflags
&= ~MIPS_HFLAG_BMASK
;
391 env
->CP0_EPC
= env
->PC
;
392 env
->CP0_Cause
&= ~0x80000000;
394 if (env
->CP0_Status
& (1 << CP0St_BEV
)) {
395 env
->PC
= (int32_t)0xBFC00200;
397 env
->PC
= (int32_t)0x80000000;
399 env
->hflags
|= MIPS_HFLAG_EXL
;
400 env
->CP0_Status
|= (1 << CP0St_EXL
);
402 env
->CP0_Cause
= (env
->CP0_Cause
& ~0x7C) | (cause
<< 2);
406 fprintf(logfile
, "Invalid MIPS exception %d. Exiting\n",
407 env
->exception_index
);
409 printf("Invalid MIPS exception %d. Exiting\n", env
->exception_index
);
412 if (logfile
&& env
->exception_index
!= EXCP_EXT_INTERRUPT
) {
413 fprintf(logfile
, "%s: PC " TLSZ
" EPC " TLSZ
" cause %d excp %d\n"
414 " S %08x C %08x A " TLSZ
" D " TLSZ
"\n",
415 __func__
, env
->PC
, env
->CP0_EPC
, cause
, env
->exception_index
,
416 env
->CP0_Status
, env
->CP0_Cause
, env
->CP0_BadVAddr
,
419 env
->exception_index
= EXCP_NONE
;
421 #endif /* !defined(CONFIG_USER_ONLY) */
423 void invalidate_tlb (CPUState
*env
, int idx
, int use_extra
)
428 uint8_t ASID
= env
->CP0_EntryHi
& 0xFF;
431 tlb
= &env
->tlb
[idx
];
432 /* The qemu TLB is flushed then the ASID changes, so no need to
433 flush these entries again. */
434 if (tlb
->G
== 0 && tlb
->ASID
!= ASID
) {
438 if (use_extra
&& env
->tlb_in_use
< MIPS_TLB_MAX
) {
439 /* For tlbwr, we can shadow the discarded entry into
440 a new (fake) TLB entry, as long as the guest can not
441 tell that it's there. */
442 env
->tlb
[env
->tlb_in_use
] = *tlb
;
447 /* 1k pages are not supported. */
448 mask
= tlb
->PageMask
| 0x1FFF;
451 end
= addr
| (mask
>> 1);
453 tlb_flush_page (env
, addr
);
454 addr
+= TARGET_PAGE_SIZE
;
458 addr
= tlb
->VPN
| ((mask
>> 1) + 1);
459 addr
= tlb
->VPN
+ TARGET_PAGE_SIZE
;
462 tlb_flush_page (env
, addr
);
463 addr
+= TARGET_PAGE_SIZE
;