diff --git a/target/s390x/mem_helper.c b/target/s390x/mem_helper.c index 223312a4b194fb5c73d9da8038a5ccf5b33f25a0..58ab2e48e34110ad82c7b73fe213f8f8d297c51c 100644 --- a/target/s390x/mem_helper.c +++ b/target/s390x/mem_helper.c @@ -798,19 +798,51 @@ uint32_t HELPER(mvcl)(CPUS390XState *env, uint32_t r1, uint32_t r2) uint64_t srclen = env->regs[r2 + 1] & 0xffffff; uint64_t src = get_address(env, r2); uint8_t pad = env->regs[r2 + 1] >> 24; - uint32_t cc; + 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 = do_mvcl(env, &dest, &destlen, &src, &srclen, pad, 1, ra); + cc = 2; } - env->regs[r1 + 1] = deposit64(env->regs[r1 + 1], 0, 24, destlen); - env->regs[r2 + 1] = deposit64(env->regs[r2 + 1], 0, 24, srclen); - set_address_zero(env, r1, dest); - set_address_zero(env, r2, src); + /* 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) { + fast_memset(env, dest, pad, cur_len, ra); + } else { + cur_len = MIN(MIN(srclen, -(src | TARGET_PAGE_MASK)), cur_len); + + fast_memmove(env, dest, src, cur_len, 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); + + /* TODO: Deliver interrupts. */ + } return cc; }