return RESPST_CHK_RKEY;
 }
 
+/* if the reth length field is zero we can assume nothing
+ * about the rkey value and should not validate or use it.
+ * Instead set qp->resp.rkey to 0 which is an invalid rkey
+ * value since the minimum index part is 1.
+ */
 static void qp_resp_from_reth(struct rxe_qp *qp, struct rxe_pkt_info *pkt)
 {
+       unsigned int length = reth_len(pkt);
+
        qp->resp.va = reth_va(pkt);
        qp->resp.offset = 0;
-       qp->resp.rkey = reth_rkey(pkt);
-       qp->resp.resid = reth_len(pkt);
-       qp->resp.length = reth_len(pkt);
+       qp->resp.resid = length;
+       qp->resp.length = length;
+       if (pkt->mask & RXE_READ_OR_WRITE_MASK && length == 0)
+               qp->resp.rkey = 0;
+       else
+               qp->resp.rkey = reth_rkey(pkt);
 }
 
 static void qp_resp_from_atmeth(struct rxe_qp *qp, struct rxe_pkt_info *pkt)
        qp->resp.resid = sizeof(u64);
 }
 
+/* resolve the packet rkey to qp->resp.mr or set qp->resp.mr to NULL
+ * if an invalid rkey is received or the rdma length is zero. For middle
+ * or last packets use the stored value of mr.
+ */
 static enum resp_states check_rkey(struct rxe_qp *qp,
                                   struct rxe_pkt_info *pkt)
 {
                return RESPST_EXECUTE;
        }
 
-       /* A zero-byte op is not required to set an addr or rkey. See C9-88 */
+       /* A zero-byte read or write op is not required to
+        * set an addr or rkey. See C9-88
+        */
        if ((pkt->mask & RXE_READ_OR_WRITE_MASK) &&
-           (pkt->mask & RXE_RETH_MASK) &&
-           reth_len(pkt) == 0) {
+           (pkt->mask & RXE_RETH_MASK) && reth_len(pkt) == 0) {
+               qp->resp.mr = NULL;
                return RESPST_EXECUTE;
        }
 
        return RESPST_EXECUTE;
 
 err:
+       qp->resp.mr = NULL;
        if (mr)
                rxe_put(mr);
        if (mw)
        }
 
        if (res->state == rdatm_res_state_new) {
-               if (!res->replay) {
+               if (!res->replay || qp->resp.length == 0) {
+                       /* if length == 0 mr will be NULL (is ok)
+                        * otherwise qp->resp.mr holds a ref on mr
+                        * which we transfer to mr and drop below.
+                        */
                        mr = qp->resp.mr;
                        qp->resp.mr = NULL;
                } else {
                else
                        opcode = IB_OPCODE_RC_RDMA_READ_RESPONSE_FIRST;
        } else {
+               /* re-lookup mr from rkey on all later packets.
+                * length will be non-zero. This can fail if someone
+                * modifies or destroys the mr since the first packet.
+                */
                mr = rxe_recheck_mr(qp, res->read.rkey);
                if (!mr)
                        return RESPST_ERR_RKEY_VIOLATION;
        skb = prepare_ack_packet(qp, &ack_pkt, opcode, payload,
                                 res->cur_psn, AETH_ACK_UNLIMITED);
        if (!skb) {
-               if (mr)
-                       rxe_put(mr);
-               return RESPST_ERR_RNR;
+               state = RESPST_ERR_RNR;
+               goto err_out;
        }
 
        err = rxe_mr_copy(mr, res->read.va, payload_addr(&ack_pkt),
                          payload, RXE_FROM_MR_OBJ);
-       if (mr)
-               rxe_put(mr);
        if (err) {
                kfree_skb(skb);
-               return RESPST_ERR_RKEY_VIOLATION;
+               state = RESPST_ERR_RKEY_VIOLATION;
+               goto err_out;
        }
 
        if (bth_pad(&ack_pkt)) {
                memset(pad, 0, bth_pad(&ack_pkt));
        }
 
+       /* rxe_xmit_packet always consumes the skb */
        err = rxe_xmit_packet(qp, &ack_pkt, skb);
-       if (err)
-               return RESPST_ERR_RNR;
+       if (err) {
+               state = RESPST_ERR_RNR;
+               goto err_out;
+       }
 
        res->read.va += payload;
        res->read.resid -= payload;
                state = RESPST_CLEANUP;
        }
 
+err_out:
+       if (mr)
+               rxe_put(mr);
        return state;
 }