xref: /linux/net/sunrpc/xprtrdma/svc_rdma_pcl.c (revision d141ec2825b4d3ec52f27c43bdd864090159273a)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Copyright (c) 2020 Oracle. All rights reserved.
4  */
5 
6 #include <linux/sunrpc/svc_rdma.h>
7 #include <linux/sunrpc/rpc_rdma.h>
8 
9 #include "xprt_rdma.h"
10 #include <trace/events/rpcrdma.h>
11 
12 /**
13  * pcl_free - Release all memory associated with a parsed chunk list
14  * @pcl: parsed chunk list
15  *
16  */
pcl_free(struct svc_rdma_pcl * pcl)17 void pcl_free(struct svc_rdma_pcl *pcl)
18 {
19 	while (!list_empty(&pcl->cl_chunks)) {
20 		struct svc_rdma_chunk *chunk;
21 
22 		chunk = pcl_first_chunk(pcl);
23 		list_del(&chunk->ch_list);
24 		kfree(chunk);
25 	}
26 }
27 
pcl_alloc_chunk(u32 segcount,u32 position)28 static struct svc_rdma_chunk *pcl_alloc_chunk(u32 segcount, u32 position)
29 {
30 	struct svc_rdma_chunk *chunk;
31 
32 	chunk = kmalloc_flex(*chunk, ch_segments, segcount);
33 	if (!chunk)
34 		return NULL;
35 
36 	chunk->ch_position = position;
37 	chunk->ch_length = 0;
38 	chunk->ch_payload_length = 0;
39 	chunk->ch_segcount = 0;
40 	return chunk;
41 }
42 
43 static struct svc_rdma_chunk *
pcl_lookup_position(struct svc_rdma_pcl * pcl,u32 position)44 pcl_lookup_position(struct svc_rdma_pcl *pcl, u32 position)
45 {
46 	struct svc_rdma_chunk *pos;
47 
48 	pcl_for_each_chunk(pos, pcl) {
49 		if (pos->ch_position == position)
50 			return pos;
51 	}
52 	return NULL;
53 }
54 
pcl_insert_position(struct svc_rdma_pcl * pcl,struct svc_rdma_chunk * chunk)55 static void pcl_insert_position(struct svc_rdma_pcl *pcl,
56 				struct svc_rdma_chunk *chunk)
57 {
58 	struct svc_rdma_chunk *pos;
59 
60 	pcl_for_each_chunk(pos, pcl) {
61 		if (pos->ch_position > chunk->ch_position)
62 			break;
63 	}
64 	__list_add(&chunk->ch_list, pos->ch_list.prev, &pos->ch_list);
65 	pcl->cl_count++;
66 }
67 
pcl_set_read_segment(const struct svc_rdma_recv_ctxt * rctxt,struct svc_rdma_chunk * chunk,u32 handle,u32 length,u64 offset)68 static void pcl_set_read_segment(const struct svc_rdma_recv_ctxt *rctxt,
69 				 struct svc_rdma_chunk *chunk,
70 				 u32 handle, u32 length, u64 offset)
71 {
72 	struct svc_rdma_segment *segment;
73 
74 	segment = &chunk->ch_segments[chunk->ch_segcount];
75 	segment->rs_handle = handle;
76 	segment->rs_length = length;
77 	segment->rs_offset = offset;
78 
79 	trace_svcrdma_decode_rseg(&rctxt->rc_cid, chunk, segment);
80 
81 	chunk->ch_length += length;
82 	chunk->ch_segcount++;
83 }
84 
85 /**
86  * pcl_alloc_call - Construct a parsed chunk list for the Call body
87  * @rctxt: Ingress receive context
88  * @p: Start of an un-decoded Read list
89  *
90  * Assumptions:
91  * - The incoming Read list has already been sanity checked.
92  * - cl_count is already set to the number of segments in
93  *   the un-decoded list.
94  * - The list might not be in order by position.
95  *
96  * Return values:
97  *       %true: Parsed chunk list was successfully constructed, and
98  *              cl_count is updated to be the number of chunks (ie.
99  *              unique positions) in the Read list.
100  *      %false: Memory allocation failed.
101  */
pcl_alloc_call(struct svc_rdma_recv_ctxt * rctxt,__be32 * p)102 bool pcl_alloc_call(struct svc_rdma_recv_ctxt *rctxt, __be32 *p)
103 {
104 	struct svc_rdma_pcl *pcl = &rctxt->rc_call_pcl;
105 	unsigned int i, segcount = pcl->cl_count;
106 
107 	pcl->cl_count = 0;
108 	for (i = 0; i < segcount; i++) {
109 		struct svc_rdma_chunk *chunk;
110 		u32 position, handle, length;
111 		u64 offset;
112 
113 		p++;	/* skip the list discriminator */
114 		p = xdr_decode_read_segment(p, &position, &handle,
115 					    &length, &offset);
116 		if (position != 0)
117 			continue;
118 
119 		if (pcl_is_empty(pcl)) {
120 			chunk = pcl_alloc_chunk(segcount, position);
121 			if (!chunk)
122 				return false;
123 			pcl_insert_position(pcl, chunk);
124 		} else {
125 			chunk = list_first_entry(&pcl->cl_chunks,
126 						 struct svc_rdma_chunk,
127 						 ch_list);
128 		}
129 
130 		pcl_set_read_segment(rctxt, chunk, handle, length, offset);
131 	}
132 
133 	return true;
134 }
135 
136 /**
137  * pcl_alloc_read - Construct a parsed chunk list for normal Read chunks
138  * @rctxt: Ingress receive context
139  * @p: Start of an un-decoded Read list
140  *
141  * Assumptions:
142  * - The incoming Read list has already been sanity checked.
143  * - cl_count is already set to the number of segments in
144  *   the un-decoded list.
145  * - The list might not be in order by position.
146  *
147  * Return values:
148  *       %true: Parsed chunk list was successfully constructed, and
149  *              cl_count is updated to be the number of chunks (ie.
150  *              unique position values) in the Read list.
151  *      %false: Memory allocation failed.
152  */
pcl_alloc_read(struct svc_rdma_recv_ctxt * rctxt,__be32 * p)153 bool pcl_alloc_read(struct svc_rdma_recv_ctxt *rctxt, __be32 *p)
154 {
155 	struct svc_rdma_pcl *pcl = &rctxt->rc_read_pcl;
156 	unsigned int i, segcount = pcl->cl_count;
157 
158 	pcl->cl_count = 0;
159 	for (i = 0; i < segcount; i++) {
160 		struct svc_rdma_chunk *chunk;
161 		u32 position, handle, length;
162 		u64 offset;
163 
164 		p++;	/* skip the list discriminator */
165 		p = xdr_decode_read_segment(p, &position, &handle,
166 					    &length, &offset);
167 		if (position == 0)
168 			continue;
169 
170 		chunk = pcl_lookup_position(pcl, position);
171 		if (!chunk) {
172 			chunk = pcl_alloc_chunk(segcount, position);
173 			if (!chunk)
174 				return false;
175 			pcl_insert_position(pcl, chunk);
176 		}
177 
178 		pcl_set_read_segment(rctxt, chunk, handle, length, offset);
179 	}
180 
181 	return true;
182 }
183 
184 /**
185  * pcl_alloc_write - Construct a parsed chunk list from a Write list
186  * @rctxt: Ingress receive context
187  * @pcl: Parsed chunk list to populate
188  * @p: Start of an un-decoded Write list
189  *
190  * Assumptions:
191  * - The incoming Write list has already been sanity checked, and
192  * - cl_count is set to the number of chunks in the un-decoded list.
193  *
194  * Return values:
195  *       %true: Parsed chunk list was successfully constructed.
196  *      %false: Memory allocation failed.
197  */
pcl_alloc_write(struct svc_rdma_recv_ctxt * rctxt,struct svc_rdma_pcl * pcl,__be32 * p)198 bool pcl_alloc_write(struct svc_rdma_recv_ctxt *rctxt,
199 		     struct svc_rdma_pcl *pcl, __be32 *p)
200 {
201 	struct svc_rdma_segment *segment;
202 	struct svc_rdma_chunk *chunk;
203 	unsigned int i, j;
204 	u32 segcount;
205 
206 	for (i = 0; i < pcl->cl_count; i++) {
207 		p++;	/* skip the list discriminator */
208 		segcount = be32_to_cpup(p++);
209 
210 		chunk = pcl_alloc_chunk(segcount, 0);
211 		if (!chunk)
212 			return false;
213 
214 		for (j = 0; j < segcount; j++) {
215 			segment = &chunk->ch_segments[j];
216 			p = xdr_decode_rdma_segment(p, &segment->rs_handle,
217 						    &segment->rs_length,
218 						    &segment->rs_offset);
219 			trace_svcrdma_decode_wseg(&rctxt->rc_cid, chunk, j);
220 
221 			chunk->ch_length += segment->rs_length;
222 			chunk->ch_segcount++;
223 		}
224 		list_add_tail(&chunk->ch_list, &pcl->cl_chunks);
225 	}
226 	return true;
227 }
228 
229 /**
230  * pcl_check_read_chunk_positions - Validate Read chunk positions
231  * @rctxt: Ingress receive context with populated chunk lists
232  * @inline_len: Length of the inline RPC body after the transport header
233  *
234  * Read chunk positions are offsets in the unreduced XDR stream
235  * (RFC 8166 Section 3.4.4), so each position includes the
236  * cumulative length of preceding Read chunks. This function
237  * subtracts those lengths to recover the inline-body offset
238  * before comparing against @inline_len or the Call chunk length.
239  *
240  * Rejects frames where a Read chunk's inline-body offset exceeds
241  * the bound, where adjacent Read chunks overlap, or where any
242  * single chunk length exceeds the page budget.
243  *
244  * Return values:
245  *       %true: Read chunk positions and lengths are valid
246  *      %false: Malformed chunk list detected
247  */
pcl_check_read_chunk_positions(struct svc_rdma_recv_ctxt * rctxt,unsigned int inline_len)248 bool pcl_check_read_chunk_positions(struct svc_rdma_recv_ctxt *rctxt,
249 				    unsigned int inline_len)
250 {
251 	unsigned int max_len, bound, total_read;
252 	struct svc_rdma_chunk *chunk, *next;
253 
254 	max_len = rctxt->rc_maxpages << PAGE_SHIFT;
255 
256 	if (!pcl_is_empty(&rctxt->rc_call_pcl)) {
257 		chunk = pcl_first_chunk(&rctxt->rc_call_pcl);
258 		if (chunk->ch_length > max_len)
259 			return false;
260 		bound = chunk->ch_length;
261 	} else {
262 		bound = inline_len;
263 	}
264 
265 	if (pcl_is_empty(&rctxt->rc_read_pcl))
266 		return true;
267 
268 	total_read = 0;
269 	pcl_for_each_chunk(chunk, &rctxt->rc_read_pcl) {
270 		if (chunk->ch_position - total_read > bound)
271 			return false;
272 		if (chunk->ch_length > max_len)
273 			return false;
274 
275 		next = pcl_next_chunk(&rctxt->rc_read_pcl, chunk);
276 		if (!next)
277 			break;
278 
279 		if (chunk->ch_position + chunk->ch_length > next->ch_position)
280 			return false;
281 		total_read += chunk->ch_length;
282 	}
283 
284 	return true;
285 }
286 
pcl_process_region(const struct xdr_buf * xdr,unsigned int offset,unsigned int length,int (* actor)(const struct xdr_buf *,void *),void * data)287 static int pcl_process_region(const struct xdr_buf *xdr,
288 			      unsigned int offset, unsigned int length,
289 			      int (*actor)(const struct xdr_buf *, void *),
290 			      void *data)
291 {
292 	struct xdr_buf subbuf;
293 
294 	if (!length)
295 		return 0;
296 	if (xdr_buf_subsegment(xdr, &subbuf, offset, length))
297 		return -EMSGSIZE;
298 	return actor(&subbuf, data);
299 }
300 
301 /**
302  * pcl_process_nonpayloads - Process non-payload regions inside @xdr
303  * @pcl: Chunk list to process
304  * @xdr: xdr_buf to process
305  * @actor: Function to invoke on each non-payload region
306  * @data: Arguments for @actor
307  *
308  * This mechanism must ignore not only result payloads that were already
309  * sent via RDMA Write, but also XDR padding for those payloads that
310  * the upper layer has added.
311  *
312  * Assumptions:
313  *  The xdr->len and ch_position fields are aligned to 4-byte multiples.
314  *
315  * Returns:
316  *   On success, zero,
317  *   %-EMSGSIZE on XDR buffer overflow, or
318  *   The return value of @actor
319  */
pcl_process_nonpayloads(const struct svc_rdma_pcl * pcl,const struct xdr_buf * xdr,int (* actor)(const struct xdr_buf *,void *),void * data)320 int pcl_process_nonpayloads(const struct svc_rdma_pcl *pcl,
321 			    const struct xdr_buf *xdr,
322 			    int (*actor)(const struct xdr_buf *, void *),
323 			    void *data)
324 {
325 	struct svc_rdma_chunk *chunk, *next;
326 	unsigned int start;
327 	int ret;
328 
329 	chunk = pcl_first_chunk(pcl);
330 
331 	/* No result payloads were generated */
332 	if (!chunk || !chunk->ch_payload_length)
333 		return actor(xdr, data);
334 
335 	/* Process the region before the first result payload */
336 	ret = pcl_process_region(xdr, 0, chunk->ch_position, actor, data);
337 	if (ret < 0)
338 		return ret;
339 
340 	/* Process the regions between each middle result payload */
341 	while ((next = pcl_next_chunk(pcl, chunk))) {
342 		if (!next->ch_payload_length)
343 			break;
344 
345 		start = pcl_chunk_end_offset(chunk);
346 		ret = pcl_process_region(xdr, start, next->ch_position - start,
347 					 actor, data);
348 		if (ret < 0)
349 			return ret;
350 
351 		chunk = next;
352 	}
353 
354 	/* Process the region after the last result payload */
355 	start = pcl_chunk_end_offset(chunk);
356 	ret = pcl_process_region(xdr, start, xdr->len - start, actor, data);
357 	if (ret < 0)
358 		return ret;
359 
360 	return 0;
361 }
362