| /* |
| * Common CPU TLB handling |
| * |
| * Copyright (c) 2003 Fabrice Bellard |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, see <http://www.gnu.org/licenses/>. |
| */ |
| |
| #include "config.h" |
| #include "cpu.h" |
| #include "exec/exec-all.h" |
| #include "exec/cputlb.h" |
| |
| #ifdef CONFIG_MEMCHECK |
| #include "memcheck/memcheck_api.h" |
| #endif |
| |
| /* statistics */ |
| int tlb_flush_count; |
| |
| static const CPUTLBEntry s_cputlb_empty_entry = { |
| .addr_read = -1, |
| .addr_write = -1, |
| .addr_code = -1, |
| .addend = -1, |
| }; |
| |
| /* NOTE: if flush_global is true, also flush global entries (not |
| implemented yet) */ |
| void tlb_flush(CPUArchState *env, int flush_global) |
| { |
| int i; |
| |
| #if defined(DEBUG_TLB) |
| printf("tlb_flush:\n"); |
| #endif |
| /* must reset current TB so that interrupts cannot modify the |
| links while we are modifying them */ |
| env->current_tb = NULL; |
| |
| for (i = 0; i < CPU_TLB_SIZE; i++) { |
| int mmu_idx; |
| |
| for (mmu_idx = 0; mmu_idx < NB_MMU_MODES; mmu_idx++) { |
| env->tlb_table[mmu_idx][i] = s_cputlb_empty_entry; |
| } |
| } |
| |
| memset(env->tb_jmp_cache, 0, TB_JMP_CACHE_SIZE * sizeof (void *)); |
| tlb_flush_count++; |
| } |
| |
| static inline void tlb_flush_entry(CPUTLBEntry *tlb_entry, target_ulong addr) |
| { |
| if (addr == (tlb_entry->addr_read & |
| (TARGET_PAGE_MASK | TLB_INVALID_MASK)) || |
| addr == (tlb_entry->addr_write & |
| (TARGET_PAGE_MASK | TLB_INVALID_MASK)) || |
| addr == (tlb_entry->addr_code & |
| (TARGET_PAGE_MASK | TLB_INVALID_MASK))) { |
| *tlb_entry = s_cputlb_empty_entry; |
| } |
| } |
| |
| void tlb_flush_page(CPUArchState *env, target_ulong addr) |
| { |
| int i; |
| int mmu_idx; |
| |
| #if defined(DEBUG_TLB) |
| printf("tlb_flush_page: " TARGET_FMT_lx "\n", addr); |
| #endif |
| /* must reset current TB so that interrupts cannot modify the |
| links while we are modifying them */ |
| env->current_tb = NULL; |
| |
| addr &= TARGET_PAGE_MASK; |
| i = (addr >> TARGET_PAGE_BITS) & (CPU_TLB_SIZE - 1); |
| for (mmu_idx = 0; mmu_idx < NB_MMU_MODES; mmu_idx++) { |
| tlb_flush_entry(&env->tlb_table[mmu_idx][i], addr); |
| } |
| |
| tb_flush_jmp_cache(env, addr); |
| } |
| |
| /* update the TLBs so that writes to code in the virtual page 'addr' |
| can be detected */ |
| void tlb_protect_code(ram_addr_t ram_addr) |
| { |
| cpu_physical_memory_reset_dirty(ram_addr, |
| ram_addr + TARGET_PAGE_SIZE, |
| CODE_DIRTY_FLAG); |
| } |
| |
| /* update the TLB so that writes in physical page 'phys_addr' are no longer |
| tested for self modifying code */ |
| void tlb_unprotect_code_phys(CPUArchState *env, ram_addr_t ram_addr, |
| target_ulong vaddr) |
| { |
| cpu_physical_memory_set_dirty_flags(ram_addr, CODE_DIRTY_FLAG); |
| } |
| |
| void tlb_reset_dirty_range(CPUTLBEntry *tlb_entry, |
| uintptr_t start, uintptr_t length) |
| { |
| uintptr_t addr; |
| if ((tlb_entry->addr_write & ~TARGET_PAGE_MASK) == IO_MEM_RAM) { |
| addr = (tlb_entry->addr_write & TARGET_PAGE_MASK) + tlb_entry->addend; |
| if ((addr - start) < length) { |
| tlb_entry->addr_write = (tlb_entry->addr_write & TARGET_PAGE_MASK) | TLB_NOTDIRTY; |
| } |
| } |
| } |
| |
| static inline void tlb_set_dirty1(CPUTLBEntry *tlb_entry, target_ulong vaddr) |
| { |
| if (tlb_entry->addr_write == (vaddr | TLB_NOTDIRTY)) |
| tlb_entry->addr_write = vaddr; |
| } |
| |
| /* update the TLB corresponding to virtual page vaddr |
| so that it is no longer dirty */ |
| void tlb_set_dirty(CPUArchState *env, target_ulong vaddr) |
| { |
| int i; |
| int mmu_idx; |
| |
| vaddr &= TARGET_PAGE_MASK; |
| i = (vaddr >> TARGET_PAGE_BITS) & (CPU_TLB_SIZE - 1); |
| for (mmu_idx = 0; mmu_idx < NB_MMU_MODES; mmu_idx++) |
| tlb_set_dirty1(&env->tlb_table[mmu_idx][i], vaddr); |
| } |
| |
| /* add a new TLB entry. At most one entry for a given virtual address |
| is permitted. Return 0 if OK or 2 if the page could not be mapped |
| (can only happen in non SOFTMMU mode for I/O pages or pages |
| conflicting with the host address space). */ |
| int tlb_set_page_exec(CPUArchState *env, target_ulong vaddr, |
| hwaddr paddr, int prot, |
| int mmu_idx, int is_softmmu) |
| { |
| PhysPageDesc *p; |
| unsigned long pd; |
| unsigned int index; |
| target_ulong address; |
| target_ulong code_address; |
| ptrdiff_t addend; |
| int ret; |
| CPUTLBEntry *te; |
| CPUWatchpoint *wp; |
| hwaddr iotlb; |
| |
| p = phys_page_find(paddr >> TARGET_PAGE_BITS); |
| if (!p) { |
| pd = IO_MEM_UNASSIGNED; |
| } else { |
| pd = p->phys_offset; |
| } |
| #if defined(DEBUG_TLB) |
| printf("tlb_set_page: vaddr=" TARGET_FMT_lx " paddr=0x%08x prot=%x idx=%d smmu=%d pd=0x%08lx\n", |
| vaddr, (int)paddr, prot, mmu_idx, is_softmmu, pd); |
| #endif |
| |
| ret = 0; |
| address = vaddr; |
| if ((pd & ~TARGET_PAGE_MASK) > IO_MEM_ROM && !(pd & IO_MEM_ROMD)) { |
| /* IO memory case (romd handled later) */ |
| address |= TLB_MMIO; |
| } |
| addend = (ptrdiff_t)qemu_get_ram_ptr(pd & TARGET_PAGE_MASK); |
| if ((pd & ~TARGET_PAGE_MASK) <= IO_MEM_ROM) { |
| /* Normal RAM. */ |
| iotlb = pd & TARGET_PAGE_MASK; |
| if ((pd & ~TARGET_PAGE_MASK) == IO_MEM_RAM) |
| iotlb |= IO_MEM_NOTDIRTY; |
| else |
| iotlb |= IO_MEM_ROM; |
| } else { |
| /* IO handlers are currently passed a physical address. |
| It would be nice to pass an offset from the base address |
| of that region. This would avoid having to special case RAM, |
| and avoid full address decoding in every device. |
| We can't use the high bits of pd for this because |
| IO_MEM_ROMD uses these as a ram address. */ |
| iotlb = (pd & ~TARGET_PAGE_MASK); |
| if (p) { |
| iotlb += p->region_offset; |
| } else { |
| iotlb += paddr; |
| } |
| } |
| |
| code_address = address; |
| /* Make accesses to pages with watchpoints go via the |
| watchpoint trap routines. */ |
| QTAILQ_FOREACH(wp, &env->watchpoints, entry) { |
| if (vaddr == (wp->vaddr & TARGET_PAGE_MASK)) { |
| iotlb = io_mem_watch + paddr; |
| /* TODO: The memory case can be optimized by not trapping |
| reads of pages with a write breakpoint. */ |
| address |= TLB_MMIO; |
| } |
| } |
| |
| index = (vaddr >> TARGET_PAGE_BITS) & (CPU_TLB_SIZE - 1); |
| env->iotlb[mmu_idx][index] = iotlb - vaddr; |
| te = &env->tlb_table[mmu_idx][index]; |
| te->addend = addend - vaddr; |
| if (prot & PAGE_READ) { |
| te->addr_read = address; |
| } else { |
| te->addr_read = -1; |
| } |
| |
| if (prot & PAGE_EXEC) { |
| te->addr_code = code_address; |
| } else { |
| te->addr_code = -1; |
| } |
| if (prot & PAGE_WRITE) { |
| if ((pd & ~TARGET_PAGE_MASK) == IO_MEM_ROM || |
| (pd & IO_MEM_ROMD)) { |
| /* Write access calls the I/O callback. */ |
| te->addr_write = address | TLB_MMIO; |
| } else if ((pd & ~TARGET_PAGE_MASK) == IO_MEM_RAM && |
| !cpu_physical_memory_is_dirty(pd)) { |
| te->addr_write = address | TLB_NOTDIRTY; |
| } else { |
| te->addr_write = address; |
| } |
| } else { |
| te->addr_write = -1; |
| } |
| |
| #ifdef CONFIG_MEMCHECK |
| /* |
| * If we have memchecker running, we need to make sure that page, cached |
| * into TLB as the result of this operation will comply with our requirement |
| * to cause __ld/__stx_mmu being called for memory access on the pages |
| * containing memory blocks that require access violation checks. |
| * |
| * We need to check with memory checker if we should invalidate this page |
| * iff: |
| * - Memchecking is enabled. |
| * - Page that's been cached belongs to the user space. |
| * - Request to cache this page didn't come from softmmu. We're covered |
| * there, because after page was cached here we will invalidate it in |
| * the __ld/__stx_mmu wrapper. |
| * - Cached page belongs to RAM, not I/O area. |
| * - Page is cached for read, or write access. |
| */ |
| if (memcheck_instrument_mmu && mmu_idx == 1 && !is_softmmu && |
| (pd & ~TARGET_PAGE_MASK) == IO_MEM_RAM && |
| (prot & (PAGE_READ | PAGE_WRITE)) && |
| memcheck_is_checked(vaddr & TARGET_PAGE_MASK, TARGET_PAGE_SIZE)) { |
| if (prot & PAGE_READ) { |
| te->addr_read ^= TARGET_PAGE_MASK; |
| } |
| if (prot & PAGE_WRITE) { |
| te->addr_write ^= TARGET_PAGE_MASK; |
| } |
| } |
| #endif // CONFIG_MEMCHECK |
| |
| return ret; |
| } |
| |
| int tlb_set_page(CPUArchState *env1, target_ulong vaddr, |
| hwaddr paddr, int prot, |
| int mmu_idx, int is_softmmu) |
| { |
| if (prot & PAGE_READ) |
| prot |= PAGE_EXEC; |
| return tlb_set_page_exec(env1, vaddr, paddr, prot, mmu_idx, is_softmmu); |
| } |
| |
| #define MMUSUFFIX _cmmu |
| #define GETPC() NULL |
| #define env cpu_single_env |
| #define SOFTMMU_CODE_ACCESS |
| |
| #define SHIFT 0 |
| #include "exec/softmmu_template.h" |
| |
| #define SHIFT 1 |
| #include "exec/softmmu_template.h" |
| |
| #define SHIFT 2 |
| #include "exec/softmmu_template.h" |
| |
| #define SHIFT 3 |
| #include "exec/softmmu_template.h" |
| |
| #undef env |