diff --git a/target-s390x/helper.h b/target-s390x/helper.h
index bb1f740..2bd4105 100644
--- a/target-s390x/helper.h
+++ b/target-s390x/helper.h
@@ -118,7 +118,7 @@ DEF_HELPER_FLAGS_3(ipte, TCG_CALL_CONST, void, env, i64, i64)
 DEF_HELPER_FLAGS_1(ptlb, TCG_CALL_CONST, void, env)
 DEF_HELPER_2(lra, i64, env, i64)
 DEF_HELPER_3(stura, void, env, i64, i32)
-DEF_HELPER_3(cksm, void, env, i32, i32)
+DEF_HELPER_4(cksm, i64, env, i64, i64, i64)
 DEF_HELPER_FLAGS_2(sfpc, TCG_CALL_CONST, void, env, i64)
 
 DEF_HELPER_FLAGS_5(calc_cc, TCG_CALL_PURE|TCG_CALL_CONST,
diff --git a/target-s390x/insn-data.def b/target-s390x/insn-data.def
index 2eb248f..eec99fe 100644
--- a/target-s390x/insn-data.def
+++ b/target-s390x/insn-data.def
@@ -91,6 +91,9 @@
     C(0xa706, BRCT,    RI_b,  Z,   0, 0, 0, 0, bct32, 0)
     C(0xa707, BRCTG,   RI_b,  Z,   0, 0, 0, 0, bct64, 0)
 
+/* CHECKSUM */
+    C(0xb241, CKSM,    RRE,   Z,   r1_o, ra2, new, r1_32, cksm, 0)
+
 /* COMPARE */
     C(0x1900, CR,      RR_a,  Z,   r1_o, r2_o, 0, 0, 0, cmps32)
     C(0x5900, C,       RX_a,  Z,   r1_o, m2_32s, 0, 0, 0, cmps32)
diff --git a/target-s390x/mem_helper.c b/target-s390x/mem_helper.c
index dfdb2c7..5f70aad 100644
--- a/target-s390x/mem_helper.c
+++ b/target-s390x/mem_helper.c
@@ -753,23 +753,22 @@ uint32_t HELPER(clcle)(CPUS390XState *env, uint32_t r1, uint64_t a2,
 }
 
 /* checksum */
-void HELPER(cksm)(CPUS390XState *env, uint32_t r1, uint32_t r2)
+uint64_t HELPER(cksm)(CPUS390XState *env, uint64_t r1,
+                      uint64_t src, uint64_t src_len)
 {
-    uint64_t src = get_address_31fix(env, r2);
-    uint64_t src_len = env->regs[(r2 + 1) & 15];
-    uint64_t cksm = (uint32_t)env->regs[r1];
+    uint64_t max_len, len;
+    uint64_t cksm = (uint32_t)r1;
 
-    while (src_len >= 4) {
-        cksm += cpu_ldl_data(env, src);
+    /* Lest we fail to service interrupts in a timely manner, limit the
+       amount of work we're willing to do.  For now, lets cap at 8k.  */
+    max_len = (src_len > 0x2000 ? 0x2000 : src_len);
 
-        /* move to next word */
-        src_len -= 4;
-        src += 4;
+    /* Process full words as available.  */
+    for (len = 0; len + 4 <= max_len; len += 4, src += 4) {
+        cksm += (uint32_t)cpu_ldl_data(env, src);
     }
 
-    switch (src_len) {
-    case 0:
-        break;
+    switch (max_len - len) {
     case 1:
         cksm += cpu_ldub_data(env, src) << 24;
         break;
@@ -782,13 +781,18 @@ void HELPER(cksm)(CPUS390XState *env, uint32_t r1, uint32_t r2)
         break;
     }
 
-    /* indicate we've processed everything */
-    env->regs[r2] = src + src_len;
-    env->regs[(r2 + 1) & 15] = 0;
+    /* 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);
 
-    /* store result */
-    env->regs[r1] = (env->regs[r1] & 0xffffffff00000000ULL) |
-        ((uint32_t)cksm + (cksm >> 32));
+    /* Return both cksm and processed length.  */
+    env->retxl = cksm;
+    return len;
 }
 
 void HELPER(unpk)(CPUS390XState *env, uint32_t len, uint64_t dest,
diff --git a/target-s390x/translate.c b/target-s390x/translate.c
index 34615cb..16eabba 100644
--- a/target-s390x/translate.c
+++ b/target-s390x/translate.c
@@ -1031,15 +1031,6 @@ static void disas_b2(DisasContext *s, int op, uint32_t insn)
     LOG_DISAS("disas_b2: op 0x%x r1 %d r2 %d\n", op, r1, r2);
 
     switch (op) {
-    case 0x41: /* CKSM    R1,R2     [RRE] */
-        tmp32_1 = tcg_const_i32(r1);
-        tmp32_2 = tcg_const_i32(r2);
-        potential_page_fault(s);
-        gen_helper_cksm(cpu_env, tmp32_1, tmp32_2);
-        tcg_temp_free_i32(tmp32_1);
-        tcg_temp_free_i32(tmp32_2);
-        gen_op_movi_cc(s, 0);
-        break;
     case 0x4e: /* SAR     R1,R2     [RRE] */
         tmp32_1 = load_reg32(r2);
         tcg_gen_st_i32(tmp32_1, cpu_env, offsetof(CPUS390XState, aregs[r1]));
@@ -2000,6 +1991,23 @@ static ExitStatus op_cxgb(DisasContext *s, DisasOps *o)
     return NO_EXIT;
 }
 
+static ExitStatus op_cksm(DisasContext *s, DisasOps *o)
+{
+    int r2 = get_field(s->fields, r2);
+    TCGv_i64 len = tcg_temp_new_i64();
+
+    potential_page_fault(s);
+    gen_helper_cksm(len, cpu_env, o->in1, o->in2, regs[r2 + 1]);
+    set_cc_static(s);
+    return_low128(o->out);
+
+    tcg_gen_add_i64(regs[r2], regs[r2], len);
+    tcg_gen_sub_i64(regs[r2 + 1], regs[r2 + 1], len);
+    tcg_temp_free_i64(len);
+
+    return NO_EXIT;
+}
+
 static ExitStatus op_clc(DisasContext *s, DisasOps *o)
 {
     int l = get_field(s->fields, l1);
@@ -3771,6 +3779,11 @@ static void in2_x2_o(DisasContext *s, DisasFields *f, DisasOps *o)
     o->g_in1 = o->g_in2 = true;
 }
 
+static void in2_ra2(DisasContext *s, DisasFields *f, DisasOps *o)
+{
+    o->in2 = get_address(s, 0, get_field(f, r2), 0);
+}
+
 static void in2_a2(DisasContext *s, DisasFields *f, DisasOps *o)
 {
     int x2 = have_field(f, x2) ? get_field(f, x2) : 0;
