/* * Copyright (c) 2005-2006 Network Appliance, Inc. All rights reserved. * * This software is available to you under a choice of one of two * licenses. You may choose to be licensed under the terms of the GNU * General Public License (GPL) Version 2, available from the file * COPYING in the main directory of this source tree, or the BSD-type * license below: * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * Neither the name of the Network Appliance, Inc. nor the names of * its contributors may be used to endorse or promote products * derived from this software without specific prior written * permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * Author: Tom Tucker <tom@opengridcomputing.com> */ #include <linux/sunrpc/xdr.h> #include <linux/sunrpc/debug.h> #include <asm/unaligned.h> #include <linux/sunrpc/rpc_rdma.h> #include <linux/sunrpc/svc_rdma.h> #define RPCDBG_FACILITY RPCDBG_SVCXPRT /* * Decodes a read chunk list. The expected format is as follows: * descrim : xdr_one * position : u32 offset into XDR stream * handle : u32 RKEY * . . . * end-of-list: xdr_zero */ static u32 *decode_read_list(u32 *va, u32 *vaend) { struct rpcrdma_read_chunk *ch = (struct rpcrdma_read_chunk *)va; while (ch->rc_discrim != xdr_zero) { u64 ch_offset; if (((unsigned long)ch + sizeof(struct rpcrdma_read_chunk)) > (unsigned long)vaend) { dprintk("svcrdma: vaend=%p, ch=%p\n", vaend, ch); return NULL; } ch->rc_discrim = ntohl(ch->rc_discrim); ch->rc_position = ntohl(ch->rc_position); ch->rc_target.rs_handle = ntohl(ch->rc_target.rs_handle); ch->rc_target.rs_length = ntohl(ch->rc_target.rs_length); va = (u32 *)&ch->rc_target.rs_offset; xdr_decode_hyper(va, &ch_offset); put_unaligned(ch_offset, (u64 *)va); ch++; } return (u32 *)&ch->rc_position; } /* * Determine number of chunks and total bytes in chunk list. The chunk * list has already been verified to fit within the RPCRDMA header. */ void svc_rdma_rcl_chunk_counts(struct rpcrdma_read_chunk *ch, int *ch_count, int *byte_count) { /* compute the number of bytes represented by read chunks */ *byte_count = 0; *ch_count = 0; for (; ch->rc_discrim != 0; ch++) { *byte_count = *byte_count + ch->rc_target.rs_length; *ch_count = *ch_count + 1; } } /* * Decodes a write chunk list. The expected format is as follows: * descrim : xdr_one * nchunks : <count> * handle : u32 RKEY ---+ * length : u32 <len of segment> | * offset : remove va + <count> * . . . | * ---+ */ static u32 *decode_write_list(u32 *va, u32 *vaend) { int ch_no; struct rpcrdma_write_array *ary = (struct rpcrdma_write_array *)va; /* Check for not write-array */ if (ary->wc_discrim == xdr_zero) return (u32 *)&ary->wc_nchunks; if ((unsigned long)ary + sizeof(struct rpcrdma_write_array) > (unsigned long)vaend) { dprintk("svcrdma: ary=%p, vaend=%p\n", ary, vaend); return NULL; } ary->wc_discrim = ntohl(ary->wc_discrim); ary->wc_nchunks = ntohl(ary->wc_nchunks); if (((unsigned long)&ary->wc_array[0] + (sizeof(struct rpcrdma_write_chunk) * ary->wc_nchunks)) > (unsigned long)vaend) { dprintk("svcrdma: ary=%p, wc_nchunks=%d, vaend=%p\n", ary, ary->wc_nchunks, vaend); return NULL; } for (ch_no = 0; ch_no < ary->wc_nchunks; ch_no++) { u64 ch_offset; ary->wc_array[ch_no].wc_target.rs_handle = ntohl(ary->wc_array[ch_no].wc_target.rs_handle); ary->wc_array[ch_no].wc_target.rs_length = ntohl(ary->wc_array[ch_no].wc_target.rs_length); va = (u32 *)&ary->wc_array[ch_no].wc_target.rs_offset; xdr_decode_hyper(va, &ch_offset); put_unaligned(ch_offset, (u64 *)va); } /* * rs_length is the 2nd 4B field in wc_target and taking its * address skips the list terminator */ return (u32 *)&ary->wc_array[ch_no].wc_target.rs_length; } static u32 *decode_reply_array(u32 *va, u32 *vaend) { int ch_no; struct rpcrdma_write_array *ary = (struct rpcrdma_write_array *)va; /* Check for no reply-array */ if (ary->wc_discrim == xdr_zero) return (u32 *)&ary->wc_nchunks; if ((unsigned long)ary + sizeof(struct rpcrdma_write_array) > (unsigned long)vaend) { dprintk("svcrdma: ary=%p, vaend=%p\n", ary, vaend); return NULL; } ary->wc_discrim = ntohl(ary->wc_discrim); ary->wc_nchunks = ntohl(ary->wc_nchunks); if (((unsigned long)&ary->wc_array[0] + (sizeof(struct rpcrdma_write_chunk) * ary->wc_nchunks)) > (unsigned long)vaend) { dprintk("svcrdma: ary=%p, wc_nchunks=%d, vaend=%p\n", ary, ary->wc_nchunks, vaend); return NULL; } for (ch_no = 0; ch_no < ary->wc_nchunks; ch_no++) { u64 ch_offset; ary->wc_array[ch_no].wc_target.rs_handle = ntohl(ary->wc_array[ch_no].wc_target.rs_handle); ary->wc_array[ch_no].wc_target.rs_length = ntohl(ary->wc_array[ch_no].wc_target.rs_length); va = (u32 *)&ary->wc_array[ch_no].wc_target.rs_offset; xdr_decode_hyper(va, &ch_offset); put_unaligned(ch_offset, (u64 *)va); } return (u32 *)&ary->wc_array[ch_no]; } int svc_rdma_xdr_decode_req(struct rpcrdma_msg **rdma_req, struct svc_rqst *rqstp) { struct rpcrdma_msg *rmsgp = NULL; u32 *va; u32 *vaend; u32 hdr_len; rmsgp = (struct rpcrdma_msg *)rqstp->rq_arg.head[0].iov_base; /* Verify that there's enough bytes for header + something */ if (rqstp->rq_arg.len <= RPCRDMA_HDRLEN_MIN) { dprintk("svcrdma: header too short = %d\n", rqstp->rq_arg.len); return -EINVAL; } /* Decode the header */ rmsgp->rm_xid = ntohl(rmsgp->rm_xid); rmsgp->rm_vers = ntohl(rmsgp->rm_vers); rmsgp->rm_credit = ntohl(rmsgp->rm_credit); rmsgp->rm_type = ntohl(rmsgp->rm_type); if (rmsgp->rm_vers != RPCRDMA_VERSION) return -ENOSYS; /* Pull in the extra for the padded case and bump our pointer */ if (rmsgp->rm_type == RDMA_MSGP) { int hdrlen; rmsgp->rm_body.rm_padded.rm_align = ntohl(rmsgp->rm_body.rm_padded.rm_align); rmsgp->rm_body.rm_padded.rm_thresh = ntohl(rmsgp->rm_body.rm_padded.rm_thresh); va = &rmsgp->rm_body.rm_padded.rm_pempty[4]; rqstp->rq_arg.head[0].iov_base = va; hdrlen = (u32)((unsigned long)va - (unsigned long)rmsgp); rqstp->rq_arg.head[0].iov_len -= hdrlen; if (hdrlen > rqstp->rq_arg.len) return -EINVAL; return hdrlen; } /* The chunk list may contain either a read chunk list or a write * chunk list and a reply chunk list. */ va = &rmsgp->rm_body.rm_chunks[0]; vaend = (u32 *)((unsigned long)rmsgp + rqstp->rq_arg.len); va = decode_read_list(va, vaend); if (!va) return -EINVAL; va = decode_write_list(va, vaend); if (!va) return -EINVAL; va = decode_reply_array(va, vaend); if (!va) return -EINVAL; rqstp->rq_arg.head[0].iov_base = va; hdr_len = (unsigned long)va - (unsigned long)rmsgp; rqstp->rq_arg.head[0].iov_len -= hdr_len; *rdma_req = rmsgp; return hdr_len; } int svc_rdma_xdr_decode_deferred_req(struct svc_rqst *rqstp) { struct rpcrdma_msg *rmsgp = NULL; struct rpcrdma_read_chunk *ch; struct rpcrdma_write_array *ary; u32 *va; u32 hdrlen; dprintk("svcrdma: processing deferred RDMA header on rqstp=%p\n", rqstp); rmsgp = (struct rpcrdma_msg *)rqstp->rq_arg.head[0].iov_base; /* Pull in the extra for the padded case and bump our pointer */ if (rmsgp->rm_type == RDMA_MSGP) { va = &rmsgp->rm_body.rm_padded.rm_pempty[4]; rqstp->rq_arg.head[0].iov_base = va; hdrlen = (u32)((unsigned long)va - (unsigned long)rmsgp); rqstp->rq_arg.head[0].iov_len -= hdrlen; return hdrlen; } /* * Skip all chunks to find RPC msg. These were previously processed */ va = &rmsgp->rm_body.rm_chunks[0]; /* Skip read-list */ for (ch = (struct rpcrdma_read_chunk *)va; ch->rc_discrim != xdr_zero; ch++); va = (u32 *)&ch->rc_position; /* Skip write-list */ ary = (struct rpcrdma_write_array *)va; if (ary->wc_discrim == xdr_zero) va = (u32 *)&ary->wc_nchunks; else /* * rs_length is the 2nd 4B field in wc_target and taking its * address skips the list terminator */ va = (u32 *)&ary->wc_array[ary->wc_nchunks].wc_target.rs_length; /* Skip reply-array */ ary = (struct rpcrdma_write_array *)va; if (ary->wc_discrim == xdr_zero) va = (u32 *)&ary->wc_nchunks; else va = (u32 *)&ary->wc_array[ary->wc_nchunks]; rqstp->rq_arg.head[0].iov_base = va; hdrlen = (unsigned long)va - (unsigned long)rmsgp; rqstp->rq_arg.head[0].iov_len -= hdrlen; return hdrlen; } int svc_rdma_xdr_encode_error(struct svcxprt_rdma *xprt, struct rpcrdma_msg *rmsgp, enum rpcrdma_errcode err, u32 *va) { u32 *startp = va; *va++ = htonl(rmsgp->rm_xid); *va++ = htonl(rmsgp->rm_vers); *va++ = htonl(xprt->sc_max_requests); *va++ = htonl(RDMA_ERROR); *va++ = htonl(err); if (err == ERR_VERS) { *va++ = htonl(RPCRDMA_VERSION); *va++ = htonl(RPCRDMA_VERSION); } return (int)((unsigned long)va - (unsigned long)startp); } int svc_rdma_xdr_get_reply_hdr_len(struct rpcrdma_msg *rmsgp) { struct rpcrdma_write_array *wr_ary; /* There is no read-list in a reply */ /* skip write list */ wr_ary = (struct rpcrdma_write_array *) &rmsgp->rm_body.rm_chunks[1]; if (wr_ary->wc_discrim) wr_ary = (struct rpcrdma_write_array *) &wr_ary->wc_array[ntohl(wr_ary->wc_nchunks)]. wc_target.rs_length; else wr_ary = (struct rpcrdma_write_array *) &wr_ary->wc_nchunks; /* skip reply array */ if (wr_ary->wc_discrim) wr_ary = (struct rpcrdma_write_array *) &wr_ary->wc_array[ntohl(wr_ary->wc_nchunks)]; else wr_ary = (struct rpcrdma_write_array *) &wr_ary->wc_nchunks; return (unsigned long) wr_ary - (unsigned long) rmsgp; } void svc_rdma_xdr_encode_write_list(struct rpcrdma_msg *rmsgp, int chunks) { struct rpcrdma_write_array *ary; /* no read-list */ rmsgp->rm_body.rm_chunks[0] = xdr_zero; /* write-array discrim */ ary = (struct rpcrdma_write_array *) &rmsgp->rm_body.rm_chunks[1]; ary->wc_discrim = xdr_one; ary->wc_nchunks = htonl(chunks); /* write-list terminator */ ary->wc_array[chunks].wc_target.rs_handle = xdr_zero; /* reply-array discriminator */ ary->wc_array[chunks].wc_target.rs_length = xdr_zero; } void svc_rdma_xdr_encode_reply_array(struct rpcrdma_write_array *ary, int chunks) { ary->wc_discrim = xdr_one; ary->wc_nchunks = htonl(chunks); } void svc_rdma_xdr_encode_array_chunk(struct rpcrdma_write_array *ary, int chunk_no, u32 rs_handle, u64 rs_offset, u32 write_len) { struct rpcrdma_segment *seg = &ary->wc_array[chunk_no].wc_target; seg->rs_handle = htonl(rs_handle); seg->rs_length = htonl(write_len); xdr_encode_hyper((u32 *) &seg->rs_offset, rs_offset); } void svc_rdma_xdr_encode_reply_header(struct svcxprt_rdma *xprt, struct rpcrdma_msg *rdma_argp, struct rpcrdma_msg *rdma_resp, enum rpcrdma_proc rdma_type) { rdma_resp->rm_xid = htonl(rdma_argp->rm_xid); rdma_resp->rm_vers = htonl(rdma_argp->rm_vers); rdma_resp->rm_credit = htonl(xprt->sc_max_requests); rdma_resp->rm_type = htonl(rdma_type); /* Encode <nul> chunks lists */ rdma_resp->rm_body.rm_chunks[0] = xdr_zero; rdma_resp->rm_body.rm_chunks[1] = xdr_zero; rdma_resp->rm_body.rm_chunks[2] = xdr_zero; }