diff options
Diffstat (limited to 'target/s390x/mmu_helper.c')
| -rw-r--r-- | target/s390x/mmu_helper.c | 429 |
1 files changed, 200 insertions, 229 deletions
diff --git a/target/s390x/mmu_helper.c b/target/s390x/mmu_helper.c index 7e6b0d0508..90b81335f9 100644 --- a/target/s390x/mmu_helper.c +++ b/target/s390x/mmu_helper.c @@ -28,37 +28,12 @@ #include "hw/hw.h" #include "hw/s390x/storage-keys.h" -/* #define DEBUG_S390 */ -/* #define DEBUG_S390_PTE */ -/* #define DEBUG_S390_STDOUT */ - -#ifdef DEBUG_S390 -#ifdef DEBUG_S390_STDOUT -#define DPRINTF(fmt, ...) \ - do { fprintf(stderr, fmt, ## __VA_ARGS__); \ - if (qemu_log_separate()) qemu_log(fmt, ##__VA_ARGS__); } while (0) -#else -#define DPRINTF(fmt, ...) \ - do { qemu_log(fmt, ## __VA_ARGS__); } while (0) -#endif -#else -#define DPRINTF(fmt, ...) \ - do { } while (0) -#endif - -#ifdef DEBUG_S390_PTE -#define PTE_DPRINTF DPRINTF -#else -#define PTE_DPRINTF(fmt, ...) \ - do { } while (0) -#endif - /* Fetch/store bits in the translation exception code: */ #define FS_READ 0x800 #define FS_WRITE 0x400 static void trigger_access_exception(CPUS390XState *env, uint32_t type, - uint32_t ilen, uint64_t tec) + uint64_t tec) { S390CPU *cpu = env_archcpu(env); @@ -69,46 +44,8 @@ static void trigger_access_exception(CPUS390XState *env, uint32_t type, if (type != PGM_ADDRESSING) { stq_phys(cs->as, env->psa + offsetof(LowCore, trans_exc_code), tec); } - trigger_pgm_exception(env, type, ilen); - } -} - -static void trigger_prot_fault(CPUS390XState *env, target_ulong vaddr, - uint64_t asc, int rw, bool exc) -{ - uint64_t tec; - - tec = vaddr | (rw == MMU_DATA_STORE ? FS_WRITE : FS_READ) | 4 | asc >> 46; - - DPRINTF("%s: trans_exc_code=%016" PRIx64 "\n", __func__, tec); - - if (!exc) { - return; - } - - trigger_access_exception(env, PGM_PROTECTION, ILEN_AUTO, tec); -} - -static void trigger_page_fault(CPUS390XState *env, target_ulong vaddr, - uint32_t type, uint64_t asc, int rw, bool exc) -{ - int ilen = ILEN_AUTO; - uint64_t tec; - - tec = vaddr | (rw == MMU_DATA_STORE ? FS_WRITE : FS_READ) | asc >> 46; - - DPRINTF("%s: trans_exc_code=%016" PRIx64 "\n", __func__, tec); - - if (!exc) { - return; - } - - /* Code accesses have an undefined ilc. */ - if (rw == MMU_INST_FETCH) { - ilen = 2; + trigger_pgm_exception(env, type); } - - trigger_access_exception(env, type, ilen, tec); } /* check whether the address would be proteted by Low-Address Protection */ @@ -156,122 +93,40 @@ target_ulong mmu_real2abs(CPUS390XState *env, target_ulong raddr) return raddr; } -/* Decode page table entry (normal 4KB page) */ -static int mmu_translate_pte(CPUS390XState *env, target_ulong vaddr, - uint64_t asc, uint64_t pt_entry, - target_ulong *raddr, int *flags, int rw, bool exc) -{ - if (pt_entry & PAGE_INVALID) { - DPRINTF("%s: PTE=0x%" PRIx64 " invalid\n", __func__, pt_entry); - trigger_page_fault(env, vaddr, PGM_PAGE_TRANS, asc, rw, exc); - return -1; - } - if (pt_entry & PAGE_RES0) { - trigger_page_fault(env, vaddr, PGM_TRANS_SPEC, asc, rw, exc); - return -1; - } - if (pt_entry & PAGE_RO) { - *flags &= ~PAGE_WRITE; - } - - *raddr = pt_entry & ASCE_ORIGIN; - - PTE_DPRINTF("%s: PTE=0x%" PRIx64 "\n", __func__, pt_entry); - - return 0; -} - -/* Decode segment table entry */ -static int mmu_translate_segment(CPUS390XState *env, target_ulong vaddr, - uint64_t asc, uint64_t st_entry, - target_ulong *raddr, int *flags, int rw, - bool exc) +static inline bool read_table_entry(CPUS390XState *env, hwaddr gaddr, + uint64_t *entry) { CPUState *cs = env_cpu(env); - uint64_t origin, offs, pt_entry; - if (st_entry & SEGMENT_ENTRY_RO) { - *flags &= ~PAGE_WRITE; - } - - if ((st_entry & SEGMENT_ENTRY_FC) && (env->cregs[0] & CR0_EDAT)) { - /* Decode EDAT1 segment frame absolute address (1MB page) */ - *raddr = (st_entry & 0xfffffffffff00000ULL) | (vaddr & 0xfffff); - PTE_DPRINTF("%s: SEG=0x%" PRIx64 "\n", __func__, st_entry); - return 0; - } - - /* Look up 4KB page entry */ - origin = st_entry & SEGMENT_ENTRY_ORIGIN; - offs = (vaddr & VADDR_PX) >> 9; - pt_entry = ldq_phys(cs->as, origin + offs); - PTE_DPRINTF("%s: 0x%" PRIx64 " + 0x%" PRIx64 " => 0x%016" PRIx64 "\n", - __func__, origin, offs, pt_entry); - return mmu_translate_pte(env, vaddr, asc, pt_entry, raddr, flags, rw, exc); -} - -/* Decode region table entries */ -static int mmu_translate_region(CPUS390XState *env, target_ulong vaddr, - uint64_t asc, uint64_t entry, int level, - target_ulong *raddr, int *flags, int rw, - bool exc) -{ - CPUState *cs = env_cpu(env); - uint64_t origin, offs, new_entry; - const int pchks[4] = { - PGM_SEGMENT_TRANS, PGM_REG_THIRD_TRANS, - PGM_REG_SEC_TRANS, PGM_REG_FIRST_TRANS - }; - - PTE_DPRINTF("%s: 0x%" PRIx64 "\n", __func__, entry); - - origin = entry & REGION_ENTRY_ORIGIN; - offs = (vaddr >> (17 + 11 * level / 4)) & 0x3ff8; - - new_entry = ldq_phys(cs->as, origin + offs); - PTE_DPRINTF("%s: 0x%" PRIx64 " + 0x%" PRIx64 " => 0x%016" PRIx64 "\n", - __func__, origin, offs, new_entry); - - if ((new_entry & REGION_ENTRY_INV) != 0) { - DPRINTF("%s: invalid region\n", __func__); - trigger_page_fault(env, vaddr, pchks[level / 4], asc, rw, exc); - return -1; - } - - if ((new_entry & REGION_ENTRY_TYPE_MASK) != level) { - trigger_page_fault(env, vaddr, PGM_TRANS_SPEC, asc, rw, exc); - return -1; - } - - if (level == ASCE_TYPE_SEGMENT) { - return mmu_translate_segment(env, vaddr, asc, new_entry, raddr, flags, - rw, exc); - } - - /* Check region table offset and length */ - offs = (vaddr >> (28 + 11 * (level - 4) / 4)) & 3; - if (offs < ((new_entry & REGION_ENTRY_TF) >> 6) - || offs > (new_entry & REGION_ENTRY_LENGTH)) { - DPRINTF("%s: invalid offset or len (%lx)\n", __func__, new_entry); - trigger_page_fault(env, vaddr, pchks[level / 4 - 1], asc, rw, exc); - return -1; - } - - if ((env->cregs[0] & CR0_EDAT) && (new_entry & REGION_ENTRY_RO)) { - *flags &= ~PAGE_WRITE; + /* + * According to the PoP, these table addresses are "unpredictably real + * or absolute". Also, "it is unpredictable whether the address wraps + * or an addressing exception is recognized". + * + * We treat them as absolute addresses and don't wrap them. + */ + if (unlikely(address_space_read(cs->as, gaddr, MEMTXATTRS_UNSPECIFIED, + (uint8_t *)entry, sizeof(*entry)) != + MEMTX_OK)) { + return false; } - - /* yet another region */ - return mmu_translate_region(env, vaddr, asc, new_entry, level - 4, - raddr, flags, rw, exc); + *entry = be64_to_cpu(*entry); + return true; } static int mmu_translate_asce(CPUS390XState *env, target_ulong vaddr, uint64_t asc, uint64_t asce, target_ulong *raddr, - int *flags, int rw, bool exc) + int *flags, int rw) { - int level; - int r; + const bool edat1 = (env->cregs[0] & CR0_EDAT) && + s390_has_feat(S390_FEAT_EDAT); + const bool edat2 = edat1 && s390_has_feat(S390_FEAT_EDAT_2); + const bool iep = (env->cregs[0] & CR0_IEP) && + s390_has_feat(S390_FEAT_INSTRUCTION_EXEC_PROT); + const int asce_tl = asce & ASCE_TABLE_LENGTH; + const int asce_p = asce & ASCE_PRIVATE_SPACE; + hwaddr gaddr = asce & ASCE_ORIGIN; + uint64_t entry; if (asce & ASCE_REAL_SPACE) { /* direct mapping */ @@ -279,60 +134,158 @@ static int mmu_translate_asce(CPUS390XState *env, target_ulong vaddr, return 0; } - level = asce & ASCE_TYPE_MASK; - switch (level) { + switch (asce & ASCE_TYPE_MASK) { case ASCE_TYPE_REGION1: - if ((vaddr >> 62) > (asce & ASCE_TABLE_LENGTH)) { - trigger_page_fault(env, vaddr, PGM_REG_FIRST_TRANS, asc, rw, exc); - return -1; + if (VADDR_REGION1_TL(vaddr) > asce_tl) { + return PGM_REG_FIRST_TRANS; } + gaddr += VADDR_REGION1_TX(vaddr) * 8; break; case ASCE_TYPE_REGION2: - if (vaddr & 0xffe0000000000000ULL) { - DPRINTF("%s: vaddr doesn't fit 0x%16" PRIx64 - " 0xffe0000000000000ULL\n", __func__, vaddr); - trigger_page_fault(env, vaddr, PGM_ASCE_TYPE, asc, rw, exc); - return -1; + if (VADDR_REGION1_TX(vaddr)) { + return PGM_ASCE_TYPE; } - if ((vaddr >> 51 & 3) > (asce & ASCE_TABLE_LENGTH)) { - trigger_page_fault(env, vaddr, PGM_REG_SEC_TRANS, asc, rw, exc); - return -1; + if (VADDR_REGION2_TL(vaddr) > asce_tl) { + return PGM_REG_SEC_TRANS; } + gaddr += VADDR_REGION2_TX(vaddr) * 8; break; case ASCE_TYPE_REGION3: - if (vaddr & 0xfffffc0000000000ULL) { - DPRINTF("%s: vaddr doesn't fit 0x%16" PRIx64 - " 0xfffffc0000000000ULL\n", __func__, vaddr); - trigger_page_fault(env, vaddr, PGM_ASCE_TYPE, asc, rw, exc); - return -1; + if (VADDR_REGION1_TX(vaddr) || VADDR_REGION2_TX(vaddr)) { + return PGM_ASCE_TYPE; } - if ((vaddr >> 40 & 3) > (asce & ASCE_TABLE_LENGTH)) { - trigger_page_fault(env, vaddr, PGM_REG_THIRD_TRANS, asc, rw, exc); - return -1; + if (VADDR_REGION3_TL(vaddr) > asce_tl) { + return PGM_REG_THIRD_TRANS; } + gaddr += VADDR_REGION3_TX(vaddr) * 8; break; case ASCE_TYPE_SEGMENT: - if (vaddr & 0xffffffff80000000ULL) { - DPRINTF("%s: vaddr doesn't fit 0x%16" PRIx64 - " 0xffffffff80000000ULL\n", __func__, vaddr); - trigger_page_fault(env, vaddr, PGM_ASCE_TYPE, asc, rw, exc); - return -1; + if (VADDR_REGION1_TX(vaddr) || VADDR_REGION2_TX(vaddr) || + VADDR_REGION3_TX(vaddr)) { + return PGM_ASCE_TYPE; + } + if (VADDR_SEGMENT_TL(vaddr) > asce_tl) { + return PGM_SEGMENT_TRANS; + } + gaddr += VADDR_SEGMENT_TX(vaddr) * 8; + break; + } + + switch (asce & ASCE_TYPE_MASK) { + case ASCE_TYPE_REGION1: + if (!read_table_entry(env, gaddr, &entry)) { + return PGM_ADDRESSING; + } + if (entry & REGION_ENTRY_I) { + return PGM_REG_FIRST_TRANS; + } + if ((entry & REGION_ENTRY_TT) != REGION_ENTRY_TT_REGION1) { + return PGM_TRANS_SPEC; + } + if (VADDR_REGION2_TL(vaddr) < (entry & REGION_ENTRY_TF) >> 6 || + VADDR_REGION2_TL(vaddr) > (entry & REGION_ENTRY_TL)) { + return PGM_REG_SEC_TRANS; + } + if (edat1 && (entry & REGION_ENTRY_P)) { + *flags &= ~PAGE_WRITE; + } + gaddr = (entry & REGION_ENTRY_ORIGIN) + VADDR_REGION2_TX(vaddr) * 8; + /* fall through */ + case ASCE_TYPE_REGION2: + if (!read_table_entry(env, gaddr, &entry)) { + return PGM_ADDRESSING; + } + if (entry & REGION_ENTRY_I) { + return PGM_REG_SEC_TRANS; + } + if ((entry & REGION_ENTRY_TT) != REGION_ENTRY_TT_REGION2) { + return PGM_TRANS_SPEC; + } + if (VADDR_REGION3_TL(vaddr) < (entry & REGION_ENTRY_TF) >> 6 || + VADDR_REGION3_TL(vaddr) > (entry & REGION_ENTRY_TL)) { + return PGM_REG_THIRD_TRANS; + } + if (edat1 && (entry & REGION_ENTRY_P)) { + *flags &= ~PAGE_WRITE; + } + gaddr = (entry & REGION_ENTRY_ORIGIN) + VADDR_REGION3_TX(vaddr) * 8; + /* fall through */ + case ASCE_TYPE_REGION3: + if (!read_table_entry(env, gaddr, &entry)) { + return PGM_ADDRESSING; + } + if (entry & REGION_ENTRY_I) { + return PGM_REG_THIRD_TRANS; + } + if ((entry & REGION_ENTRY_TT) != REGION_ENTRY_TT_REGION3) { + return PGM_TRANS_SPEC; + } + if (edat2 && (entry & REGION3_ENTRY_CR) && asce_p) { + return PGM_TRANS_SPEC; + } + if (edat1 && (entry & REGION_ENTRY_P)) { + *flags &= ~PAGE_WRITE; + } + if (edat2 && (entry & REGION3_ENTRY_FC)) { + if (iep && (entry & REGION3_ENTRY_IEP)) { + *flags &= ~PAGE_EXEC; + } + *raddr = (entry & REGION3_ENTRY_RFAA) | + (vaddr & ~REGION3_ENTRY_RFAA); + return 0; + } + if (VADDR_SEGMENT_TL(vaddr) < (entry & REGION_ENTRY_TF) >> 6 || + VADDR_SEGMENT_TL(vaddr) > (entry & REGION_ENTRY_TL)) { + return PGM_SEGMENT_TRANS; + } + gaddr = (entry & REGION_ENTRY_ORIGIN) + VADDR_SEGMENT_TX(vaddr) * 8; + /* fall through */ + case ASCE_TYPE_SEGMENT: + if (!read_table_entry(env, gaddr, &entry)) { + return PGM_ADDRESSING; + } + if (entry & SEGMENT_ENTRY_I) { + return PGM_SEGMENT_TRANS; + } + if ((entry & SEGMENT_ENTRY_TT) != SEGMENT_ENTRY_TT_SEGMENT) { + return PGM_TRANS_SPEC; + } + if ((entry & SEGMENT_ENTRY_CS) && asce_p) { + return PGM_TRANS_SPEC; } - if ((vaddr >> 29 & 3) > (asce & ASCE_TABLE_LENGTH)) { - trigger_page_fault(env, vaddr, PGM_SEGMENT_TRANS, asc, rw, exc); - return -1; + if (entry & SEGMENT_ENTRY_P) { + *flags &= ~PAGE_WRITE; + } + if (edat1 && (entry & SEGMENT_ENTRY_FC)) { + if (iep && (entry & SEGMENT_ENTRY_IEP)) { + *flags &= ~PAGE_EXEC; + } + *raddr = (entry & SEGMENT_ENTRY_SFAA) | + (vaddr & ~SEGMENT_ENTRY_SFAA); + return 0; } + gaddr = (entry & SEGMENT_ENTRY_ORIGIN) + VADDR_PAGE_TX(vaddr) * 8; break; } - r = mmu_translate_region(env, vaddr, asc, asce, level, raddr, flags, rw, - exc); - if (!r && rw == MMU_DATA_STORE && !(*flags & PAGE_WRITE)) { - trigger_prot_fault(env, vaddr, asc, rw, exc); - return -1; + if (!read_table_entry(env, gaddr, &entry)) { + return PGM_ADDRESSING; + } + if (entry & PAGE_ENTRY_I) { + return PGM_PAGE_TRANS; + } + if (entry & PAGE_ENTRY_0) { + return PGM_TRANS_SPEC; + } + if (entry & PAGE_ENTRY_P) { + *flags &= ~PAGE_WRITE; + } + if (iep && (entry & PAGE_ENTRY_IEP)) { + *flags &= ~PAGE_EXEC; } - return r; + *raddr = entry & TARGET_PAGE_MASK; + return 0; } static void mmu_handle_skey(target_ulong addr, int rw, int *flags) @@ -412,16 +365,18 @@ static void mmu_handle_skey(target_ulong addr, int rw, int *flags) * @param raddr the translated address is stored to this pointer * @param flags the PAGE_READ/WRITE/EXEC flags are stored to this pointer * @param exc true = inject a program check if a fault occurred - * @return 0 if the translation was successful, -1 if a fault occurred + * @return 0 = success, != 0, the exception to raise */ int mmu_translate(CPUS390XState *env, target_ulong vaddr, int rw, uint64_t asc, - target_ulong *raddr, int *flags, bool exc) + target_ulong *raddr, int *flags, uint64_t *tec) { uint64_t asce; int r; - + *tec = (vaddr & TARGET_PAGE_MASK) | (asc >> 46) | + (rw == MMU_DATA_STORE ? FS_WRITE : FS_READ); *flags = PAGE_READ | PAGE_WRITE | PAGE_EXEC; + if (is_low_address(vaddr & TARGET_PAGE_MASK) && lowprot_enabled(env, asc)) { /* * If any part of this page is currently protected, make sure the @@ -433,10 +388,9 @@ int mmu_translate(CPUS390XState *env, target_ulong vaddr, int rw, uint64_t asc, */ *flags |= PAGE_WRITE_INV; if (is_low_address(vaddr) && rw == MMU_DATA_STORE) { - if (exc) { - trigger_access_exception(env, PGM_PROTECTION, ILEN_AUTO, 0); - } - return -EACCES; + /* LAP sets bit 56 */ + *tec |= 0x80; + return PGM_PROTECTION; } } @@ -449,15 +403,12 @@ int mmu_translate(CPUS390XState *env, target_ulong vaddr, int rw, uint64_t asc, switch (asc) { case PSW_ASC_PRIMARY: - PTE_DPRINTF("%s: asc=primary\n", __func__); asce = env->cregs[1]; break; case PSW_ASC_HOME: - PTE_DPRINTF("%s: asc=home\n", __func__); asce = env->cregs[13]; break; case PSW_ASC_SECONDARY: - PTE_DPRINTF("%s: asc=secondary\n", __func__); asce = env->cregs[7]; break; case PSW_ASC_ACCREG: @@ -467,11 +418,25 @@ int mmu_translate(CPUS390XState *env, target_ulong vaddr, int rw, uint64_t asc, } /* perform the DAT translation */ - r = mmu_translate_asce(env, vaddr, asc, asce, raddr, flags, rw, exc); - if (r) { + r = mmu_translate_asce(env, vaddr, asc, asce, raddr, flags, rw); + if (unlikely(r)) { return r; } + /* check for DAT protection */ + if (unlikely(rw == MMU_DATA_STORE && !(*flags & PAGE_WRITE))) { + /* DAT sets bit 61 only */ + *tec |= 0x4; + return PGM_PROTECTION; + } + + /* check for Instruction-Execution-Protection */ + if (unlikely(rw == MMU_INST_FETCH && !(*flags & PAGE_EXEC))) { + /* IEP sets bit 56 and 61 */ + *tec |= 0x84; + return PGM_PROTECTION; + } + nodat: /* Convert real address -> absolute address */ *raddr = mmu_real2abs(env, *raddr); @@ -486,22 +451,22 @@ nodat: * the MEMOP interface. */ static int translate_pages(S390CPU *cpu, vaddr addr, int nr_pages, - target_ulong *pages, bool is_write) + target_ulong *pages, bool is_write, uint64_t *tec) { uint64_t asc = cpu->env.psw.mask & PSW_MASK_ASC; CPUS390XState *env = &cpu->env; int ret, i, pflags; for (i = 0; i < nr_pages; i++) { - ret = mmu_translate(env, addr, is_write, asc, &pages[i], &pflags, true); + ret = mmu_translate(env, addr, is_write, asc, &pages[i], &pflags, tec); if (ret) { return ret; } if (!address_space_access_valid(&address_space_memory, pages[i], TARGET_PAGE_SIZE, is_write, MEMTXATTRS_UNSPECIFIED)) { - trigger_access_exception(env, PGM_ADDRESSING, ILEN_AUTO, 0); - return -EFAULT; + *tec = 0; /* unused */ + return PGM_ADDRESSING; } addr += TARGET_PAGE_SIZE; } @@ -529,6 +494,7 @@ int s390_cpu_virt_mem_rw(S390CPU *cpu, vaddr laddr, uint8_t ar, void *hostbuf, { int currlen, nr_pages, i; target_ulong *pages; + uint64_t tec; int ret; if (kvm_enabled()) { @@ -542,8 +508,10 @@ int s390_cpu_virt_mem_rw(S390CPU *cpu, vaddr laddr, uint8_t ar, void *hostbuf, + 1; pages = g_malloc(nr_pages * sizeof(*pages)); - ret = translate_pages(cpu, laddr, nr_pages, pages, is_write); - if (ret == 0 && hostbuf != NULL) { + ret = translate_pages(cpu, laddr, nr_pages, pages, is_write, &tec); + if (ret) { + trigger_access_exception(&cpu->env, ret, tec); + } else if (hostbuf != NULL) { /* Copy data by stepping through the area page by page */ for (i = 0; i < nr_pages; i++) { currlen = MIN(len, TARGET_PAGE_SIZE - (laddr % TARGET_PAGE_SIZE)); @@ -575,10 +543,10 @@ void s390_cpu_virt_mem_handle_exc(S390CPU *cpu, uintptr_t ra) * @param rw 0 = read, 1 = write, 2 = code fetch * @param addr the translated address is stored to this pointer * @param flags the PAGE_READ/WRITE/EXEC flags are stored to this pointer - * @return 0 if the translation was successful, < 0 if a fault occurred + * @return 0 = success, != 0, the exception to raise */ int mmu_translate_real(CPUS390XState *env, target_ulong raddr, int rw, - target_ulong *addr, int *flags) + target_ulong *addr, int *flags, uint64_t *tec) { const bool lowprot_enabled = env->cregs[0] & CR0_LOWPROT; @@ -587,8 +555,11 @@ int mmu_translate_real(CPUS390XState *env, target_ulong raddr, int rw, /* see comment in mmu_translate() how this works */ *flags |= PAGE_WRITE_INV; if (is_low_address(raddr) && rw == MMU_DATA_STORE) { - trigger_access_exception(env, PGM_PROTECTION, ILEN_AUTO, 0); - return -EACCES; + /* LAP sets bit 56 */ + *tec = (raddr & TARGET_PAGE_MASK) + | (rw == MMU_DATA_STORE ? FS_WRITE : FS_READ) + | 0x80; + return PGM_PROTECTION; } } |