On 14.06.2017 15:38, David Hildenbrand wrote: > This adds support for the MOVE WITH OPTIONAL SPECIFICATIONS (MVCOS) > instruction. Allow to enable it for the qemu cpu model using > > qemu-system-s390x ... -cpu qemu,mvcos=on ... > > This allows to boot linux kernel that uses it for uacccess. > > We are missing (as for most other part) low address protection checks, > PSW key / storage key checks and support for AR-mode. > > We fake an ADDRESSING exception when called from problem state (which > seems to rely on PSW key checks to be in place) and if AR-mode is used. > user mode will always see a PRIVILEDGED exception. > > This patch is based on an original patch by Miroslav Benes (thanks!). > > Signed-off-by: David Hildenbrand <da...@redhat.com> > --- [...] > index 80caab9..5f76dae 100644 > --- a/target/s390x/mem_helper.c > +++ b/target/s390x/mem_helper.c > @@ -110,6 +110,20 @@ static inline void cpu_stsize_data_ra(CPUS390XState > *env, uint64_t addr, > } > } > > +static inline uint64_t wrap_address(CPUS390XState *env, uint64_t a) > +{ > + if (!(env->psw.mask & PSW_MASK_64)) { > + if (!(env->psw.mask & PSW_MASK_32)) { > + /* 24-Bit mode */ > + a &= 0x00ffffff; > + } else { > + /* 31-Bit mode */ > + a &= 0x7fffffff; > + } > + } > + return a; > +} > + > static void fast_memset(CPUS390XState *env, uint64_t dest, uint8_t byte, > uint32_t l, uintptr_t ra) > { > @@ -118,7 +132,7 @@ static void fast_memset(CPUS390XState *env, uint64_t > dest, uint8_t byte, > while (l > 0) { > void *p = tlb_vaddr_to_host(env, dest, MMU_DATA_STORE, mmu_idx); > if (p) { > - /* Access to the whole page in write mode granted. */ > + /* Access to the whole page in write mode granted */
Unrelated change ;-) > uint32_t l_adj = adj_len_to_page(l, dest); > memset(p, byte, l_adj); > dest += l_adj; > @@ -133,6 +147,68 @@ static void fast_memset(CPUS390XState *env, uint64_t > dest, uint8_t byte, > } > } > > +#ifndef CONFIG_USER_ONLY > +static void fast_memmove_idx(CPUS390XState *env, uint64_t dest, uint64_t src, > + uint32_t len, int dest_idx, int src_idx, > + uintptr_t ra) > +{ > + TCGMemOpIdx oi_dest = make_memop_idx(MO_UB, dest_idx); > + TCGMemOpIdx oi_src = make_memop_idx(MO_UB, src_idx); > + uint32_t len_adj; > + void *src_p; > + void *dest_p; > + uint8_t x; > + > + while (len > 0) { > + src = wrap_address(env, src); > + dest = wrap_address(env, dest); > + src_p = tlb_vaddr_to_host(env, src, MMU_DATA_LOAD, src_idx); > + dest_p = tlb_vaddr_to_host(env, dest, MMU_DATA_STORE, dest_idx); > + > + if (src_p && dest_p) { > + /* Access to both whole pages granted. */ > + len_adj = adj_len_to_page(adj_len_to_page(len, src), dest); > + memmove(dest_p, src_p, len_adj); > + } else { > + /* We failed to get access to one or both whole pages. The next > + read or write access will likely fill the QEMU TLB for the > + next iteration. */ > + len_adj = 1; > + x = helper_ret_ldub_mmu(env, src, oi_src, ra); > + helper_ret_stb_mmu(env, dest, x, oi_dest, ra); > + } > + src += len_adj; > + dest += len_adj; > + len -= len_adj; > + } > +} The code is pretty similar to fast_memmove() ... I wonder whether it makes sense to change fast_memmove() to call fast_memmove_idx(), too? Something like: static void fast_memmove(CPUS390XState *env, uint64_t dest, uint64_t src, uint32_t l, uintptr_t ra) { int mmu_idx = cpu_mmu_index(env, false); fast_memmove_idx(env, dest, src, l, mmu_idx, mmu_idx, ra); } Or could that result in some speed penalty here? Anyway, this should likely be done in a separate patch. > +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; > + } > + /* FIXME AS_ACCREG */ > + assert(false); > + return -1; > +} Hmm, maybe it would make sense to change the MMU_*_IDX defines to match the AS value directly? > +static void fast_memmove_as(CPUS390XState *env, uint64_t dest, uint64_t src, > + uint32_t len, uint8_t dest_as, uint8_t src_as, > + uintptr_t ra) > +{ > + int src_idx = mmu_idx_from_as(src_as); > + int dest_idx = mmu_idx_from_as(dest_as); > + > + fast_memmove_idx(env, dest, src, len, dest_idx, src_idx, ra); > +} > +#endif > + > static void fast_memmove(CPUS390XState *env, uint64_t dest, uint64_t src, > uint32_t l, uintptr_t ra) > { > @@ -408,20 +484,6 @@ uint32_t HELPER(clm)(CPUS390XState *env, uint32_t r1, > uint32_t mask, > return cc; > } > > -static inline uint64_t wrap_address(CPUS390XState *env, uint64_t a) > -{ > - if (!(env->psw.mask & PSW_MASK_64)) { > - if (!(env->psw.mask & PSW_MASK_32)) { > - /* 24-Bit mode */ > - a &= 0x00ffffff; > - } else { > - /* 31-Bit mode */ > - a &= 0x7fffffff; > - } > - } > - return a; > -} > - > static inline uint64_t get_address(CPUS390XState *env, int reg) > { > return wrap_address(env, env->regs[reg]); > @@ -1789,3 +1851,91 @@ void HELPER(ex)(CPUS390XState *env, uint32_t ilen, > uint64_t r1, uint64_t addr) > that requires such execution. */ > env->ex_value = insn | ilen; > } > + > +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(); > + CPUState *cs = CPU(s390_env_get_cpu(env)); > + 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)) { > + cpu_restore_state(cs, ra); > + program_interrupt(env, PGM_SPECIAL_OP, 6); > + } > + > + /* 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; > + } Not sure, but maybe it's nicer to write all this in a more compact way? src_k = (val >> 1) & 0x1; src_key = srk_k ? (val >> 12) & 0xf : psw_key; src_a = val & 0x1; src_as = src_a ? (val >> 6) & 0x3 : psw_as; ... etc ... > + if (dest_a && dest_as == AS_HOME && (env->psw.mask & PSW_MASK_PSTATE)) { > + cpu_restore_state(cs, ra); > + program_interrupt(env, PGM_SPECIAL_OP, 6); > + } > + if (!(env->cregs[0] & CR0_SECONDARY) && > + (dest_as == AS_SECONDARY || src_as == AS_SECONDARY)) { > + cpu_restore_state(cs, ra); > + program_interrupt(env, PGM_SPECIAL_OP, 6); > + } > + if (!psw_key_valid(env, dest_key) || !psw_key_valid(env, src_key)) { > + cpu_restore_state(cs, ra); > + program_interrupt(env, PGM_PRIVILEGED, 6); > + } > + > + 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__); > + cpu_restore_state(cs, ra); > + program_interrupt(env, PGM_ADDRESSING, 6); > + } > + > + /* FIXME: a) LAP > + * b) Access using correct keys > + * c) AR-mode > + */ > +#ifndef CONFIG_USER_ONLY > + /* psw keys are never valid in user mode, we will never reach this */ > + fast_memmove_as(env, dest, src, len, dest_as, src_as, ra); > +#endif > + > + return cc; > +} Apart from the bikeshed-painting-worthy cosmetic nits, this looks fine now to me. Thanks for tackling this! Thomas