| /* |
| * S/390 memory access helper routines |
| * |
| * Copyright (c) 2009 Ulrich Hecht |
| * Copyright (c) 2009 Alexander Graf |
| * |
| * 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.1 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 "qemu/osdep.h" |
| #include "qemu/log.h" |
| #include "cpu.h" |
| #include "s390x-internal.h" |
| #include "tcg_s390x.h" |
| #include "exec/helper-proto.h" |
| #include "exec/exec-all.h" |
| #include "exec/page-protection.h" |
| #include "exec/cpu_ldst.h" |
| #include "hw/core/tcg-cpu-ops.h" |
| #include "qemu/int128.h" |
| #include "qemu/atomic128.h" |
| |
| #if !defined(CONFIG_USER_ONLY) |
| #include "hw/s390x/storage-keys.h" |
| #include "hw/boards.h" |
| #endif |
| |
| #ifdef CONFIG_USER_ONLY |
| # define user_or_likely(X) true |
| #else |
| # define user_or_likely(X) likely(X) |
| #endif |
| |
| /*****************************************************************************/ |
| /* Softmmu support */ |
| |
| /* #define DEBUG_HELPER */ |
| #ifdef DEBUG_HELPER |
| #define HELPER_LOG(x...) qemu_log(x) |
| #else |
| #define HELPER_LOG(x...) |
| #endif |
| |
| static inline bool psw_key_valid(CPUS390XState *env, uint8_t psw_key) |
| { |
| uint16_t pkm = env->cregs[3] >> 16; |
| |
| if (env->psw.mask & PSW_MASK_PSTATE) { |
| /* PSW key has range 0..15, it is valid if the bit is 1 in the PKM */ |
| return pkm & (0x8000 >> psw_key); |
| } |
| return true; |
| } |
| |
| static bool is_destructive_overlap(CPUS390XState *env, uint64_t dest, |
| uint64_t src, uint32_t len) |
| { |
| if (!len || src == dest) { |
| return false; |
| } |
| /* Take care of wrapping at the end of address space. */ |
| if (unlikely(wrap_address(env, src + len - 1) < src)) { |
| return dest > src || dest <= wrap_address(env, src + len - 1); |
| } |
| return dest > src && dest <= src + len - 1; |
| } |
| |
| /* Trigger a SPECIFICATION exception if an address or a length is not |
| naturally aligned. */ |
| static inline void check_alignment(CPUS390XState *env, uint64_t v, |
| int wordsize, uintptr_t ra) |
| { |
| if (v % wordsize) { |
| tcg_s390_program_interrupt(env, PGM_SPECIFICATION, ra); |
| } |
| } |
| |
| /* Load a value from memory according to its size. */ |
| static inline uint64_t cpu_ldusize_data_ra(CPUS390XState *env, uint64_t addr, |
| int wordsize, uintptr_t ra) |
| { |
| switch (wordsize) { |
| case 1: |
| return cpu_ldub_data_ra(env, addr, ra); |
| case 2: |
| return cpu_lduw_data_ra(env, addr, ra); |
| default: |
| abort(); |
| } |
| } |
| |
| /* Store a to memory according to its size. */ |
| static inline void cpu_stsize_data_ra(CPUS390XState *env, uint64_t addr, |
| uint64_t value, int wordsize, |
| uintptr_t ra) |
| { |
| switch (wordsize) { |
| case 1: |
| cpu_stb_data_ra(env, addr, value, ra); |
| break; |
| case 2: |
| cpu_stw_data_ra(env, addr, value, ra); |
| break; |
| default: |
| abort(); |
| } |
| } |
| |
| /* An access covers at most 4096 bytes and therefore at most two pages. */ |
| typedef struct S390Access { |
| target_ulong vaddr1; |
| target_ulong vaddr2; |
| void *haddr1; |
| void *haddr2; |
| uint16_t size1; |
| uint16_t size2; |
| /* |
| * If we can't access the host page directly, we'll have to do I/O access |
| * via ld/st helpers. These are internal details, so we store the |
| * mmu idx to do the access here instead of passing it around in the |
| * helpers. |
| */ |
| int mmu_idx; |
| } S390Access; |
| |
| /* |
| * With nonfault=1, return the PGM_ exception that would have been injected |
| * into the guest; return 0 if no exception was detected. |
| * |
| * For !CONFIG_USER_ONLY, the TEC is stored stored to env->tlb_fill_tec. |
| * For CONFIG_USER_ONLY, the faulting address is stored to env->__excp_addr. |
| */ |
| static inline int s390_probe_access(CPUArchState *env, target_ulong addr, |
| int size, MMUAccessType access_type, |
| int mmu_idx, bool nonfault, |
| void **phost, uintptr_t ra) |
| { |
| int flags = probe_access_flags(env, addr, 0, access_type, mmu_idx, |
| nonfault, phost, ra); |
| |
| if (unlikely(flags & TLB_INVALID_MASK)) { |
| #ifdef CONFIG_USER_ONLY |
| /* Address is in TEC in system mode; see s390_cpu_record_sigsegv. */ |
| env->__excp_addr = addr & TARGET_PAGE_MASK; |
| return (page_get_flags(addr) & PAGE_VALID |
| ? PGM_PROTECTION : PGM_ADDRESSING); |
| #else |
| return env->tlb_fill_exc; |
| #endif |
| } |
| |
| #ifndef CONFIG_USER_ONLY |
| if (unlikely(flags & TLB_WATCHPOINT)) { |
| /* S390 does not presently use transaction attributes. */ |
| cpu_check_watchpoint(env_cpu(env), addr, size, |
| MEMTXATTRS_UNSPECIFIED, |
| (access_type == MMU_DATA_STORE |
| ? BP_MEM_WRITE : BP_MEM_READ), ra); |
| } |
| #endif |
| |
| return 0; |
| } |
| |
| static int access_prepare_nf(S390Access *access, CPUS390XState *env, |
| bool nonfault, vaddr vaddr1, int size, |
| MMUAccessType access_type, |
| int mmu_idx, uintptr_t ra) |
| { |
| int size1, size2, exc; |
| |
| assert(size > 0 && size <= 4096); |
| |
| size1 = MIN(size, -(vaddr1 | TARGET_PAGE_MASK)), |
| size2 = size - size1; |
| |
| memset(access, 0, sizeof(*access)); |
| access->vaddr1 = vaddr1; |
| access->size1 = size1; |
| access->size2 = size2; |
| access->mmu_idx = mmu_idx; |
| |
| exc = s390_probe_access(env, vaddr1, size1, access_type, mmu_idx, nonfault, |
| &access->haddr1, ra); |
| if (unlikely(exc)) { |
| return exc; |
| } |
| if (unlikely(size2)) { |
| /* The access crosses page boundaries. */ |
| vaddr vaddr2 = wrap_address(env, vaddr1 + size1); |
| |
| access->vaddr2 = vaddr2; |
| exc = s390_probe_access(env, vaddr2, size2, access_type, mmu_idx, |
| nonfault, &access->haddr2, ra); |
| if (unlikely(exc)) { |
| return exc; |
| } |
| } |
| return 0; |
| } |
| |
| static inline void access_prepare(S390Access *ret, CPUS390XState *env, |
| vaddr vaddr, int size, |
| MMUAccessType access_type, int mmu_idx, |
| uintptr_t ra) |
| { |
| int exc = access_prepare_nf(ret, env, false, vaddr, size, |
| access_type, mmu_idx, ra); |
| assert(!exc); |
| } |
| |
| /* Helper to handle memset on a single page. */ |
| static void do_access_memset(CPUS390XState *env, vaddr vaddr, char *haddr, |
| uint8_t byte, uint16_t size, int mmu_idx, |
| uintptr_t ra) |
| { |
| #ifdef CONFIG_USER_ONLY |
| memset(haddr, byte, size); |
| #else |
| if (likely(haddr)) { |
| memset(haddr, byte, size); |
| } else { |
| MemOpIdx oi = make_memop_idx(MO_UB, mmu_idx); |
| for (int i = 0; i < size; i++) { |
| cpu_stb_mmu(env, vaddr + i, byte, oi, ra); |
| } |
| } |
| #endif |
| } |
| |
| static void access_memset(CPUS390XState *env, S390Access *desta, |
| uint8_t byte, uintptr_t ra) |
| { |
| |
| do_access_memset(env, desta->vaddr1, desta->haddr1, byte, desta->size1, |
| desta->mmu_idx, ra); |
| if (likely(!desta->size2)) { |
| return; |
| } |
| do_access_memset(env, desta->vaddr2, desta->haddr2, byte, desta->size2, |
| desta->mmu_idx, ra); |
| } |
| |
| static uint8_t access_get_byte(CPUS390XState *env, S390Access *access, |
| int offset, uintptr_t ra) |
| { |
| target_ulong vaddr = access->vaddr1; |
| void *haddr = access->haddr1; |
| |
| if (unlikely(offset >= access->size1)) { |
| offset -= access->size1; |
| vaddr = access->vaddr2; |
| haddr = access->haddr2; |
| } |
| |
| if (user_or_likely(haddr)) { |
| return ldub_p(haddr + offset); |
| } else { |
| MemOpIdx oi = make_memop_idx(MO_UB, access->mmu_idx); |
| return cpu_ldb_mmu(env, vaddr + offset, oi, ra); |
| } |
| } |
| |
| static void access_set_byte(CPUS390XState *env, S390Access *access, |
| int offset, uint8_t byte, uintptr_t ra) |
| { |
| target_ulong vaddr = access->vaddr1; |
| void *haddr = access->haddr1; |
| |
| if (unlikely(offset >= access->size1)) { |
| offset -= access->size1; |
| vaddr = access->vaddr2; |
| haddr = access->haddr2; |
| } |
| |
| if (user_or_likely(haddr)) { |
| stb_p(haddr + offset, byte); |
| } else { |
| MemOpIdx oi = make_memop_idx(MO_UB, access->mmu_idx); |
| cpu_stb_mmu(env, vaddr + offset, byte, oi, ra); |
| } |
| } |
| |
| /* |
| * Move data with the same semantics as memmove() in case ranges don't overlap |
| * or src > dest. Undefined behavior on destructive overlaps. |
| */ |
| static void access_memmove(CPUS390XState *env, S390Access *desta, |
| S390Access *srca, uintptr_t ra) |
| { |
| int len = desta->size1 + desta->size2; |
| int diff; |
| |
| assert(len == srca->size1 + srca->size2); |
| |
| /* Fallback to slow access in case we don't have access to all host pages */ |
| if (unlikely(!desta->haddr1 || (desta->size2 && !desta->haddr2) || |
| !srca->haddr1 || (srca->size2 && !srca->haddr2))) { |
| int i; |
| |
| for (i = 0; i < len; i++) { |
| uint8_t byte = access_get_byte(env, srca, i, ra); |
| |
| access_set_byte(env, desta, i, byte, ra); |
| } |
| return; |
| } |
| |
| diff = desta->size1 - srca->size1; |
| if (likely(diff == 0)) { |
| memmove(desta->haddr1, srca->haddr1, srca->size1); |
| if (unlikely(srca->size2)) { |
| memmove(desta->haddr2, srca->haddr2, srca->size2); |
| } |
| } else if (diff > 0) { |
| memmove(desta->haddr1, srca->haddr1, srca->size1); |
| memmove(desta->haddr1 + srca->size1, srca->haddr2, diff); |
| if (likely(desta->size2)) { |
| memmove(desta->haddr2, srca->haddr2 + diff, desta->size2); |
| } |
| } else { |
| diff = -diff; |
| memmove(desta->haddr1, srca->haddr1, desta->size1); |
| memmove(desta->haddr2, srca->haddr1 + desta->size1, diff); |
| if (likely(srca->size2)) { |
| memmove(desta->haddr2 + diff, srca->haddr2, srca->size2); |
| } |
| } |
| } |
| |
| static int mmu_idx_from_as(uint8_t as) |
| { |
| switch (as) { |
| case AS_PRIMARY: |
| return MMU_PRIMARY_IDX; |
| case AS_SECONDARY: |
| return MMU_SECONDARY_IDX; |
| case AS_HOME: |
| return MMU_HOME_IDX; |
| default: |
| /* FIXME AS_ACCREG */ |
| g_assert_not_reached(); |
| } |
| } |
| |
| /* and on array */ |
| static uint32_t do_helper_nc(CPUS390XState *env, uint32_t l, uint64_t dest, |
| uint64_t src, uintptr_t ra) |
| { |
| const int mmu_idx = s390x_env_mmu_index(env, false); |
| S390Access srca1, srca2, desta; |
| uint32_t i; |
| uint8_t c = 0; |
| |
| HELPER_LOG("%s l %d dest %" PRIx64 " src %" PRIx64 "\n", |
| __func__, l, dest, src); |
| |
| /* NC always processes one more byte than specified - maximum is 256 */ |
| l++; |
| |
| access_prepare(&srca1, env, src, l, MMU_DATA_LOAD, mmu_idx, ra); |
| access_prepare(&srca2, env, dest, l, MMU_DATA_LOAD, mmu_idx, ra); |
| access_prepare(&desta, env, dest, l, MMU_DATA_STORE, mmu_idx, ra); |
| for (i = 0; i < l; i++) { |
| const uint8_t x = access_get_byte(env, &srca1, i, ra) & |
| access_get_byte(env, &srca2, i, ra); |
| |
| c |= x; |
| access_set_byte(env, &desta, i, x, ra); |
| } |
| return c != 0; |
| } |
| |
| uint32_t HELPER(nc)(CPUS390XState *env, uint32_t l, uint64_t dest, |
| uint64_t src) |
| { |
| return do_helper_nc(env, l, dest, src, GETPC()); |
| } |
| |
| /* xor on array */ |
| static uint32_t do_helper_xc(CPUS390XState *env, uint32_t l, uint64_t dest, |
| uint64_t src, uintptr_t ra) |
| { |
| const int mmu_idx = s390x_env_mmu_index(env, false); |
| S390Access srca1, srca2, desta; |
| uint32_t i; |
| uint8_t c = 0; |
| |
| HELPER_LOG("%s l %d dest %" PRIx64 " src %" PRIx64 "\n", |
| __func__, l, dest, src); |
| |
| /* XC always processes one more byte than specified - maximum is 256 */ |
| l++; |
| |
| access_prepare(&srca1, env, src, l, MMU_DATA_LOAD, mmu_idx, ra); |
| access_prepare(&srca2, env, dest, l, MMU_DATA_LOAD, mmu_idx, ra); |
| access_prepare(&desta, env, dest, l, MMU_DATA_STORE, mmu_idx, ra); |
| |
| /* xor with itself is the same as memset(0) */ |
| if (src == dest) { |
| access_memset(env, &desta, 0, ra); |
| return 0; |
| } |
| |
| for (i = 0; i < l; i++) { |
| const uint8_t x = access_get_byte(env, &srca1, i, ra) ^ |
| access_get_byte(env, &srca2, i, ra); |
| |
| c |= x; |
| access_set_byte(env, &desta, i, x, ra); |
| } |
| return c != 0; |
| } |
| |
| uint32_t HELPER(xc)(CPUS390XState *env, uint32_t l, uint64_t dest, |
| uint64_t src) |
| { |
| return do_helper_xc(env, l, dest, src, GETPC()); |
| } |
| |
| /* or on array */ |
| static uint32_t do_helper_oc(CPUS390XState *env, uint32_t l, uint64_t dest, |
| uint64_t src, uintptr_t ra) |
| { |
| const int mmu_idx = s390x_env_mmu_index(env, false); |
| S390Access srca1, srca2, desta; |
| uint32_t i; |
| uint8_t c = 0; |
| |
| HELPER_LOG("%s l %d dest %" PRIx64 " src %" PRIx64 "\n", |
| __func__, l, dest, src); |
| |
| /* OC always processes one more byte than specified - maximum is 256 */ |
| l++; |
| |
| access_prepare(&srca1, env, src, l, MMU_DATA_LOAD, mmu_idx, ra); |
| access_prepare(&srca2, env, dest, l, MMU_DATA_LOAD, mmu_idx, ra); |
| access_prepare(&desta, env, dest, l, MMU_DATA_STORE, mmu_idx, ra); |
| for (i = 0; i < l; i++) { |
| const uint8_t x = access_get_byte(env, &srca1, i, ra) | |
| access_get_byte(env, &srca2, i, ra); |
| |
| c |= x; |
| access_set_byte(env, &desta, i, x, ra); |
| } |
| return c != 0; |
| } |
| |
| uint32_t HELPER(oc)(CPUS390XState *env, uint32_t l, uint64_t dest, |
| uint64_t src) |
| { |
| return do_helper_oc(env, l, dest, src, GETPC()); |
| } |
| |
| /* memmove */ |
| static uint32_t do_helper_mvc(CPUS390XState *env, uint32_t l, uint64_t dest, |
| uint64_t src, uintptr_t ra) |
| { |
| const int mmu_idx = s390x_env_mmu_index(env, false); |
| S390Access srca, desta; |
| uint32_t i; |
| |
| HELPER_LOG("%s l %d dest %" PRIx64 " src %" PRIx64 "\n", |
| __func__, l, dest, src); |
| |
| /* MVC always copies one more byte than specified - maximum is 256 */ |
| l++; |
| |
| access_prepare(&srca, env, src, l, MMU_DATA_LOAD, mmu_idx, ra); |
| access_prepare(&desta, env, dest, l, MMU_DATA_STORE, mmu_idx, ra); |
| |
| /* |
| * "When the operands overlap, the result is obtained as if the operands |
| * were processed one byte at a time". Only non-destructive overlaps |
| * behave like memmove(). |
| */ |
| if (dest == src + 1) { |
| access_memset(env, &desta, access_get_byte(env, &srca, 0, ra), ra); |
| } else if (!is_destructive_overlap(env, dest, src, l)) { |
| access_memmove(env, &desta, &srca, ra); |
| } else { |
| for (i = 0; i < l; i++) { |
| uint8_t byte = access_get_byte(env, &srca, i, ra); |
| |
| access_set_byte(env, &desta, i, byte, ra); |
| } |
| } |
| |
| return env->cc_op; |
| } |
| |
| void HELPER(mvc)(CPUS390XState *env, uint32_t l, uint64_t dest, uint64_t src) |
| { |
| do_helper_mvc(env, l, dest, src, GETPC()); |
| } |
| |
| /* move right to left */ |
| void HELPER(mvcrl)(CPUS390XState *env, uint64_t l, uint64_t dest, uint64_t src) |
| { |
| const int mmu_idx = s390x_env_mmu_index(env, false); |
| const uint64_t ra = GETPC(); |
| S390Access srca, desta; |
| int32_t i; |
| |
| /* MVCRL always copies one more byte than specified - maximum is 256 */ |
| l &= 0xff; |
| l++; |
| |
| access_prepare(&srca, env, src, l, MMU_DATA_LOAD, mmu_idx, ra); |
| access_prepare(&desta, env, dest, l, MMU_DATA_STORE, mmu_idx, ra); |
| |
| for (i = l - 1; i >= 0; i--) { |
| uint8_t byte = access_get_byte(env, &srca, i, ra); |
| access_set_byte(env, &desta, i, byte, ra); |
| } |
| } |
| |
| /* move inverse */ |
| void HELPER(mvcin)(CPUS390XState *env, uint32_t l, uint64_t dest, uint64_t src) |
| { |
| const int mmu_idx = s390x_env_mmu_index(env, false); |
| S390Access srca, desta; |
| uintptr_t ra = GETPC(); |
| int i; |
| |
| /* MVCIN always copies one more byte than specified - maximum is 256 */ |
| l++; |
| |
| src = wrap_address(env, src - l + 1); |
| access_prepare(&srca, env, src, l, MMU_DATA_LOAD, mmu_idx, ra); |
| access_prepare(&desta, env, dest, l, MMU_DATA_STORE, mmu_idx, ra); |
| for (i = 0; i < l; i++) { |
| const uint8_t x = access_get_byte(env, &srca, l - i - 1, ra); |
| |
| access_set_byte(env, &desta, i, x, ra); |
| } |
| } |
| |
| /* move numerics */ |
| void HELPER(mvn)(CPUS390XState *env, uint32_t l, uint64_t dest, uint64_t src) |
| { |
| const int mmu_idx = s390x_env_mmu_index(env, false); |
| S390Access srca1, srca2, desta; |
| uintptr_t ra = GETPC(); |
| int i; |
| |
| /* MVN always copies one more byte than specified - maximum is 256 */ |
| l++; |
| |
| access_prepare(&srca1, env, src, l, MMU_DATA_LOAD, mmu_idx, ra); |
| access_prepare(&srca2, env, dest, l, MMU_DATA_LOAD, mmu_idx, ra); |
| access_prepare(&desta, env, dest, l, MMU_DATA_STORE, mmu_idx, ra); |
| for (i = 0; i < l; i++) { |
| const uint8_t x = (access_get_byte(env, &srca1, i, ra) & 0x0f) | |
| (access_get_byte(env, &srca2, i, ra) & 0xf0); |
| |
| access_set_byte(env, &desta, i, x, ra); |
| } |
| } |
| |
| /* move with offset */ |
| void HELPER(mvo)(CPUS390XState *env, uint32_t l, uint64_t dest, uint64_t src) |
| { |
| const int mmu_idx = s390x_env_mmu_index(env, false); |
| /* MVO always processes one more byte than specified - maximum is 16 */ |
| const int len_dest = (l >> 4) + 1; |
| const int len_src = (l & 0xf) + 1; |
| uintptr_t ra = GETPC(); |
| uint8_t byte_dest, byte_src; |
| S390Access srca, desta; |
| int i, j; |
| |
| access_prepare(&srca, env, src, len_src, MMU_DATA_LOAD, mmu_idx, ra); |
| access_prepare(&desta, env, dest, len_dest, MMU_DATA_STORE, mmu_idx, ra); |
| |
| /* Handle rightmost byte */ |
| byte_dest = cpu_ldub_data_ra(env, dest + len_dest - 1, ra); |
| byte_src = access_get_byte(env, &srca, len_src - 1, ra); |
| byte_dest = (byte_dest & 0x0f) | (byte_src << 4); |
| access_set_byte(env, &desta, len_dest - 1, byte_dest, ra); |
| |
| /* Process remaining bytes from right to left */ |
| for (i = len_dest - 2, j = len_src - 2; i >= 0; i--, j--) { |
| byte_dest = byte_src >> 4; |
| if (j >= 0) { |
| byte_src = access_get_byte(env, &srca, j, ra); |
| } else { |
| byte_src = 0; |
| } |
| byte_dest |= byte_src << 4; |
| access_set_byte(env, &desta, i, byte_dest, ra); |
| } |
| } |
| |
| /* move zones */ |
| void HELPER(mvz)(CPUS390XState *env, uint32_t l, uint64_t dest, uint64_t src) |
| { |
| const int mmu_idx = s390x_env_mmu_index(env, false); |
| S390Access srca1, srca2, desta; |
| uintptr_t ra = GETPC(); |
| int i; |
| |
| /* MVZ always copies one more byte than specified - maximum is 256 */ |
| l++; |
| |
| access_prepare(&srca1, env, src, l, MMU_DATA_LOAD, mmu_idx, ra); |
| access_prepare(&srca2, env, dest, l, MMU_DATA_LOAD, mmu_idx, ra); |
| access_prepare(&desta, env, dest, l, MMU_DATA_STORE, mmu_idx, ra); |
| for (i = 0; i < l; i++) { |
| const uint8_t x = (access_get_byte(env, &srca1, i, ra) & 0xf0) | |
| (access_get_byte(env, &srca2, i, ra) & 0x0f); |
| |
| access_set_byte(env, &desta, i, x, ra); |
| } |
| } |
| |
| /* compare unsigned byte arrays */ |
| static uint32_t do_helper_clc(CPUS390XState *env, uint32_t l, uint64_t s1, |
| uint64_t s2, uintptr_t ra) |
| { |
| uint32_t i; |
| uint32_t cc = 0; |
| |
| HELPER_LOG("%s l %d s1 %" PRIx64 " s2 %" PRIx64 "\n", |
| __func__, l, s1, s2); |
| |
| for (i = 0; i <= l; i++) { |
| uint8_t x = cpu_ldub_data_ra(env, s1 + i, ra); |
| uint8_t y = cpu_ldub_data_ra(env, s2 + i, ra); |
| HELPER_LOG("%02x (%c)/%02x (%c) ", x, x, y, y); |
| if (x < y) { |
| cc = 1; |
| break; |
| } else if (x > y) { |
| cc = 2; |
| break; |
| } |
| } |
| |
| HELPER_LOG("\n"); |
| return cc; |
| } |
| |
| uint32_t HELPER(clc)(CPUS390XState *env, uint32_t l, uint64_t s1, uint64_t s2) |
| { |
| return do_helper_clc(env, l, s1, s2, GETPC()); |
| } |
| |
| /* compare logical under mask */ |
| uint32_t HELPER(clm)(CPUS390XState *env, uint32_t r1, uint32_t mask, |
| uint64_t addr) |
| { |
| uintptr_t ra = GETPC(); |
| uint32_t cc = 0; |
| |
| HELPER_LOG("%s: r1 0x%x mask 0x%x addr 0x%" PRIx64 "\n", __func__, r1, |
| mask, addr); |
| |
| if (!mask) { |
| /* Recognize access exceptions for the first byte */ |
| probe_read(env, addr, 1, s390x_env_mmu_index(env, false), ra); |
| } |
| |
| while (mask) { |
| if (mask & 8) { |
| uint8_t d = cpu_ldub_data_ra(env, addr, ra); |
| uint8_t r = extract32(r1, 24, 8); |
| HELPER_LOG("mask 0x%x %02x/%02x (0x%" PRIx64 ") ", mask, r, d, |
| addr); |
| if (r < d) { |
| cc = 1; |
| break; |
| } else if (r > d) { |
| cc = 2; |
| break; |
| } |
| addr++; |
| } |
| mask = (mask << 1) & 0xf; |
| r1 <<= 8; |
| } |
| |
| HELPER_LOG("\n"); |
| return cc; |
| } |
| |
| static inline uint64_t get_address(CPUS390XState *env, int reg) |
| { |
| return wrap_address(env, env->regs[reg]); |
| } |
| |
| /* |
| * Store the address to the given register, zeroing out unused leftmost |
| * bits in bit positions 32-63 (24-bit and 31-bit mode only). |
| */ |
| static inline void set_address_zero(CPUS390XState *env, int reg, |
| uint64_t address) |
| { |
| if (env->psw.mask & PSW_MASK_64) { |
| env->regs[reg] = address; |
| } else { |
| if (!(env->psw.mask & PSW_MASK_32)) { |
| address &= 0x00ffffff; |
| } else { |
| address &= 0x7fffffff; |
| } |
| env->regs[reg] = deposit64(env->regs[reg], 0, 32, address); |
| } |
| } |
| |
| static inline void set_address(CPUS390XState *env, int reg, uint64_t address) |
| { |
| if (env->psw.mask & PSW_MASK_64) { |
| /* 64-Bit mode */ |
| env->regs[reg] = address; |
| } else { |
| if (!(env->psw.mask & PSW_MASK_32)) { |
| /* 24-Bit mode. According to the PoO it is implementation |
| dependent if bits 32-39 remain unchanged or are set to |
| zeros. Choose the former so that the function can also be |
| used for TRT. */ |
| env->regs[reg] = deposit64(env->regs[reg], 0, 24, address); |
| } else { |
| /* 31-Bit mode. According to the PoO it is implementation |
| dependent if bit 32 remains unchanged or is set to zero. |
| Choose the latter so that the function can also be used for |
| TRT. */ |
| address &= 0x7fffffff; |
| env->regs[reg] = deposit64(env->regs[reg], 0, 32, address); |
| } |
| } |
| } |
| |
| static inline uint64_t wrap_length32(CPUS390XState *env, uint64_t length) |
| { |
| if (!(env->psw.mask & PSW_MASK_64)) { |
| return (uint32_t)length; |
| } |
| return length; |
| } |
| |
| static inline uint64_t wrap_length31(CPUS390XState *env, uint64_t length) |
| { |
| if (!(env->psw.mask & PSW_MASK_64)) { |
| /* 24-Bit and 31-Bit mode */ |
| length &= 0x7fffffff; |
| } |
| return length; |
| } |
| |
| static inline uint64_t get_length(CPUS390XState *env, int reg) |
| { |
| return wrap_length31(env, env->regs[reg]); |
| } |
| |
| static inline void set_length(CPUS390XState *env, int reg, uint64_t length) |
| { |
| if (env->psw.mask & PSW_MASK_64) { |
| /* 64-Bit mode */ |
| env->regs[reg] = length; |
| } else { |
| /* 24-Bit and 31-Bit mode */ |
| env->regs[reg] = deposit64(env->regs[reg], 0, 32, length); |
| } |
| } |
| |
| /* search string (c is byte to search, r2 is string, r1 end of string) */ |
| void HELPER(srst)(CPUS390XState *env, uint32_t r1, uint32_t r2) |
| { |
| uintptr_t ra = GETPC(); |
| uint64_t end, str; |
| uint32_t len; |
| uint8_t v, c = env->regs[0]; |
| |
| /* Bits 32-55 must contain all 0. */ |
| if (env->regs[0] & 0xffffff00u) { |
| tcg_s390_program_interrupt(env, PGM_SPECIFICATION, ra); |
| } |
| |
| str = get_address(env, r2); |
| end = get_address(env, r1); |
| |
| /* Lest we fail to service interrupts in a timely manner, limit the |
| amount of work we're willing to do. For now, let's cap at 8k. */ |
| for (len = 0; len < 0x2000; ++len) { |
| if (str + len == end) { |
| /* Character not found. R1 & R2 are unmodified. */ |
| env->cc_op = 2; |
| return; |
| } |
| v = cpu_ldub_data_ra(env, str + len, ra); |
| if (v == c) { |
| /* Character found. Set R1 to the location; R2 is unmodified. */ |
| env->cc_op = 1; |
| set_address(env, r1, str + len); |
| return; |
| } |
| } |
| |
| /* CPU-determined bytes processed. Advance R2 to next byte to process. */ |
| env->cc_op = 3; |
| set_address(env, r2, str + len); |
| } |
| |
| void HELPER(srstu)(CPUS390XState *env, uint32_t r1, uint32_t r2) |
| { |
| uintptr_t ra = GETPC(); |
| uint32_t len; |
| uint16_t v, c = env->regs[0]; |
| uint64_t end, str, adj_end; |
| |
| /* Bits 32-47 of R0 must be zero. */ |
| if (env->regs[0] & 0xffff0000u) { |
| tcg_s390_program_interrupt(env, PGM_SPECIFICATION, ra); |
| } |
| |
| str = get_address(env, r2); |
| end = get_address(env, r1); |
| |
| /* If the LSB of the two addresses differ, use one extra byte. */ |
| adj_end = end + ((str ^ end) & 1); |
| |
| /* Lest we fail to service interrupts in a timely manner, limit the |
| amount of work we're willing to do. For now, let's cap at 8k. */ |
| for (len = 0; len < 0x2000; len += 2) { |
| if (str + len == adj_end) { |
| /* End of input found. */ |
| env->cc_op = 2; |
| return; |
| } |
| v = cpu_lduw_data_ra(env, str + len, ra); |
| if (v == c) { |
| /* Character found. Set R1 to the location; R2 is unmodified. */ |
| env->cc_op = 1; |
| set_address(env, r1, str + len); |
| return; |
| } |
| } |
| |
| /* CPU-determined bytes processed. Advance R2 to next byte to process. */ |
| env->cc_op = 3; |
| set_address(env, r2, str + len); |
| } |
| |
| /* unsigned string compare (c is string terminator) */ |
| Int128 HELPER(clst)(CPUS390XState *env, uint64_t c, uint64_t s1, uint64_t s2) |
| { |
| uintptr_t ra = GETPC(); |
| uint32_t len; |
| |
| c = c & 0xff; |
| s1 = wrap_address(env, s1); |
| s2 = wrap_address(env, s2); |
| |
| /* Lest we fail to service interrupts in a timely manner, limit the |
| amount of work we're willing to do. For now, let's cap at 8k. */ |
| for (len = 0; len < 0x2000; ++len) { |
| uint8_t v1 = cpu_ldub_data_ra(env, s1 + len, ra); |
| uint8_t v2 = cpu_ldub_data_ra(env, s2 + len, ra); |
| if (v1 == v2) { |
| if (v1 == c) { |
| /* Equal. CC=0, and don't advance the registers. */ |
| env->cc_op = 0; |
| return int128_make128(s2, s1); |
| } |
| } else { |
| /* Unequal. CC={1,2}, and advance the registers. Note that |
| the terminator need not be zero, but the string that contains |
| the terminator is by definition "low". */ |
| env->cc_op = (v1 == c ? 1 : v2 == c ? 2 : v1 < v2 ? 1 : 2); |
| return int128_make128(s2 + len, s1 + len); |
| } |
| } |
| |
| /* CPU-determined bytes equal; advance the registers. */ |
| env->cc_op = 3; |
| return int128_make128(s2 + len, s1 + len); |
| } |
| |
| /* move page */ |
| uint32_t HELPER(mvpg)(CPUS390XState *env, uint64_t r0, uint32_t r1, uint32_t r2) |
| { |
| const uint64_t src = get_address(env, r2) & TARGET_PAGE_MASK; |
| const uint64_t dst = get_address(env, r1) & TARGET_PAGE_MASK; |
| const int mmu_idx = s390x_env_mmu_index(env, false); |
| const bool f = extract64(r0, 11, 1); |
| const bool s = extract64(r0, 10, 1); |
| const bool cco = extract64(r0, 8, 1); |
| uintptr_t ra = GETPC(); |
| S390Access srca, desta; |
| int exc; |
| |
| if ((f && s) || extract64(r0, 12, 4)) { |
| tcg_s390_program_interrupt(env, PGM_SPECIFICATION, GETPC()); |
| } |
| |
| /* |
| * We always manually handle exceptions such that we can properly store |
| * r1/r2 to the lowcore on page-translation exceptions. |
| * |
| * TODO: Access key handling |
| */ |
| exc = access_prepare_nf(&srca, env, true, src, TARGET_PAGE_SIZE, |
| MMU_DATA_LOAD, mmu_idx, ra); |
| if (exc) { |
| if (cco) { |
| return 2; |
| } |
| goto inject_exc; |
| } |
| exc = access_prepare_nf(&desta, env, true, dst, TARGET_PAGE_SIZE, |
| MMU_DATA_STORE, mmu_idx, ra); |
| if (exc) { |
| if (cco && exc != PGM_PROTECTION) { |
| return 1; |
| } |
| goto inject_exc; |
| } |
| access_memmove(env, &desta, &srca, ra); |
| return 0; /* data moved */ |
| inject_exc: |
| #if !defined(CONFIG_USER_ONLY) |
| if (exc != PGM_ADDRESSING) { |
| stq_phys(env_cpu(env)->as, env->psa + offsetof(LowCore, trans_exc_code), |
| env->tlb_fill_tec); |
| } |
| if (exc == PGM_PAGE_TRANS) { |
| stb_phys(env_cpu(env)->as, env->psa + offsetof(LowCore, op_access_id), |
| r1 << 4 | r2); |
| } |
| #endif |
| tcg_s390_program_interrupt(env, exc, ra); |
| } |
| |
| /* string copy */ |
| uint32_t HELPER(mvst)(CPUS390XState *env, uint32_t r1, uint32_t r2) |
| { |
| const int mmu_idx = s390x_env_mmu_index(env, false); |
| const uint64_t d = get_address(env, r1); |
| const uint64_t s = get_address(env, r2); |
| const uint8_t c = env->regs[0]; |
| const int len = MIN(-(d | TARGET_PAGE_MASK), -(s | TARGET_PAGE_MASK)); |
| S390Access srca, desta; |
| uintptr_t ra = GETPC(); |
| int i; |
| |
| if (env->regs[0] & 0xffffff00ull) { |
| tcg_s390_program_interrupt(env, PGM_SPECIFICATION, ra); |
| } |
| |
| /* |
| * Our access should not exceed single pages, as we must not report access |
| * exceptions exceeding the actually copied range (which we don't know at |
| * this point). We might over-indicate watchpoints within the pages |
| * (if we ever care, we have to limit processing to a single byte). |
| */ |
| access_prepare(&srca, env, s, len, MMU_DATA_LOAD, mmu_idx, ra); |
| access_prepare(&desta, env, d, len, MMU_DATA_STORE, mmu_idx, ra); |
| for (i = 0; i < len; i++) { |
| const uint8_t v = access_get_byte(env, &srca, i, ra); |
| |
| access_set_byte(env, &desta, i, v, ra); |
| if (v == c) { |
| set_address_zero(env, r1, d + i); |
| return 1; |
| } |
| } |
| set_address_zero(env, r1, d + len); |
| set_address_zero(env, r2, s + len); |
| return 3; |
| } |
| |
| /* load access registers r1 to r3 from memory at a2 */ |
| void HELPER(lam)(CPUS390XState *env, uint32_t r1, uint64_t a2, uint32_t r3) |
| { |
| uintptr_t ra = GETPC(); |
| int i; |
| |
| if (a2 & 0x3) { |
| tcg_s390_program_interrupt(env, PGM_SPECIFICATION, ra); |
| } |
| |
| for (i = r1;; i = (i + 1) % 16) { |
| env->aregs[i] = cpu_ldl_data_ra(env, a2, ra); |
| a2 += 4; |
| |
| if (i == r3) { |
| break; |
| } |
| } |
| } |
| |
| /* store access registers r1 to r3 in memory at a2 */ |
| void HELPER(stam)(CPUS390XState *env, uint32_t r1, uint64_t a2, uint32_t r3) |
| { |
| uintptr_t ra = GETPC(); |
| int i; |
| |
| if (a2 & 0x3) { |
| tcg_s390_program_interrupt(env, PGM_SPECIFICATION, ra); |
| } |
| |
| for (i = r1;; i = (i + 1) % 16) { |
| cpu_stl_data_ra(env, a2, env->aregs[i], ra); |
| a2 += 4; |
| |
| if (i == r3) { |
| break; |
| } |
| } |
| } |
| |
| /* move long helper */ |
| static inline uint32_t do_mvcl(CPUS390XState *env, |
| uint64_t *dest, uint64_t *destlen, |
| uint64_t *src, uint64_t *srclen, |
| uint16_t pad, int wordsize, uintptr_t ra) |
| { |
| const int mmu_idx = s390x_env_mmu_index(env, false); |
| int len = MIN(*destlen, -(*dest | TARGET_PAGE_MASK)); |
| S390Access srca, desta; |
| int i, cc; |
| |
| if (*destlen == *srclen) { |
| cc = 0; |
| } else if (*destlen < *srclen) { |
| cc = 1; |
| } else { |
| cc = 2; |
| } |
| |
| if (!*destlen) { |
| return cc; |
| } |
| |
| /* |
| * Only perform one type of type of operation (move/pad) at a time. |
| * Stay within single pages. |
| */ |
| if (*srclen) { |
| /* Copy the src array */ |
| len = MIN(MIN(*srclen, -(*src | TARGET_PAGE_MASK)), len); |
| *destlen -= len; |
| *srclen -= len; |
| access_prepare(&srca, env, *src, len, MMU_DATA_LOAD, mmu_idx, ra); |
| access_prepare(&desta, env, *dest, len, MMU_DATA_STORE, mmu_idx, ra); |
| access_memmove(env, &desta, &srca, ra); |
| *src = wrap_address(env, *src + len); |
| *dest = wrap_address(env, *dest + len); |
| } else if (wordsize == 1) { |
| /* Pad the remaining area */ |
| *destlen -= len; |
| access_prepare(&desta, env, *dest, len, MMU_DATA_STORE, mmu_idx, ra); |
| access_memset(env, &desta, pad, ra); |
| *dest = wrap_address(env, *dest + len); |
| } else { |
| access_prepare(&desta, env, *dest, len, MMU_DATA_STORE, mmu_idx, ra); |
| |
| /* The remaining length selects the padding byte. */ |
| for (i = 0; i < len; (*destlen)--, i++) { |
| if (*destlen & 1) { |
| access_set_byte(env, &desta, i, pad, ra); |
| } else { |
| access_set_byte(env, &desta, i, pad >> 8, ra); |
| } |
| } |
| *dest = wrap_address(env, *dest + len); |
| } |
| |
| return *destlen ? 3 : cc; |
| } |
| |
| /* move long */ |
| uint32_t HELPER(mvcl)(CPUS390XState *env, uint32_t r1, uint32_t r2) |
| { |
| const int mmu_idx = s390x_env_mmu_index(env, false); |
| uintptr_t ra = GETPC(); |
| uint64_t destlen = env->regs[r1 + 1] & 0xffffff; |
| uint64_t dest = get_address(env, r1); |
| uint64_t srclen = env->regs[r2 + 1] & 0xffffff; |
| uint64_t src = get_address(env, r2); |
| uint8_t pad = env->regs[r2 + 1] >> 24; |
| CPUState *cs = env_cpu(env); |
| S390Access srca, desta; |
| uint32_t cc, cur_len; |
| |
| if (is_destructive_overlap(env, dest, src, MIN(srclen, destlen))) { |
| cc = 3; |
| } else if (srclen == destlen) { |
| cc = 0; |
| } else if (destlen < srclen) { |
| cc = 1; |
| } else { |
| cc = 2; |
| } |
| |
| /* We might have to zero-out some bits even if there was no action. */ |
| if (unlikely(!destlen || cc == 3)) { |
| set_address_zero(env, r2, src); |
| set_address_zero(env, r1, dest); |
| return cc; |
| } else if (!srclen) { |
| set_address_zero(env, r2, src); |
| } |
| |
| /* |
| * Only perform one type of type of operation (move/pad) in one step. |
| * Stay within single pages. |
| */ |
| while (destlen) { |
| cur_len = MIN(destlen, -(dest | TARGET_PAGE_MASK)); |
| if (!srclen) { |
| access_prepare(&desta, env, dest, cur_len, |
| MMU_DATA_STORE, mmu_idx, ra); |
| access_memset(env, &desta, pad, ra); |
| } else { |
| cur_len = MIN(MIN(srclen, -(src | TARGET_PAGE_MASK)), cur_len); |
| |
| access_prepare(&srca, env, src, cur_len, |
| MMU_DATA_LOAD, mmu_idx, ra); |
| access_prepare(&desta, env, dest, cur_len, |
| MMU_DATA_STORE, mmu_idx, ra); |
| access_memmove(env, &desta, &srca, ra); |
| src = wrap_address(env, src + cur_len); |
| srclen -= cur_len; |
| env->regs[r2 + 1] = deposit64(env->regs[r2 + 1], 0, 24, srclen); |
| set_address_zero(env, r2, src); |
| } |
| dest = wrap_address(env, dest + cur_len); |
| destlen -= cur_len; |
| env->regs[r1 + 1] = deposit64(env->regs[r1 + 1], 0, 24, destlen); |
| set_address_zero(env, r1, dest); |
| |
| /* |
| * MVCL is interruptible. Return to the main loop if requested after |
| * writing back all state to registers. If no interrupt will get |
| * injected, we'll end up back in this handler and continue processing |
| * the remaining parts. |
| */ |
| if (destlen && unlikely(cpu_loop_exit_requested(cs))) { |
| cpu_loop_exit_restore(cs, ra); |
| } |
| } |
| return cc; |
| } |
| |
| /* move long extended */ |
| uint32_t HELPER(mvcle)(CPUS390XState *env, uint32_t r1, uint64_t a2, |
| uint32_t r3) |
| { |
| uintptr_t ra = GETPC(); |
| uint64_t destlen = get_length(env, r1 + 1); |
| uint64_t dest = get_address(env, r1); |
| uint64_t srclen = get_length(env, r3 + 1); |
| uint64_t src = get_address(env, r3); |
| uint8_t pad = a2; |
| uint32_t cc; |
| |
| cc = do_mvcl(env, &dest, &destlen, &src, &srclen, pad, 1, ra); |
| |
| set_length(env, r1 + 1, destlen); |
| set_length(env, r3 + 1, srclen); |
| set_address(env, r1, dest); |
| set_address(env, r3, src); |
| |
| return cc; |
| } |
| |
| /* move long unicode */ |
| uint32_t HELPER(mvclu)(CPUS390XState *env, uint32_t r1, uint64_t a2, |
| uint32_t r3) |
| { |
| uintptr_t ra = GETPC(); |
| uint64_t destlen = get_length(env, r1 + 1); |
| uint64_t dest = get_address(env, r1); |
| uint64_t srclen = get_length(env, r3 + 1); |
| uint64_t src = get_address(env, r3); |
| uint16_t pad = a2; |
| uint32_t cc; |
| |
| cc = do_mvcl(env, &dest, &destlen, &src, &srclen, pad, 2, ra); |
| |
| set_length(env, r1 + 1, destlen); |
| set_length(env, r3 + 1, srclen); |
| set_address(env, r1, dest); |
| set_address(env, r3, src); |
| |
| return cc; |
| } |
| |
| /* compare logical long helper */ |
| static inline uint32_t do_clcl(CPUS390XState *env, |
| uint64_t *src1, uint64_t *src1len, |
| uint64_t *src3, uint64_t *src3len, |
| uint16_t pad, uint64_t limit, |
| int wordsize, uintptr_t ra) |
| { |
| uint64_t len = MAX(*src1len, *src3len); |
| uint32_t cc = 0; |
| |
| check_alignment(env, *src1len | *src3len, wordsize, ra); |
| |
| if (!len) { |
| return cc; |
| } |
| |
| /* Lest we fail to service interrupts in a timely manner, limit the |
| amount of work we're willing to do. */ |
| if (len > limit) { |
| len = limit; |
| cc = 3; |
| } |
| |
| for (; len; len -= wordsize) { |
| uint16_t v1 = pad; |
| uint16_t v3 = pad; |
| |
| if (*src1len) { |
| v1 = cpu_ldusize_data_ra(env, *src1, wordsize, ra); |
| } |
| if (*src3len) { |
| v3 = cpu_ldusize_data_ra(env, *src3, wordsize, ra); |
| } |
| |
| if (v1 != v3) { |
| cc = (v1 < v3) ? 1 : 2; |
| break; |
| } |
| |
| if (*src1len) { |
| *src1 += wordsize; |
| *src1len -= wordsize; |
| } |
| if (*src3len) { |
| *src3 += wordsize; |
| *src3len -= wordsize; |
| } |
| } |
| |
| return cc; |
| } |
| |
| |
| /* compare logical long */ |
| uint32_t HELPER(clcl)(CPUS390XState *env, uint32_t r1, uint32_t r2) |
| { |
| uintptr_t ra = GETPC(); |
| uint64_t src1len = extract64(env->regs[r1 + 1], 0, 24); |
| uint64_t src1 = get_address(env, r1); |
| uint64_t src3len = extract64(env->regs[r2 + 1], 0, 24); |
| uint64_t src3 = get_address(env, r2); |
| uint8_t pad = env->regs[r2 + 1] >> 24; |
| uint32_t cc; |
| |
| cc = do_clcl(env, &src1, &src1len, &src3, &src3len, pad, -1, 1, ra); |
| |
| env->regs[r1 + 1] = deposit64(env->regs[r1 + 1], 0, 24, src1len); |
| env->regs[r2 + 1] = deposit64(env->regs[r2 + 1], 0, 24, src3len); |
| set_address(env, r1, src1); |
| set_address(env, r2, src3); |
| |
| return cc; |
| } |
| |
| /* compare logical long extended memcompare insn with padding */ |
| uint32_t HELPER(clcle)(CPUS390XState *env, uint32_t r1, uint64_t a2, |
| uint32_t r3) |
| { |
| uintptr_t ra = GETPC(); |
| uint64_t src1len = get_length(env, r1 + 1); |
| uint64_t src1 = get_address(env, r1); |
| uint64_t src3len = get_length(env, r3 + 1); |
| uint64_t src3 = get_address(env, r3); |
| uint8_t pad = a2; |
| uint32_t cc; |
| |
| cc = do_clcl(env, &src1, &src1len, &src3, &src3len, pad, 0x2000, 1, ra); |
| |
| set_length(env, r1 + 1, src1len); |
| set_length(env, r3 + 1, src3len); |
| set_address(env, r1, src1); |
| set_address(env, r3, src3); |
| |
| return cc; |
| } |
| |
| /* compare logical long unicode memcompare insn with padding */ |
| uint32_t HELPER(clclu)(CPUS390XState *env, uint32_t r1, uint64_t a2, |
| uint32_t r3) |
| { |
| uintptr_t ra = GETPC(); |
| uint64_t src1len = get_length(env, r1 + 1); |
| uint64_t src1 = get_address(env, r1); |
| uint64_t src3len = get_length(env, r3 + 1); |
| uint64_t src3 = get_address(env, r3); |
| uint16_t pad = a2; |
| uint32_t cc = 0; |
| |
| cc = do_clcl(env, &src1, &src1len, &src3, &src3len, pad, 0x1000, 2, ra); |
| |
| set_length(env, r1 + 1, src1len); |
| set_length(env, r3 + 1, src3len); |
| set_address(env, r1, src1); |
| set_address(env, r3, src3); |
| |
| return cc; |
| } |
| |
| /* checksum */ |
| Int128 HELPER(cksm)(CPUS390XState *env, uint64_t r1, |
| uint64_t src, uint64_t src_len) |
| { |
| uintptr_t ra = GETPC(); |
| uint64_t max_len, len; |
| uint64_t cksm = (uint32_t)r1; |
| |
| /* Lest we fail to service interrupts in a timely manner, limit the |
| amount of work we're willing to do. For now, let's cap at 8k. */ |
| max_len = (src_len > 0x2000 ? 0x2000 : src_len); |
| |
| /* Process full words as available. */ |
| for (len = 0; len + 4 <= max_len; len += 4, src += 4) { |
| cksm += (uint32_t)cpu_ldl_data_ra(env, src, ra); |
| } |
| |
| switch (max_len - len) { |
| case 1: |
| cksm += cpu_ldub_data_ra(env, src, ra) << 24; |
| len += 1; |
| break; |
| case 2: |
| cksm += cpu_lduw_data_ra(env, src, ra) << 16; |
| len += 2; |
| break; |
| case 3: |
| cksm += cpu_lduw_data_ra(env, src, ra) << 16; |
| cksm += cpu_ldub_data_ra(env, src + 2, ra) << 8; |
| len += 3; |
| break; |
| } |
| |
| /* Fold the carry from the checksum. Note that we can see carry-out |
| during folding more than once (but probably not more than twice). */ |
| while (cksm > 0xffffffffull) { |
| cksm = (uint32_t)cksm + (cksm >> 32); |
| } |
| |
| /* Indicate whether or not we've processed everything. */ |
| env->cc_op = (len == src_len ? 0 : 3); |
| |
| /* Return both cksm and processed length. */ |
| return int128_make128(cksm, len); |
| } |
| |
| void HELPER(pack)(CPUS390XState *env, uint32_t len, uint64_t dest, uint64_t src) |
| { |
| uintptr_t ra = GETPC(); |
| int len_dest = len >> 4; |
| int len_src = len & 0xf; |
| uint8_t b; |
| |
| dest += len_dest; |
| src += len_src; |
| |
| /* last byte is special, it only flips the nibbles */ |
| b = cpu_ldub_data_ra(env, src, ra); |
| cpu_stb_data_ra(env, dest, (b << 4) | (b >> 4), ra); |
| src--; |
| len_src--; |
| |
| /* now pack every value */ |
| while (len_dest > 0) { |
| b = 0; |
| |
| if (len_src >= 0) { |
| b = cpu_ldub_data_ra(env, src, ra) & 0x0f; |
| src--; |
| len_src--; |
| } |
| if (len_src >= 0) { |
| b |= cpu_ldub_data_ra(env, src, ra) << 4; |
| src--; |
| len_src--; |
| } |
| |
| len_dest--; |
| dest--; |
| cpu_stb_data_ra(env, dest, b, ra); |
| } |
| } |
| |
| static inline void do_pkau(CPUS390XState *env, uint64_t dest, uint64_t src, |
| uint32_t srclen, int ssize, uintptr_t ra) |
| { |
| int i; |
| /* The destination operand is always 16 bytes long. */ |
| const int destlen = 16; |
| |
| /* The operands are processed from right to left. */ |
| src += srclen - 1; |
| dest += destlen - 1; |
| |
| for (i = 0; i < destlen; i++) { |
| uint8_t b = 0; |
| |
| /* Start with a positive sign */ |
| if (i == 0) { |
| b = 0xc; |
| } else if (srclen > ssize) { |
| b = cpu_ldub_data_ra(env, src, ra) & 0x0f; |
| src -= ssize; |
| srclen -= ssize; |
| } |
| |
| if (srclen > ssize) { |
| b |= cpu_ldub_data_ra(env, src, ra) << 4; |
| src -= ssize; |
| srclen -= ssize; |
| } |
| |
| cpu_stb_data_ra(env, dest, b, ra); |
| dest--; |
| } |
| } |
| |
| |
| void HELPER(pka)(CPUS390XState *env, uint64_t dest, uint64_t src, |
| uint32_t srclen) |
| { |
| do_pkau(env, dest, src, srclen, 1, GETPC()); |
| } |
| |
| void HELPER(pku)(CPUS390XState *env, uint64_t dest, uint64_t src, |
| uint32_t srclen) |
| { |
| do_pkau(env, dest, src, srclen, 2, GETPC()); |
| } |
| |
| void HELPER(unpk)(CPUS390XState *env, uint32_t len, uint64_t dest, |
| uint64_t src) |
| { |
| uintptr_t ra = GETPC(); |
| int len_dest = len >> 4; |
| int len_src = len & 0xf; |
| uint8_t b; |
| int second_nibble = 0; |
| |
| dest += len_dest; |
| src += len_src; |
| |
| /* last byte is special, it only flips the nibbles */ |
| b = cpu_ldub_data_ra(env, src, ra); |
| cpu_stb_data_ra(env, dest, (b << 4) | (b >> 4), ra); |
| src--; |
| len_src--; |
| |
| /* now pad every nibble with 0xf0 */ |
| |
| while (len_dest > 0) { |
| uint8_t cur_byte = 0; |
| |
| if (len_src > 0) { |
| cur_byte = cpu_ldub_data_ra(env, src, ra); |
| } |
| |
| len_dest--; |
| dest--; |
| |
| /* only advance one nibble at a time */ |
| if (second_nibble) { |
| cur_byte >>= 4; |
| len_src--; |
| src--; |
| } |
| second_nibble = !second_nibble; |
| |
| /* digit */ |
| cur_byte = (cur_byte & 0xf); |
| /* zone bits */ |
| cur_byte |= 0xf0; |
| |
| cpu_stb_data_ra(env, dest, cur_byte, ra); |
| } |
| } |
| |
| static inline uint32_t do_unpkau(CPUS390XState *env, uint64_t dest, |
| uint32_t destlen, int dsize, uint64_t src, |
| uintptr_t ra) |
| { |
| int i; |
| uint32_t cc; |
| uint8_t b; |
| /* The source operand is always 16 bytes long. */ |
| const int srclen = 16; |
| |
| /* The operands are processed from right to left. */ |
| src += srclen - 1; |
| dest += destlen - dsize; |
| |
| /* Check for the sign. */ |
| b = cpu_ldub_data_ra(env, src, ra); |
| src--; |
| switch (b & 0xf) { |
| case 0xa: |
| case 0xc: |
| case 0xe ... 0xf: |
| cc = 0; /* plus */ |
| break; |
| case 0xb: |
| case 0xd: |
| cc = 1; /* minus */ |
| break; |
| default: |
| case 0x0 ... 0x9: |
| cc = 3; /* invalid */ |
| break; |
| } |
| |
| /* Now pad every nibble with 0x30, advancing one nibble at a time. */ |
| for (i = 0; i < destlen; i += dsize) { |
| if (i == (31 * dsize)) { |
| /* If length is 32/64 bytes, the leftmost byte is 0. */ |
| b = 0; |
| } else if (i % (2 * dsize)) { |
| b = cpu_ldub_data_ra(env, src, ra); |
| src--; |
| } else { |
| b >>= 4; |
| } |
| cpu_stsize_data_ra(env, dest, 0x30 + (b & 0xf), dsize, ra); |
| dest -= dsize; |
| } |
| |
| return cc; |
| } |
| |
| uint32_t HELPER(unpka)(CPUS390XState *env, uint64_t dest, uint32_t destlen, |
| uint64_t src) |
| { |
| return do_unpkau(env, dest, destlen, 1, src, GETPC()); |
| } |
| |
| uint32_t HELPER(unpku)(CPUS390XState *env, uint64_t dest, uint32_t destlen, |
| uint64_t src) |
| { |
| return do_unpkau(env, dest, destlen, 2, src, GETPC()); |
| } |
| |
| uint32_t HELPER(tp)(CPUS390XState *env, uint64_t dest, uint32_t destlen) |
| { |
| uintptr_t ra = GETPC(); |
| uint32_t cc = 0; |
| int i; |
| |
| for (i = 0; i < destlen; i++) { |
| uint8_t b = cpu_ldub_data_ra(env, dest + i, ra); |
| /* digit */ |
| cc |= (b & 0xf0) > 0x90 ? 2 : 0; |
| |
| if (i == (destlen - 1)) { |
| /* sign */ |
| cc |= (b & 0xf) < 0xa ? 1 : 0; |
| } else { |
| /* digit */ |
| cc |= (b & 0xf) > 0x9 ? 2 : 0; |
| } |
| } |
| |
| return cc; |
| } |
| |
| static uint32_t do_helper_tr(CPUS390XState *env, uint32_t len, uint64_t array, |
| uint64_t trans, uintptr_t ra) |
| { |
| uint32_t i; |
| |
| for (i = 0; i <= len; i++) { |
| uint8_t byte = cpu_ldub_data_ra(env, array + i, ra); |
| uint8_t new_byte = cpu_ldub_data_ra(env, trans + byte, ra); |
| cpu_stb_data_ra(env, array + i, new_byte, ra); |
| } |
| |
| return env->cc_op; |
| } |
| |
| void HELPER(tr)(CPUS390XState *env, uint32_t len, uint64_t array, |
| uint64_t trans) |
| { |
| do_helper_tr(env, len, array, trans, GETPC()); |
| } |
| |
| Int128 HELPER(tre)(CPUS390XState *env, uint64_t array, |
| uint64_t len, uint64_t trans) |
| { |
| uintptr_t ra = GETPC(); |
| uint8_t end = env->regs[0] & 0xff; |
| uint64_t l = len; |
| uint64_t i; |
| uint32_t cc = 0; |
| |
| if (!(env->psw.mask & PSW_MASK_64)) { |
| array &= 0x7fffffff; |
| l = (uint32_t)l; |
| } |
| |
| /* Lest we fail to service interrupts in a timely manner, limit the |
| amount of work we're willing to do. For now, let's cap at 8k. */ |
| if (l > 0x2000) { |
| l = 0x2000; |
| cc = 3; |
| } |
| |
| for (i = 0; i < l; i++) { |
| uint8_t byte, new_byte; |
| |
| byte = cpu_ldub_data_ra(env, array + i, ra); |
| |
| if (byte == end) { |
| cc = 1; |
| break; |
| } |
| |
| new_byte = cpu_ldub_data_ra(env, trans + byte, ra); |
| cpu_stb_data_ra(env, array + i, new_byte, ra); |
| } |
| |
| env->cc_op = cc; |
| return int128_make128(len - i, array + i); |
| } |
| |
| static inline uint32_t do_helper_trt(CPUS390XState *env, int len, |
| uint64_t array, uint64_t trans, |
| int inc, uintptr_t ra) |
| { |
| int i; |
| |
| for (i = 0; i <= len; i++) { |
| uint8_t byte = cpu_ldub_data_ra(env, array + i * inc, ra); |
| uint8_t sbyte = cpu_ldub_data_ra(env, trans + byte, ra); |
| |
| if (sbyte != 0) { |
| set_address(env, 1, array + i * inc); |
| env->regs[2] = deposit64(env->regs[2], 0, 8, sbyte); |
| return (i == len) ? 2 : 1; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static uint32_t do_helper_trt_fwd(CPUS390XState *env, uint32_t len, |
| uint64_t array, uint64_t trans, |
| uintptr_t ra) |
| { |
| return do_helper_trt(env, len, array, trans, 1, ra); |
| } |
| |
| uint32_t HELPER(trt)(CPUS390XState *env, uint32_t len, uint64_t array, |
| uint64_t trans) |
| { |
| return do_helper_trt(env, len, array, trans, 1, GETPC()); |
| } |
| |
| static uint32_t do_helper_trt_bkwd(CPUS390XState *env, uint32_t len, |
| uint64_t array, uint64_t trans, |
| uintptr_t ra) |
| { |
| return do_helper_trt(env, len, array, trans, -1, ra); |
| } |
| |
| uint32_t HELPER(trtr)(CPUS390XState *env, uint32_t len, uint64_t array, |
| uint64_t trans) |
| { |
| return do_helper_trt(env, len, array, trans, -1, GETPC()); |
| } |
| |
| /* Translate one/two to one/two */ |
| uint32_t HELPER(trXX)(CPUS390XState *env, uint32_t r1, uint32_t r2, |
| uint32_t tst, uint32_t sizes) |
| { |
| uintptr_t ra = GETPC(); |
| int dsize = (sizes & 1) ? 1 : 2; |
| int ssize = (sizes & 2) ? 1 : 2; |
| uint64_t tbl = get_address(env, 1); |
| uint64_t dst = get_address(env, r1); |
| uint64_t len = get_length(env, r1 + 1); |
| uint64_t src = get_address(env, r2); |
| uint32_t cc = 3; |
| int i; |
| |
| /* The lower address bits of TBL are ignored. For TROO, TROT, it's |
| the low 3 bits (double-word aligned). For TRTO, TRTT, it's either |
| the low 12 bits (4K, without ETF2-ENH) or 3 bits (with ETF2-ENH). */ |
| if (ssize == 2 && !s390_has_feat(S390_FEAT_ETF2_ENH)) { |
| tbl &= -4096; |
| } else { |
| tbl &= -8; |
| } |
| |
| check_alignment(env, len, ssize, ra); |
| |
| /* Lest we fail to service interrupts in a timely manner, */ |
| /* limit the amount of work we're willing to do. */ |
| for (i = 0; i < 0x2000; i++) { |
| uint16_t sval = cpu_ldusize_data_ra(env, src, ssize, ra); |
| uint64_t tble = tbl + (sval * dsize); |
| uint16_t dval = cpu_ldusize_data_ra(env, tble, dsize, ra); |
| if (dval == tst) { |
| cc = 1; |
| break; |
| } |
| cpu_stsize_data_ra(env, dst, dval, dsize, ra); |
| |
| len -= ssize; |
| src += ssize; |
| dst += dsize; |
| |
| if (len == 0) { |
| cc = 0; |
| break; |
| } |
| } |
| |
| set_address(env, r1, dst); |
| set_length(env, r1 + 1, len); |
| set_address(env, r2, src); |
| |
| return cc; |
| } |
| |
| static uint32_t do_csst(CPUS390XState *env, uint32_t r3, uint64_t a1, |
| uint64_t a2, bool parallel) |
| { |
| uint32_t mem_idx = s390x_env_mmu_index(env, false); |
| MemOpIdx oi16 = make_memop_idx(MO_TE | MO_128, mem_idx); |
| MemOpIdx oi8 = make_memop_idx(MO_TE | MO_64, mem_idx); |
| MemOpIdx oi4 = make_memop_idx(MO_TE | MO_32, mem_idx); |
| MemOpIdx oi2 = make_memop_idx(MO_TE | MO_16, mem_idx); |
| MemOpIdx oi1 = make_memop_idx(MO_8, mem_idx); |
| uintptr_t ra = GETPC(); |
| uint32_t fc = extract32(env->regs[0], 0, 8); |
| uint32_t sc = extract32(env->regs[0], 8, 8); |
| uint64_t pl = get_address(env, 1) & -16; |
| uint64_t svh, svl; |
| uint32_t cc; |
| |
| /* Sanity check the function code and storage characteristic. */ |
| if (fc > 1 || sc > 3) { |
| if (!s390_has_feat(S390_FEAT_COMPARE_AND_SWAP_AND_STORE_2)) { |
| goto spec_exception; |
| } |
| if (fc > 2 || sc > 4 || (fc == 2 && (r3 & 1))) { |
| goto spec_exception; |
| } |
| } |
| |
| /* Sanity check the alignments. */ |
| if (extract32(a1, 0, fc + 2) || extract32(a2, 0, sc)) { |
| goto spec_exception; |
| } |
| |
| /* Sanity check writability of the store address. */ |
| probe_write(env, a2, 1 << sc, mem_idx, ra); |
| |
| /* |
| * Note that the compare-and-swap is atomic, and the store is atomic, |
| * but the complete operation is not. Therefore we do not need to |
| * assert serial context in order to implement this. That said, |
| * restart early if we can't support either operation that is supposed |
| * to be atomic. |
| */ |
| if (parallel) { |
| uint32_t max = 2; |
| #ifdef CONFIG_ATOMIC64 |
| max = 3; |
| #endif |
| if ((HAVE_CMPXCHG128 ? 0 : fc + 2 > max) || |
| (HAVE_ATOMIC128_RW ? 0 : sc > max)) { |
| cpu_loop_exit_atomic(env_cpu(env), ra); |
| } |
| } |
| |
| /* |
| * All loads happen before all stores. For simplicity, load the entire |
| * store value area from the parameter list. |
| */ |
| svh = cpu_ldq_mmu(env, pl + 16, oi8, ra); |
| svl = cpu_ldq_mmu(env, pl + 24, oi8, ra); |
| |
| switch (fc) { |
| case 0: |
| { |
| uint32_t nv = cpu_ldl_mmu(env, pl, oi4, ra); |
| uint32_t cv = env->regs[r3]; |
| uint32_t ov; |
| |
| if (parallel) { |
| ov = cpu_atomic_cmpxchgl_be_mmu(env, a1, cv, nv, oi4, ra); |
| } else { |
| ov = cpu_ldl_mmu(env, a1, oi4, ra); |
| cpu_stl_mmu(env, a1, (ov == cv ? nv : ov), oi4, ra); |
| } |
| cc = (ov != cv); |
| env->regs[r3] = deposit64(env->regs[r3], 32, 32, ov); |
| } |
| break; |
| |
| case 1: |
| { |
| uint64_t nv = cpu_ldq_mmu(env, pl, oi8, ra); |
| uint64_t cv = env->regs[r3]; |
| uint64_t ov; |
| |
| if (parallel) { |
| #ifdef CONFIG_ATOMIC64 |
| ov = cpu_atomic_cmpxchgq_be_mmu(env, a1, cv, nv, oi8, ra); |
| #else |
| /* Note that we asserted !parallel above. */ |
| g_assert_not_reached(); |
| #endif |
| } else { |
| ov = cpu_ldq_mmu(env, a1, oi8, ra); |
| cpu_stq_mmu(env, a1, (ov == cv ? nv : ov), oi8, ra); |
| } |
| cc = (ov != cv); |
| env->regs[r3] = ov; |
| } |
| break; |
| |
| case 2: |
| { |
| Int128 nv = cpu_ld16_mmu(env, pl, oi16, ra); |
| Int128 cv = int128_make128(env->regs[r3 + 1], env->regs[r3]); |
| Int128 ov; |
| |
| if (!parallel) { |
| ov = cpu_ld16_mmu(env, a1, oi16, ra); |
| cc = !int128_eq(ov, cv); |
| if (cc) { |
| nv = ov; |
| } |
| cpu_st16_mmu(env, a1, nv, oi16, ra); |
| } else if (HAVE_CMPXCHG128) { |
| ov = cpu_atomic_cmpxchgo_be_mmu(env, a1, cv, nv, oi16, ra); |
| cc = !int128_eq(ov, cv); |
| } else { |
| /* Note that we asserted !parallel above. */ |
| g_assert_not_reached(); |
| } |
| |
| env->regs[r3 + 0] = int128_gethi(ov); |
| env->regs[r3 + 1] = int128_getlo(ov); |
| } |
| break; |
| |
| default: |
| g_assert_not_reached(); |
| } |
| |
| /* Store only if the comparison succeeded. Note that above we use a pair |
| of 64-bit big-endian loads, so for sc < 3 we must extract the value |
| from the most-significant bits of svh. */ |
| if (cc == 0) { |
| switch (sc) { |
| case 0: |
| cpu_stb_mmu(env, a2, svh >> 56, oi1, ra); |
| break; |
| case 1: |
| cpu_stw_mmu(env, a2, svh >> 48, oi2, ra); |
| break; |
| case 2: |
| cpu_stl_mmu(env, a2, svh >> 32, oi4, ra); |
| break; |
| case 3: |
| cpu_stq_mmu(env, a2, svh, oi8, ra); |
| break; |
| case 4: |
| cpu_st16_mmu(env, a2, int128_make128(svl, svh), oi16, ra); |
| break; |
| default: |
| g_assert_not_reached(); |
| } |
| } |
| |
| return cc; |
| |
| spec_exception: |
| tcg_s390_program_interrupt(env, PGM_SPECIFICATION, ra); |
| } |
| |
| uint32_t HELPER(csst)(CPUS390XState *env, uint32_t r3, uint64_t a1, uint64_t a2) |
| { |
| return do_csst(env, r3, a1, a2, false); |
| } |
| |
| uint32_t HELPER(csst_parallel)(CPUS390XState *env, uint32_t r3, uint64_t a1, |
| uint64_t a2) |
| { |
| return do_csst(env, r3, a1, a2, true); |
| } |
| |
| #if !defined(CONFIG_USER_ONLY) |
| void HELPER(lctlg)(CPUS390XState *env, uint32_t r1, uint64_t a2, uint32_t r3) |
| { |
| uintptr_t ra = GETPC(); |
| bool PERchanged = false; |
| uint64_t src = a2; |
| uint32_t i; |
| |
| if (src & 0x7) { |
| tcg_s390_program_interrupt(env, PGM_SPECIFICATION, ra); |
| } |
| |
| for (i = r1;; i = (i + 1) % 16) { |
| uint64_t val = cpu_ldq_data_ra(env, src, ra); |
| if (env->cregs[i] != val && i >= 9 && i <= 11) { |
| PERchanged = true; |
| } |
| env->cregs[i] = val; |
| HELPER_LOG("load ctl %d from 0x%" PRIx64 " == 0x%" PRIx64 "\n", |
| i, src, val); |
| src += sizeof(uint64_t); |
| |
| if (i == r3) { |
| break; |
| } |
| } |
| |
| if (PERchanged && env->psw.mask & PSW_MASK_PER) { |
| s390_cpu_recompute_watchpoints(env_cpu(env)); |
| } |
| |
| tlb_flush(env_cpu(env)); |
| } |
| |
| void HELPER(lctl)(CPUS390XState *env, uint32_t r1, uint64_t a2, uint32_t r3) |
| { |
| uintptr_t ra = GETPC(); |
| bool PERchanged = false; |
| uint64_t src = a2; |
| uint32_t i; |
| |
| if (src & 0x3) { |
| tcg_s390_program_interrupt(env, PGM_SPECIFICATION, ra); |
| } |
| |
| for (i = r1;; i = (i + 1) % 16) { |
| uint32_t val = cpu_ldl_data_ra(env, src, ra); |
| if ((uint32_t)env->cregs[i] != val && i >= 9 && i <= 11) { |
| PERchanged = true; |
| } |
| env->cregs[i] = deposit64(env->cregs[i], 0, 32, val); |
| HELPER_LOG("load ctl %d from 0x%" PRIx64 " == 0x%x\n", i, src, val); |
| src += sizeof(uint32_t); |
| |
| if (i == r3) { |
| break; |
| } |
| } |
| |
| if (PERchanged && env->psw.mask & PSW_MASK_PER) { |
| s390_cpu_recompute_watchpoints(env_cpu(env)); |
| } |
| |
| tlb_flush(env_cpu(env)); |
| } |
| |
| void HELPER(stctg)(CPUS390XState *env, uint32_t r1, uint64_t a2, uint32_t r3) |
| { |
| uintptr_t ra = GETPC(); |
| uint64_t dest = a2; |
| uint32_t i; |
| |
| if (dest & 0x7) { |
| tcg_s390_program_interrupt(env, PGM_SPECIFICATION, ra); |
| } |
| |
| for (i = r1;; i = (i + 1) % 16) { |
| cpu_stq_data_ra(env, dest, env->cregs[i], ra); |
| dest += sizeof(uint64_t); |
| |
| if (i == r3) { |
| break; |
| } |
| } |
| } |
| |
| void HELPER(stctl)(CPUS390XState *env, uint32_t r1, uint64_t a2, uint32_t r3) |
| { |
| uintptr_t ra = GETPC(); |
| uint64_t dest = a2; |
| uint32_t i; |
| |
| if (dest & 0x3) { |
| tcg_s390_program_interrupt(env, PGM_SPECIFICATION, ra); |
| } |
| |
| for (i = r1;; i = (i + 1) % 16) { |
| cpu_stl_data_ra(env, dest, env->cregs[i], ra); |
| dest += sizeof(uint32_t); |
| |
| if (i == r3) { |
| break; |
| } |
| } |
| } |
| |
| uint32_t HELPER(testblock)(CPUS390XState *env, uint64_t real_addr) |
| { |
| uintptr_t ra = GETPC(); |
| int i; |
| |
| real_addr = wrap_address(env, real_addr) & TARGET_PAGE_MASK; |
| |
| for (i = 0; i < TARGET_PAGE_SIZE; i += 8) { |
| cpu_stq_mmuidx_ra(env, real_addr + i, 0, MMU_REAL_IDX, ra); |
| } |
| |
| return 0; |
| } |
| |
| uint32_t HELPER(tprot)(CPUS390XState *env, uint64_t a1, uint64_t a2) |
| { |
| S390CPU *cpu = env_archcpu(env); |
| CPUState *cs = env_cpu(env); |
| |
| /* |
| * TODO: we currently don't handle all access protection types |
| * (including access-list and key-controlled) as well as AR mode. |
| */ |
| if (!s390_cpu_virt_mem_check_write(cpu, a1, 0, 1)) { |
| /* Fetching permitted; storing permitted */ |
| return 0; |
| } |
| |
| if (env->int_pgm_code == PGM_PROTECTION) { |
| /* retry if reading is possible */ |
| cs->exception_index = -1; |
| if (!s390_cpu_virt_mem_check_read(cpu, a1, 0, 1)) { |
| /* Fetching permitted; storing not permitted */ |
| return 1; |
| } |
| } |
| |
| switch (env->int_pgm_code) { |
| case PGM_PROTECTION: |
| /* Fetching not permitted; storing not permitted */ |
| cs->exception_index = -1; |
| return 2; |
| case PGM_ADDRESSING: |
| case PGM_TRANS_SPEC: |
| /* exceptions forwarded to the guest */ |
| s390_cpu_virt_mem_handle_exc(cpu, GETPC()); |
| return 0; |
| } |
| |
| /* Translation not available */ |
| cs->exception_index = -1; |
| return 3; |
| } |
| |
| /* insert storage key extended */ |
| uint64_t HELPER(iske)(CPUS390XState *env, uint64_t r2) |
| { |
| static S390SKeysState *ss; |
| static S390SKeysClass *skeyclass; |
| uint64_t addr = wrap_address(env, r2); |
| uint8_t key; |
| int rc; |
| |
| addr = mmu_real2abs(env, addr); |
| if (!mmu_absolute_addr_valid(addr, false)) { |
| tcg_s390_program_interrupt(env, PGM_ADDRESSING, GETPC()); |
| } |
| |
| if (unlikely(!ss)) { |
| ss = s390_get_skeys_device(); |
| skeyclass = S390_SKEYS_GET_CLASS(ss); |
| if (skeyclass->enable_skeys && !skeyclass->enable_skeys(ss)) { |
| tlb_flush_all_cpus_synced(env_cpu(env)); |
| } |
| } |
| |
| rc = s390_skeys_get(ss, addr / TARGET_PAGE_SIZE, 1, &key); |
| if (rc) { |
| return 0; |
| } |
| return key; |
| } |
| |
| /* set storage key extended */ |
| void HELPER(sske)(CPUS390XState *env, uint64_t r1, uint64_t r2) |
| { |
| static S390SKeysState *ss; |
| static S390SKeysClass *skeyclass; |
| uint64_t addr = wrap_address(env, r2); |
| uint8_t key; |
| |
| addr = mmu_real2abs(env, addr); |
| if (!mmu_absolute_addr_valid(addr, false)) { |
| tcg_s390_program_interrupt(env, PGM_ADDRESSING, GETPC()); |
| } |
| |
| if (unlikely(!ss)) { |
| ss = s390_get_skeys_device(); |
| skeyclass = S390_SKEYS_GET_CLASS(ss); |
| if (skeyclass->enable_skeys && !skeyclass->enable_skeys(ss)) { |
| tlb_flush_all_cpus_synced(env_cpu(env)); |
| } |
| } |
| |
| key = r1 & 0xfe; |
| s390_skeys_set(ss, addr / TARGET_PAGE_SIZE, 1, &key); |
| /* |
| * As we can only flush by virtual address and not all the entries |
| * that point to a physical address we have to flush the whole TLB. |
| */ |
| tlb_flush_all_cpus_synced(env_cpu(env)); |
| } |
| |
| /* reset reference bit extended */ |
| uint32_t HELPER(rrbe)(CPUS390XState *env, uint64_t r2) |
| { |
| uint64_t addr = wrap_address(env, r2); |
| static S390SKeysState *ss; |
| static S390SKeysClass *skeyclass; |
| uint8_t re, key; |
| int rc; |
| |
| addr = mmu_real2abs(env, addr); |
| if (!mmu_absolute_addr_valid(addr, false)) { |
| tcg_s390_program_interrupt(env, PGM_ADDRESSING, GETPC()); |
| } |
| |
| if (unlikely(!ss)) { |
| ss = s390_get_skeys_device(); |
| skeyclass = S390_SKEYS_GET_CLASS(ss); |
| if (skeyclass->enable_skeys && !skeyclass->enable_skeys(ss)) { |
| tlb_flush_all_cpus_synced(env_cpu(env)); |
| } |
| } |
| |
| rc = s390_skeys_get(ss, addr / TARGET_PAGE_SIZE, 1, &key); |
| if (rc) { |
| return 0; |
| } |
| |
| re = key & (SK_R | SK_C); |
| key &= ~SK_R; |
| |
| rc = s390_skeys_set(ss, addr / TARGET_PAGE_SIZE, 1, &key); |
| if (rc) { |
| return 0; |
| } |
| /* |
| * As we can only flush by virtual address and not all the entries |
| * that point to a physical address we have to flush the whole TLB. |
| */ |
| tlb_flush_all_cpus_synced(env_cpu(env)); |
| |
| /* |
| * cc |
| * |
| * 0 Reference bit zero; change bit zero |
| * 1 Reference bit zero; change bit one |
| * 2 Reference bit one; change bit zero |
| * 3 Reference bit one; change bit one |
| */ |
| |
| return re >> 1; |
| } |
| |
| uint32_t HELPER(mvcs)(CPUS390XState *env, uint64_t l, uint64_t a1, uint64_t a2, |
| uint64_t key) |
| { |
| const uint8_t psw_as = (env->psw.mask & PSW_MASK_ASC) >> PSW_SHIFT_ASC; |
| S390Access srca, desta; |
| uintptr_t ra = GETPC(); |
| int cc = 0; |
| |
| HELPER_LOG("%s: %16" PRIx64 " %16" PRIx64 " %16" PRIx64 "\n", |
| __func__, l, a1, a2); |
| |
| if (!(env->psw.mask & PSW_MASK_DAT) || !(env->cregs[0] & CR0_SECONDARY) || |
| psw_as == AS_HOME || psw_as == AS_ACCREG) { |
| s390_program_interrupt(env, PGM_SPECIAL_OP, ra); |
| } |
| |
| if (!psw_key_valid(env, (key >> 4) & 0xf)) { |
| s390_program_interrupt(env, PGM_PRIVILEGED, ra); |
| } |
| |
| l = wrap_length32(env, l); |
| if (l > 256) { |
| /* max 256 */ |
| l = 256; |
| cc = 3; |
| } else if (!l) { |
| return cc; |
| } |
| |
| access_prepare(&srca, env, a2, l, MMU_DATA_LOAD, MMU_PRIMARY_IDX, ra); |
| access_prepare(&desta, env, a1, l, MMU_DATA_STORE, MMU_SECONDARY_IDX, ra); |
| access_memmove(env, &desta, &srca, ra); |
| return cc; |
| } |
| |
| uint32_t HELPER(mvcp)(CPUS390XState *env, uint64_t l, uint64_t a1, uint64_t a2, |
| uint64_t key) |
| { |
| const uint8_t psw_as = (env->psw.mask & PSW_MASK_ASC) >> PSW_SHIFT_ASC; |
| S390Access srca, desta; |
| uintptr_t ra = GETPC(); |
| int cc = 0; |
| |
| HELPER_LOG("%s: %16" PRIx64 " %16" PRIx64 " %16" PRIx64 "\n", |
| __func__, l, a1, a2); |
| |
| if (!(env->psw.mask & PSW_MASK_DAT) || !(env->cregs[0] & CR0_SECONDARY) || |
| psw_as == AS_HOME || psw_as == AS_ACCREG) { |
| s390_program_interrupt(env, PGM_SPECIAL_OP, ra); |
| } |
| |
| if (!psw_key_valid(env, (key >> 4) & 0xf)) { |
| s390_program_interrupt(env, PGM_PRIVILEGED, ra); |
| } |
| |
| l = wrap_length32(env, l); |
| if (l > 256) { |
| /* max 256 */ |
| l = 256; |
| cc = 3; |
| } else if (!l) { |
| return cc; |
| } |
| access_prepare(&srca, env, a2, l, MMU_DATA_LOAD, MMU_SECONDARY_IDX, ra); |
| access_prepare(&desta, env, a1, l, MMU_DATA_STORE, MMU_PRIMARY_IDX, ra); |
| access_memmove(env, &desta, &srca, ra); |
| return cc; |
| } |
| |
| void HELPER(idte)(CPUS390XState *env, uint64_t r1, uint64_t r2, uint32_t m4) |
| { |
| CPUState *cs = env_cpu(env); |
| const uintptr_t ra = GETPC(); |
| uint64_t table, entry, raddr; |
| uint16_t entries, i, index = 0; |
| |
| if (r2 & 0xff000) { |
| tcg_s390_program_interrupt(env, PGM_SPECIFICATION, ra); |
| } |
| |
| if (!(r2 & 0x800)) { |
| /* invalidation-and-clearing operation */ |
| table = r1 & ASCE_ORIGIN; |
| entries = (r2 & 0x7ff) + 1; |
| |
| switch (r1 & ASCE_TYPE_MASK) { |
| case ASCE_TYPE_REGION1: |
| index = (r2 >> 53) & 0x7ff; |
| break; |
| case ASCE_TYPE_REGION2: |
| index = (r2 >> 42) & 0x7ff; |
| break; |
| case ASCE_TYPE_REGION3: |
| index = (r2 >> 31) & 0x7ff; |
| break; |
| case ASCE_TYPE_SEGMENT: |
| index = (r2 >> 20) & 0x7ff; |
| break; |
| } |
| for (i = 0; i < entries; i++) { |
| /* addresses are not wrapped in 24/31bit mode but table index is */ |
| raddr = table + ((index + i) & 0x7ff) * sizeof(entry); |
| entry = cpu_ldq_mmuidx_ra(env, raddr, MMU_REAL_IDX, ra); |
| if (!(entry & REGION_ENTRY_I)) { |
| /* we are allowed to not store if already invalid */ |
| entry |= REGION_ENTRY_I; |
| cpu_stq_mmuidx_ra(env, raddr, entry, MMU_REAL_IDX, ra); |
| } |
| } |
| } |
| |
| /* We simply flush the complete tlb, therefore we can ignore r3. */ |
| if (m4 & 1) { |
| tlb_flush(cs); |
| } else { |
| tlb_flush_all_cpus_synced(cs); |
| } |
| } |
| |
| /* invalidate pte */ |
| void HELPER(ipte)(CPUS390XState *env, uint64_t pto, uint64_t vaddr, |
| uint32_t m4) |
| { |
| CPUState *cs = env_cpu(env); |
| const uintptr_t ra = GETPC(); |
| uint64_t page = vaddr & TARGET_PAGE_MASK; |
| uint64_t pte_addr, pte; |
| |
| /* Compute the page table entry address */ |
| pte_addr = (pto & SEGMENT_ENTRY_ORIGIN); |
| pte_addr += VADDR_PAGE_TX(vaddr) * 8; |
| |
| /* Mark the page table entry as invalid */ |
| pte = cpu_ldq_mmuidx_ra(env, pte_addr, MMU_REAL_IDX, ra); |
| pte |= PAGE_ENTRY_I; |
| cpu_stq_mmuidx_ra(env, pte_addr, pte, MMU_REAL_IDX, ra); |
| |
| /* XXX we exploit the fact that Linux passes the exact virtual |
| address here - it's not obliged to! */ |
| if (m4 & 1) { |
| if (vaddr & ~VADDR_PAGE_TX_MASK) { |
| tlb_flush_page(cs, page); |
| /* XXX 31-bit hack */ |
| tlb_flush_page(cs, page ^ 0x80000000); |
| } else { |
| /* looks like we don't have a valid virtual address */ |
| tlb_flush(cs); |
| } |
| } else { |
| if (vaddr & ~VADDR_PAGE_TX_MASK) { |
| tlb_flush_page_all_cpus_synced(cs, page); |
| /* XXX 31-bit hack */ |
| tlb_flush_page_all_cpus_synced(cs, page ^ 0x80000000); |
| } else { |
| /* looks like we don't have a valid virtual address */ |
| tlb_flush_all_cpus_synced(cs); |
| } |
| } |
| } |
| |
| /* flush local tlb */ |
| void HELPER(ptlb)(CPUS390XState *env) |
| { |
| tlb_flush(env_cpu(env)); |
| } |
| |
| /* flush global tlb */ |
| void HELPER(purge)(CPUS390XState *env) |
| { |
| tlb_flush_all_cpus_synced(env_cpu(env)); |
| } |
| |
| /* load real address */ |
| uint64_t HELPER(lra)(CPUS390XState *env, uint64_t r1, uint64_t addr) |
| { |
| uint64_t asc = env->psw.mask & PSW_MASK_ASC; |
| uint64_t ret, tec; |
| int flags, exc, cc; |
| |
| /* XXX incomplete - has more corner cases */ |
| if (!(env->psw.mask & PSW_MASK_64) && (addr >> 32)) { |
| tcg_s390_program_interrupt(env, PGM_SPECIAL_OP, GETPC()); |
| } |
| |
| exc = mmu_translate(env, addr, MMU_S390_LRA, asc, &ret, &flags, &tec); |
| if (exc) { |
| cc = 3; |
| ret = (r1 & 0xFFFFFFFF00000000ULL) | exc | 0x80000000; |
| } else { |
| cc = 0; |
| ret |= addr & ~TARGET_PAGE_MASK; |
| } |
| |
| env->cc_op = cc; |
| return ret; |
| } |
| #endif |
| |
| /* Execute instruction. This instruction executes an insn modified with |
| the contents of r1. It does not change the executed instruction in memory; |
| it does not change the program counter. |
| |
| Perform this by recording the modified instruction in env->ex_value. |
| This will be noticed by cpu_get_tb_cpu_state and thus tb translation. |
| */ |
| void HELPER(ex)(CPUS390XState *env, uint32_t ilen, uint64_t r1, uint64_t addr) |
| { |
| uint64_t insn; |
| uint8_t opc; |
| |
| /* EXECUTE targets must be at even addresses. */ |
| if (addr & 1) { |
| tcg_s390_program_interrupt(env, PGM_SPECIFICATION, GETPC()); |
| } |
| |
| insn = cpu_lduw_code(env, addr); |
| opc = insn >> 8; |
| |
| /* Or in the contents of R1[56:63]. */ |
| insn |= r1 & 0xff; |
| |
| /* Load the rest of the instruction. */ |
| insn <<= 48; |
| switch (get_ilen(opc)) { |
| case 2: |
| break; |
| case 4: |
| insn |= (uint64_t)cpu_lduw_code(env, addr + 2) << 32; |
| break; |
| case 6: |
| insn |= (uint64_t)(uint32_t)cpu_ldl_code(env, addr + 2) << 16; |
| break; |
| default: |
| g_assert_not_reached(); |
| } |
| |
| /* The very most common cases can be sped up by avoiding a new TB. */ |
| if ((opc & 0xf0) == 0xd0) { |
| typedef uint32_t (*dx_helper)(CPUS390XState *, uint32_t, uint64_t, |
| uint64_t, uintptr_t); |
| static const dx_helper dx[16] = { |
| [0x0] = do_helper_trt_bkwd, |
| [0x2] = do_helper_mvc, |
| [0x4] = do_helper_nc, |
| [0x5] = do_helper_clc, |
| [0x6] = do_helper_oc, |
| [0x7] = do_helper_xc, |
| [0xc] = do_helper_tr, |
| [0xd] = do_helper_trt_fwd, |
| }; |
| dx_helper helper = dx[opc & 0xf]; |
| |
| if (helper) { |
| uint32_t l = extract64(insn, 48, 8); |
| uint32_t b1 = extract64(insn, 44, 4); |
| uint32_t d1 = extract64(insn, 32, 12); |
| uint32_t b2 = extract64(insn, 28, 4); |
| uint32_t d2 = extract64(insn, 16, 12); |
| uint64_t a1 = wrap_address(env, (b1 ? env->regs[b1] : 0) + d1); |
| uint64_t a2 = wrap_address(env, (b2 ? env->regs[b2] : 0) + d2); |
| |
| env->cc_op = helper(env, l, a1, a2, 0); |
| env->psw.addr += ilen; |
| return; |
| } |
| } else if (opc == 0x0a) { |
| env->int_svc_code = extract64(insn, 48, 8); |
| env->int_svc_ilen = ilen; |
| helper_exception(env, EXCP_SVC); |
| g_assert_not_reached(); |
| } |
| |
| /* Record the insn we want to execute as well as the ilen to use |
| during the execution of the target insn. This will also ensure |
| that ex_value is non-zero, which flags that we are in a state |
| that requires such execution. */ |
| env->ex_value = insn | ilen; |
| env->ex_target = addr; |
| } |
| |
| uint32_t HELPER(mvcos)(CPUS390XState *env, uint64_t dest, uint64_t src, |
| uint64_t len) |
| { |
| const uint8_t psw_key = (env->psw.mask & PSW_MASK_KEY) >> PSW_SHIFT_KEY; |
| const uint8_t psw_as = (env->psw.mask & PSW_MASK_ASC) >> PSW_SHIFT_ASC; |
| const uint64_t r0 = env->regs[0]; |
| const uintptr_t ra = GETPC(); |
| uint8_t dest_key, dest_as, dest_k, dest_a; |
| uint8_t src_key, src_as, src_k, src_a; |
| uint64_t val; |
| int cc = 0; |
| |
| HELPER_LOG("%s dest %" PRIx64 ", src %" PRIx64 ", len %" PRIx64 "\n", |
| __func__, dest, src, len); |
| |
| if (!(env->psw.mask & PSW_MASK_DAT)) { |
| tcg_s390_program_interrupt(env, PGM_SPECIAL_OP, ra); |
| } |
| |
| /* OAC (operand access control) for the first operand -> dest */ |
| val = (r0 & 0xffff0000ULL) >> 16; |
| dest_key = (val >> 12) & 0xf; |
| dest_as = (val >> 6) & 0x3; |
| dest_k = (val >> 1) & 0x1; |
| dest_a = val & 0x1; |
| |
| /* OAC (operand access control) for the second operand -> src */ |
| val = (r0 & 0x0000ffffULL); |
| src_key = (val >> 12) & 0xf; |
| src_as = (val >> 6) & 0x3; |
| src_k = (val >> 1) & 0x1; |
| src_a = val & 0x1; |
| |
| if (!dest_k) { |
| dest_key = psw_key; |
| } |
| if (!src_k) { |
| src_key = psw_key; |
| } |
| if (!dest_a) { |
| dest_as = psw_as; |
| } |
| if (!src_a) { |
| src_as = psw_as; |
| } |
| |
| if (dest_a && dest_as == AS_HOME && (env->psw.mask & PSW_MASK_PSTATE)) { |
| tcg_s390_program_interrupt(env, PGM_SPECIAL_OP, ra); |
| } |
| if (!(env->cregs[0] & CR0_SECONDARY) && |
| (dest_as == AS_SECONDARY || src_as == AS_SECONDARY)) { |
| tcg_s390_program_interrupt(env, PGM_SPECIAL_OP, ra); |
| } |
| if (!psw_key_valid(env, dest_key) || !psw_key_valid(env, src_key)) { |
| tcg_s390_program_interrupt(env, PGM_PRIVILEGED, ra); |
| } |
| |
| len = wrap_length32(env, len); |
| if (len > 4096) { |
| cc = 3; |
| len = 4096; |
| } |
| |
| /* FIXME: AR-mode and proper problem state mode (using PSW keys) missing */ |
| if (src_as == AS_ACCREG || dest_as == AS_ACCREG || |
| (env->psw.mask & PSW_MASK_PSTATE)) { |
| qemu_log_mask(LOG_UNIMP, "%s: AR-mode and PSTATE support missing\n", |
| __func__); |
| tcg_s390_program_interrupt(env, PGM_ADDRESSING, ra); |
| } |
| |
| /* FIXME: Access using correct keys and AR-mode */ |
| if (len) { |
| S390Access srca, desta; |
| |
| access_prepare(&srca, env, src, len, MMU_DATA_LOAD, |
| mmu_idx_from_as(src_as), ra); |
| access_prepare(&desta, env, dest, len, MMU_DATA_STORE, |
| mmu_idx_from_as(dest_as), ra); |
| |
| access_memmove(env, &desta, &srca, ra); |
| } |
| |
| return cc; |
| } |
| |
| /* Decode a Unicode character. A return value < 0 indicates success, storing |
| the UTF-32 result into OCHAR and the input length into OLEN. A return |
| value >= 0 indicates failure, and the CC value to be returned. */ |
| typedef int (*decode_unicode_fn)(CPUS390XState *env, uint64_t addr, |
| uint64_t ilen, bool enh_check, uintptr_t ra, |
| uint32_t *ochar, uint32_t *olen); |
| |
| /* Encode a Unicode character. A return value < 0 indicates success, storing |
| the bytes into ADDR and the output length into OLEN. A return value >= 0 |
| indicates failure, and the CC value to be returned. */ |
| typedef int (*encode_unicode_fn)(CPUS390XState *env, uint64_t addr, |
| uint64_t ilen, uintptr_t ra, uint32_t c, |
| uint32_t *olen); |
| |
| static int decode_utf8(CPUS390XState *env, uint64_t addr, uint64_t ilen, |
| bool enh_check, uintptr_t ra, |
| uint32_t *ochar, uint32_t *olen) |
| { |
| uint8_t s0, s1, s2, s3; |
| uint32_t c, l; |
| |
| if (ilen < 1) { |
| return 0; |
| } |
| s0 = cpu_ldub_data_ra(env, addr, ra); |
| if (s0 <= 0x7f) { |
| /* one byte character */ |
| l = 1; |
| c = s0; |
| } else if (s0 <= (enh_check ? 0xc1 : 0xbf)) { |
| /* invalid character */ |
| return 2; |
| } else if (s0 <= 0xdf) { |
| /* two byte character */ |
| l = 2; |
| if (ilen < 2) { |
| return 0; |
| } |
| s1 = cpu_ldub_data_ra(env, addr + 1, ra); |
| c = s0 & 0x1f; |
| c = (c << 6) | (s1 & 0x3f); |
| if (enh_check && (s1 & 0xc0) != 0x80) { |
| return 2; |
| } |
| } else if (s0 <= 0xef) { |
| /* three byte character */ |
| l = 3; |
| if (ilen < 3) { |
| return 0; |
| } |
| s1 = cpu_ldub_data_ra(env, addr + 1, ra); |
| s2 = cpu_ldub_data_ra(env, addr + 2, ra); |
| c = s0 & 0x0f; |
| c = (c << 6) | (s1 & 0x3f); |
| c = (c << 6) | (s2 & 0x3f); |
| /* Fold the byte-by-byte range descriptions in the PoO into |
| tests against the complete value. It disallows encodings |
| that could be smaller, and the UTF-16 surrogates. */ |
| if (enh_check |
| && ((s1 & 0xc0) != 0x80 |
| || (s2 & 0xc0) != 0x80 |
| || c < 0x1000 |
| || (c >= 0xd800 && c <= 0xdfff))) { |
| return 2; |
| } |
| } else if (s0 <= (enh_check ? 0xf4 : 0xf7)) { |
| /* four byte character */ |
| l = 4; |
| if (ilen < 4) { |
| return 0; |
| } |
| s1 = cpu_ldub_data_ra(env, addr + 1, ra); |
| s2 = cpu_ldub_data_ra(env, addr + 2, ra); |
| s3 = cpu_ldub_data_ra(env, addr + 3, ra); |
| c = s0 & 0x07; |
| c = (c << 6) | (s1 & 0x3f); |
| c = (c << 6) | (s2 & 0x3f); |
| c = (c << 6) | (s3 & 0x3f); |
| /* See above. */ |
| if (enh_check |
| && ((s1 & 0xc0) != 0x80 |
| || (s2 & 0xc0) != 0x80 |
| || (s3 & 0xc0) != 0x80 |
| || c < 0x010000 |
| || c > 0x10ffff)) { |
| return 2; |
| } |
| } else { |
| /* invalid character */ |
| return 2; |
| } |
| |
| *ochar = c; |
| *olen = l; |
| return -1; |
| } |
| |
| static int decode_utf16(CPUS390XState *env, uint64_t addr, uint64_t ilen, |
| bool enh_check, uintptr_t ra, |
| uint32_t *ochar, uint32_t *olen) |
| { |
| uint16_t s0, s1; |
| uint32_t c, l; |
| |
| if (ilen < 2) { |
| return 0; |
| } |
| s0 = cpu_lduw_data_ra(env, addr, ra); |
| if ((s0 & 0xfc00) != 0xd800) { |
| /* one word character */ |
| l = 2; |
| c = s0; |
| } else { |
| /* two word character */ |
| l = 4; |
| if (ilen < 4) { |
| return 0; |
| } |
| s1 = cpu_lduw_data_ra(env, addr + 2, ra); |
| c = extract32(s0, 6, 4) + 1; |
| c = (c << 6) | (s0 & 0x3f); |
| c = (c << 10) | (s1 & 0x3ff); |
| if (enh_check && (s1 & 0xfc00) != 0xdc00) { |
| /* invalid surrogate character */ |
| return 2; |
| } |
| } |
| |
| *ochar = c; |
| *olen = l; |
| return -1; |
| } |
| |
| static int decode_utf32(CPUS390XState *env, uint64_t addr, uint64_t ilen, |
| bool enh_check, uintptr_t ra, |
| uint32_t *ochar, uint32_t *olen) |
| { |
| uint32_t c; |
| |
| if (ilen < 4) { |
| return 0; |
| } |
| c = cpu_ldl_data_ra(env, addr, ra); |
| if ((c >= 0xd800 && c <= 0xdbff) || c > 0x10ffff) { |
| /* invalid unicode character */ |
| return 2; |
| } |
| |
| *ochar = c; |
| *olen = 4; |
| return -1; |
| } |
| |
| static int encode_utf8(CPUS390XState *env, uint64_t addr, uint64_t ilen, |
| uintptr_t ra, uint32_t c, uint32_t *olen) |
| { |
| uint8_t d[4]; |
| uint32_t l, i; |
| |
| if (c <= 0x7f) { |
| /* one byte character */ |
| l = 1; |
| d[0] = c; |
| } else if (c <= 0x7ff) { |
| /* two byte character */ |
| l = 2; |
| d[1] = 0x80 | extract32(c, 0, 6); |
| d[0] = 0xc0 | extract32(c, 6, 5); |
| } else if (c <= 0xffff) { |
| /* three byte character */ |
| l = 3; |
| d[2] = 0x80 | extract32(c, 0, 6); |
| d[1] = 0x80 | extract32(c, 6, 6); |
| d[0] = 0xe0 | extract32(c, 12, 4); |
| } else { |
| /* four byte character */ |
| l = 4; |
| d[3] = 0x80 | extract32(c, 0, 6); |
| d[2] = 0x80 | extract32(c, 6, 6); |
| d[1] = 0x80 | extract32(c, 12, 6); |
| d[0] = 0xf0 | extract32(c, 18, 3); |
| } |
| |
| if (ilen < l) { |
| return 1; |
| } |
| for (i = 0; i < l; ++i) { |
| cpu_stb_data_ra(env, addr + i, d[i], ra); |
| } |
| |
| *olen = l; |
| return -1; |
| } |
| |
| static int encode_utf16(CPUS390XState *env, uint64_t addr, uint64_t ilen, |
| uintptr_t ra, uint32_t c, uint32_t *olen) |
| { |
| uint16_t d0, d1; |
| |
| if (c <= 0xffff) { |
| /* one word character */ |
| if (ilen < 2) { |
| return 1; |
| } |
| cpu_stw_data_ra(env, addr, c, ra); |
| *olen = 2; |
| } else { |
| /* two word character */ |
| if (ilen < 4) { |
| return 1; |
| } |
| d1 = 0xdc00 | extract32(c, 0, 10); |
| d0 = 0xd800 | extract32(c, 10, 6); |
| d0 = deposit32(d0, 6, 4, extract32(c, 16, 5) - 1); |
| cpu_stw_data_ra(env, addr + 0, d0, ra); |
| cpu_stw_data_ra(env, addr + 2, d1, ra); |
| *olen = 4; |
| } |
| |
| return -1; |
| } |
| |
| static int encode_utf32(CPUS390XState *env, uint64_t addr, uint64_t ilen, |
| uintptr_t ra, uint32_t c, uint32_t *olen) |
| { |
| if (ilen < 4) { |
| return 1; |
| } |
| cpu_stl_data_ra(env, addr, c, ra); |
| *olen = 4; |
| return -1; |
| } |
| |
| static inline uint32_t convert_unicode(CPUS390XState *env, uint32_t r1, |
| uint32_t r2, uint32_t m3, uintptr_t ra, |
| decode_unicode_fn decode, |
| encode_unicode_fn encode) |
| { |
| uint64_t dst = get_address(env, r1); |
| uint64_t dlen = get_length(env, r1 + 1); |
| uint64_t src = get_address(env, r2); |
| uint64_t slen = get_length(env, r2 + 1); |
| bool enh_check = m3 & 1; |
| int cc, i; |
| |
| /* Lest we fail to service interrupts in a timely manner, limit the |
| amount of work we're willing to do. For now, let's cap at 256. */ |
| for (i = 0; i < 256; ++i) { |
| uint32_t c, ilen, olen; |
| |
| cc = decode(env, src, slen, enh_check, ra, &c, &ilen); |
| if (unlikely(cc >= 0)) { |
| break; |
| } |
| cc = encode(env, dst, dlen, ra, c, &olen); |
| if (unlikely(cc >= 0)) { |
| break; |
| } |
| |
| src += ilen; |
| slen -= ilen; |
| dst += olen; |
| dlen -= olen; |
| cc = 3; |
| } |
| |
| set_address(env, r1, dst); |
| set_length(env, r1 + 1, dlen); |
| set_address(env, r2, src); |
| set_length(env, r2 + 1, slen); |
| |
| return cc; |
| } |
| |
| uint32_t HELPER(cu12)(CPUS390XState *env, uint32_t r1, uint32_t r2, uint32_t m3) |
| { |
| return convert_unicode(env, r1, r2, m3, GETPC(), |
| decode_utf8, encode_utf16); |
| } |
| |
| uint32_t HELPER(cu14)(CPUS390XState *env, uint32_t r1, uint32_t r2, uint32_t m3) |
| { |
| return convert_unicode(env, r1, r2, m3, GETPC(), |
| decode_utf8, encode_utf32); |
| } |
| |
| uint32_t HELPER(cu21)(CPUS390XState *env, uint32_t r1, uint32_t r2, uint32_t m3) |
| { |
| return convert_unicode(env, r1, r2, m3, GETPC(), |
| decode_utf16, encode_utf8); |
| } |
| |
| uint32_t HELPER(cu24)(CPUS390XState *env, uint32_t r1, uint32_t r2, uint32_t m3) |
| { |
| return convert_unicode(env, r1, r2, m3, GETPC(), |
| decode_utf16, encode_utf32); |
| } |
| |
| uint32_t HELPER(cu41)(CPUS390XState *env, uint32_t r1, uint32_t r2, uint32_t m3) |
| { |
| return convert_unicode(env, r1, r2, m3, GETPC(), |
| decode_utf32, encode_utf8); |
| } |
| |
| uint32_t HELPER(cu42)(CPUS390XState *env, uint32_t r1, uint32_t r2, uint32_t m3) |
| { |
| return convert_unicode(env, r1, r2, m3, GETPC(), |
| decode_utf32, encode_utf16); |
| } |
| |
| void probe_write_access(CPUS390XState *env, uint64_t addr, uint64_t len, |
| uintptr_t ra) |
| { |
| const int mmu_idx = s390x_env_mmu_index(env, false); |
| |
| /* test the actual access, not just any access to the page due to LAP */ |
| while (len) { |
| const uint64_t pagelen = -(addr | TARGET_PAGE_MASK); |
| const uint64_t curlen = MIN(pagelen, len); |
| |
| probe_write(env, addr, curlen, mmu_idx, ra); |
| addr = wrap_address(env, addr + curlen); |
| len -= curlen; |
| } |
| } |
| |
| void HELPER(probe_write_access)(CPUS390XState *env, uint64_t addr, uint64_t len) |
| { |
| probe_write_access(env, addr, len, GETPC()); |
| } |