Blame view

crypto/async_tx/async_pq.c 12.5 KB
b2f46fd8e   Dan Williams   async_tx: add sup...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
  /*
   * Copyright(c) 2007 Yuri Tikhonov <yur@emcraft.com>
   * Copyright(c) 2009 Intel Corporation
   *
   * This program is free software; you can redistribute it and/or modify it
   * under the terms of the GNU General Public License as published by the Free
   * Software Foundation; either version 2 of the License, or (at your option)
   * any later version.
   *
   * This program is distributed in the hope that it will be useful, but WITHOUT
   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
   * more details.
   *
   * You should have received a copy of the GNU General Public License along with
   * this program; if not, write to the Free Software Foundation, Inc., 59
   * Temple Place - Suite 330, Boston, MA  02111-1307, USA.
   *
   * The full GNU General Public License is included in this distribution in the
   * file called COPYING.
   */
  #include <linux/kernel.h>
  #include <linux/interrupt.h>
4bb33cc89   Paul Gortmaker   crypto: add modul...
24
  #include <linux/module.h>
b2f46fd8e   Dan Williams   async_tx: add sup...
25
26
27
  #include <linux/dma-mapping.h>
  #include <linux/raid/pq.h>
  #include <linux/async_tx.h>
5a0e3ad6a   Tejun Heo   include cleanup: ...
28
  #include <linux/gfp.h>
b2f46fd8e   Dan Williams   async_tx: add sup...
29
30
  
  /**
030b07720   Dan Williams   async_pq: rename ...
31
32
   * pq_scribble_page - space to hold throwaway P or Q buffer for
   * synchronous gen_syndrome
b2f46fd8e   Dan Williams   async_tx: add sup...
33
   */
030b07720   Dan Williams   async_pq: rename ...
34
  static struct page *pq_scribble_page;
b2f46fd8e   Dan Williams   async_tx: add sup...
35

b2f46fd8e   Dan Williams   async_tx: add sup...
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
  /* the struct page *blocks[] parameter passed to async_gen_syndrome()
   * and async_syndrome_val() contains the 'P' destination address at
   * blocks[disks-2] and the 'Q' destination address at blocks[disks-1]
   *
   * note: these are macros as they are used as lvalues
   */
  #define P(b, d) (b[d-2])
  #define Q(b, d) (b[d-1])
  
  /**
   * do_async_gen_syndrome - asynchronously calculate P and/or Q
   */
  static __async_inline struct dma_async_tx_descriptor *
  do_async_gen_syndrome(struct dma_chan *chan, struct page **blocks,
  		      const unsigned char *scfs, unsigned int offset, int disks,
  		      size_t len, dma_addr_t *dma_src,
  		      struct async_submit_ctl *submit)
  {
  	struct dma_async_tx_descriptor *tx = NULL;
  	struct dma_device *dma = chan->device;
  	enum dma_ctrl_flags dma_flags = 0;
  	enum async_tx_flags flags_orig = submit->flags;
  	dma_async_tx_callback cb_fn_orig = submit->cb_fn;
  	dma_async_tx_callback cb_param_orig = submit->cb_param;
  	int src_cnt = disks - 2;
  	unsigned char coefs[src_cnt];
  	unsigned short pq_src_cnt;
  	dma_addr_t dma_dest[2];
  	int src_off = 0;
  	int idx;
  	int i;
  
  	/* DMAs use destinations as sources, so use BIDIRECTIONAL mapping */
  	if (P(blocks, disks))
  		dma_dest[0] = dma_map_page(dma->dev, P(blocks, disks), offset,
  					   len, DMA_BIDIRECTIONAL);
  	else
  		dma_flags |= DMA_PREP_PQ_DISABLE_P;
  	if (Q(blocks, disks))
  		dma_dest[1] = dma_map_page(dma->dev, Q(blocks, disks), offset,
  					   len, DMA_BIDIRECTIONAL);
  	else
  		dma_flags |= DMA_PREP_PQ_DISABLE_Q;
  
  	/* convert source addresses being careful to collapse 'empty'
  	 * sources and update the coefficients accordingly
  	 */
  	for (i = 0, idx = 0; i < src_cnt; i++) {
5dd33c9a4   NeilBrown   md/async: don't p...
84
  		if (blocks[i] == NULL)
b2f46fd8e   Dan Williams   async_tx: add sup...
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
  			continue;
  		dma_src[idx] = dma_map_page(dma->dev, blocks[i], offset, len,
  					    DMA_TO_DEVICE);
  		coefs[idx] = scfs[i];
  		idx++;
  	}
  	src_cnt = idx;
  
  	while (src_cnt > 0) {
  		submit->flags = flags_orig;
  		pq_src_cnt = min(src_cnt, dma_maxpq(dma, dma_flags));
  		/* if we are submitting additional pqs, leave the chain open,
  		 * clear the callback parameters, and leave the destination
  		 * buffers mapped
  		 */
  		if (src_cnt > pq_src_cnt) {
  			submit->flags &= ~ASYNC_TX_ACK;
0403e3827   Dan Williams   dmaengine: add fe...
102
  			submit->flags |= ASYNC_TX_FENCE;
b2f46fd8e   Dan Williams   async_tx: add sup...
103
104
105
106
107
108
109
110
111
112
  			dma_flags |= DMA_COMPL_SKIP_DEST_UNMAP;
  			submit->cb_fn = NULL;
  			submit->cb_param = NULL;
  		} else {
  			dma_flags &= ~DMA_COMPL_SKIP_DEST_UNMAP;
  			submit->cb_fn = cb_fn_orig;
  			submit->cb_param = cb_param_orig;
  			if (cb_fn_orig)
  				dma_flags |= DMA_PREP_INTERRUPT;
  		}
0403e3827   Dan Williams   dmaengine: add fe...
113
114
  		if (submit->flags & ASYNC_TX_FENCE)
  			dma_flags |= DMA_PREP_FENCE;
b2f46fd8e   Dan Williams   async_tx: add sup...
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
  
  		/* Since we have clobbered the src_list we are committed
  		 * to doing this asynchronously.  Drivers force forward
  		 * progress in case they can not provide a descriptor
  		 */
  		for (;;) {
  			tx = dma->device_prep_dma_pq(chan, dma_dest,
  						     &dma_src[src_off],
  						     pq_src_cnt,
  						     &coefs[src_off], len,
  						     dma_flags);
  			if (likely(tx))
  				break;
  			async_tx_quiesce(&submit->depend_tx);
  			dma_async_issue_pending(chan);
  		}
  
  		async_tx_submit(chan, tx, submit);
  		submit->depend_tx = tx;
  
  		/* drop completed sources */
  		src_cnt -= pq_src_cnt;
  		src_off += pq_src_cnt;
  
  		dma_flags |= DMA_PREP_CONTINUE;
  	}
  
  	return tx;
  }
  
  /**
   * do_sync_gen_syndrome - synchronously calculate a raid6 syndrome
   */
  static void
  do_sync_gen_syndrome(struct page **blocks, unsigned int offset, int disks,
  		     size_t len, struct async_submit_ctl *submit)
  {
  	void **srcs;
  	int i;
  
  	if (submit->scribble)
  		srcs = submit->scribble;
  	else
  		srcs = (void **) blocks;
  
  	for (i = 0; i < disks; i++) {
5dd33c9a4   NeilBrown   md/async: don't p...
161
  		if (blocks[i] == NULL) {
b2f46fd8e   Dan Williams   async_tx: add sup...
162
  			BUG_ON(i > disks - 3); /* P or Q can't be zero */
5dd33c9a4   NeilBrown   md/async: don't p...
163
  			srcs[i] = (void*)raid6_empty_zero_page;
b2f46fd8e   Dan Williams   async_tx: add sup...
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
  		} else
  			srcs[i] = page_address(blocks[i]) + offset;
  	}
  	raid6_call.gen_syndrome(disks, len, srcs);
  	async_tx_sync_epilog(submit);
  }
  
  /**
   * async_gen_syndrome - asynchronously calculate a raid6 syndrome
   * @blocks: source blocks from idx 0..disks-3, P @ disks-2 and Q @ disks-1
   * @offset: common offset into each block (src and dest) to start transaction
   * @disks: number of blocks (including missing P or Q, see below)
   * @len: length of operation in bytes
   * @submit: submission/completion modifiers
   *
   * General note: This routine assumes a field of GF(2^8) with a
   * primitive polynomial of 0x11d and a generator of {02}.
   *
   * 'disks' note: callers can optionally omit either P or Q (but not
   * both) from the calculation by setting blocks[disks-2] or
   * blocks[disks-1] to NULL.  When P or Q is omitted 'len' must be <=
   * PAGE_SIZE as a temporary buffer of this size is used in the
   * synchronous path.  'disks' always accounts for both destination
5676470f0   Dan Williams   async_pq: kill a ...
187
188
189
190
   * buffers.  If any source buffers (blocks[i] where i < disks - 2) are
   * set to NULL those buffers will be replaced with the raid6_zero_page
   * in the synchronous path and omitted in the hardware-asynchronous
   * path.
b2f46fd8e   Dan Williams   async_tx: add sup...
191
192
   *
   * 'blocks' note: if submit->scribble is NULL then the contents of
5676470f0   Dan Williams   async_pq: kill a ...
193
194
   * 'blocks' may be overwritten to perform address conversions
   * (dma_map_page() or page_address()).
b2f46fd8e   Dan Williams   async_tx: add sup...
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
   */
  struct dma_async_tx_descriptor *
  async_gen_syndrome(struct page **blocks, unsigned int offset, int disks,
  		   size_t len, struct async_submit_ctl *submit)
  {
  	int src_cnt = disks - 2;
  	struct dma_chan *chan = async_tx_find_channel(submit, DMA_PQ,
  						      &P(blocks, disks), 2,
  						      blocks, src_cnt, len);
  	struct dma_device *device = chan ? chan->device : NULL;
  	dma_addr_t *dma_src = NULL;
  
  	BUG_ON(disks > 255 || !(P(blocks, disks) || Q(blocks, disks)));
  
  	if (submit->scribble)
  		dma_src = submit->scribble;
  	else if (sizeof(dma_addr_t) <= sizeof(struct page *))
  		dma_src = (dma_addr_t *) blocks;
  
  	if (dma_src && device &&
  	    (src_cnt <= dma_maxpq(device, 0) ||
83544ae9f   Dan Williams   dmaengine, async_...
216
217
  	     dma_maxpq(device, DMA_PREP_CONTINUE) > 0) &&
  	    is_dma_pq_aligned(device, offset, 0, len)) {
b2f46fd8e   Dan Williams   async_tx: add sup...
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
  		/* run the p+q asynchronously */
  		pr_debug("%s: (async) disks: %d len: %zu
  ",
  			 __func__, disks, len);
  		return do_async_gen_syndrome(chan, blocks, raid6_gfexp, offset,
  					     disks, len, dma_src, submit);
  	}
  
  	/* run the pq synchronously */
  	pr_debug("%s: (sync) disks: %d len: %zu
  ", __func__, disks, len);
  
  	/* wait for any prerequisite operations */
  	async_tx_quiesce(&submit->depend_tx);
  
  	if (!P(blocks, disks)) {
030b07720   Dan Williams   async_pq: rename ...
234
  		P(blocks, disks) = pq_scribble_page;
b2f46fd8e   Dan Williams   async_tx: add sup...
235
236
237
  		BUG_ON(len + offset > PAGE_SIZE);
  	}
  	if (!Q(blocks, disks)) {
030b07720   Dan Williams   async_pq: rename ...
238
  		Q(blocks, disks) = pq_scribble_page;
b2f46fd8e   Dan Williams   async_tx: add sup...
239
240
241
242
243
244
245
  		BUG_ON(len + offset > PAGE_SIZE);
  	}
  	do_sync_gen_syndrome(blocks, offset, disks, len, submit);
  
  	return NULL;
  }
  EXPORT_SYMBOL_GPL(async_gen_syndrome);
7b3cc2b1f   Dan Williams   async_tx: build-t...
246
247
248
249
250
251
252
253
254
  static inline struct dma_chan *
  pq_val_chan(struct async_submit_ctl *submit, struct page **blocks, int disks, size_t len)
  {
  	#ifdef CONFIG_ASYNC_TX_DISABLE_PQ_VAL_DMA
  	return NULL;
  	#endif
  	return async_tx_find_channel(submit, DMA_PQ_VAL, NULL, 0,  blocks,
  				     disks, len);
  }
b2f46fd8e   Dan Williams   async_tx: add sup...
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
  /**
   * async_syndrome_val - asynchronously validate a raid6 syndrome
   * @blocks: source blocks from idx 0..disks-3, P @ disks-2 and Q @ disks-1
   * @offset: common offset into each block (src and dest) to start transaction
   * @disks: number of blocks (including missing P or Q, see below)
   * @len: length of operation in bytes
   * @pqres: on val failure SUM_CHECK_P_RESULT and/or SUM_CHECK_Q_RESULT are set
   * @spare: temporary result buffer for the synchronous case
   * @submit: submission / completion modifiers
   *
   * The same notes from async_gen_syndrome apply to the 'blocks',
   * and 'disks' parameters of this routine.  The synchronous path
   * requires a temporary result buffer and submit->scribble to be
   * specified.
   */
  struct dma_async_tx_descriptor *
  async_syndrome_val(struct page **blocks, unsigned int offset, int disks,
  		   size_t len, enum sum_check_flags *pqres, struct page *spare,
  		   struct async_submit_ctl *submit)
  {
7b3cc2b1f   Dan Williams   async_tx: build-t...
275
  	struct dma_chan *chan = pq_val_chan(submit, blocks, disks, len);
b2f46fd8e   Dan Williams   async_tx: add sup...
276
277
  	struct dma_device *device = chan ? chan->device : NULL;
  	struct dma_async_tx_descriptor *tx;
b2141e695   NeilBrown   raid6/async_tx: h...
278
  	unsigned char coefs[disks-2];
b2f46fd8e   Dan Williams   async_tx: add sup...
279
280
  	enum dma_ctrl_flags dma_flags = submit->cb_fn ? DMA_PREP_INTERRUPT : 0;
  	dma_addr_t *dma_src = NULL;
b2141e695   NeilBrown   raid6/async_tx: h...
281
  	int src_cnt = 0;
b2f46fd8e   Dan Williams   async_tx: add sup...
282
283
284
285
286
287
288
  
  	BUG_ON(disks < 4);
  
  	if (submit->scribble)
  		dma_src = submit->scribble;
  	else if (sizeof(dma_addr_t) <= sizeof(struct page *))
  		dma_src = (dma_addr_t *) blocks;
83544ae9f   Dan Williams   dmaengine, async_...
289
290
  	if (dma_src && device && disks <= dma_maxpq(device, 0) &&
  	    is_dma_pq_aligned(device, offset, 0, len)) {
b2f46fd8e   Dan Williams   async_tx: add sup...
291
292
293
294
295
296
297
298
299
  		struct device *dev = device->dev;
  		dma_addr_t *pq = &dma_src[disks-2];
  		int i;
  
  		pr_debug("%s: (async) disks: %d len: %zu
  ",
  			 __func__, disks, len);
  		if (!P(blocks, disks))
  			dma_flags |= DMA_PREP_PQ_DISABLE_P;
b2141e695   NeilBrown   raid6/async_tx: h...
300
  		else
5676470f0   Dan Williams   async_pq: kill a ...
301
  			pq[0] = dma_map_page(dev, P(blocks, disks),
b2141e695   NeilBrown   raid6/async_tx: h...
302
303
  					     offset, len,
  					     DMA_TO_DEVICE);
b2f46fd8e   Dan Williams   async_tx: add sup...
304
305
  		if (!Q(blocks, disks))
  			dma_flags |= DMA_PREP_PQ_DISABLE_Q;
b2141e695   NeilBrown   raid6/async_tx: h...
306
  		else
5676470f0   Dan Williams   async_pq: kill a ...
307
  			pq[1] = dma_map_page(dev, Q(blocks, disks),
b2141e695   NeilBrown   raid6/async_tx: h...
308
309
  					     offset, len,
  					     DMA_TO_DEVICE);
0403e3827   Dan Williams   dmaengine: add fe...
310
311
  		if (submit->flags & ASYNC_TX_FENCE)
  			dma_flags |= DMA_PREP_FENCE;
b2141e695   NeilBrown   raid6/async_tx: h...
312
313
314
315
316
317
318
319
  		for (i = 0; i < disks-2; i++)
  			if (likely(blocks[i])) {
  				dma_src[src_cnt] = dma_map_page(dev, blocks[i],
  								offset, len,
  								DMA_TO_DEVICE);
  				coefs[src_cnt] = raid6_gfexp[i];
  				src_cnt++;
  			}
b2f46fd8e   Dan Williams   async_tx: add sup...
320
321
322
  
  		for (;;) {
  			tx = device->device_prep_dma_pq_val(chan, pq, dma_src,
b2141e695   NeilBrown   raid6/async_tx: h...
323
324
  							    src_cnt,
  							    coefs,
b2f46fd8e   Dan Williams   async_tx: add sup...
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
  							    len, pqres,
  							    dma_flags);
  			if (likely(tx))
  				break;
  			async_tx_quiesce(&submit->depend_tx);
  			dma_async_issue_pending(chan);
  		}
  		async_tx_submit(chan, tx, submit);
  
  		return tx;
  	} else {
  		struct page *p_src = P(blocks, disks);
  		struct page *q_src = Q(blocks, disks);
  		enum async_tx_flags flags_orig = submit->flags;
  		dma_async_tx_callback cb_fn_orig = submit->cb_fn;
  		void *scribble = submit->scribble;
  		void *cb_param_orig = submit->cb_param;
  		void *p, *q, *s;
  
  		pr_debug("%s: (sync) disks: %d len: %zu
  ",
  			 __func__, disks, len);
  
  		/* caller must provide a temporary result buffer and
  		 * allow the input parameters to be preserved
  		 */
  		BUG_ON(!spare || !scribble);
  
  		/* wait for any prerequisite operations */
  		async_tx_quiesce(&submit->depend_tx);
  
  		/* recompute p and/or q into the temporary buffer and then
  		 * check to see the result matches the current value
  		 */
  		tx = NULL;
  		*pqres = 0;
  		if (p_src) {
  			init_async_submit(submit, ASYNC_TX_XOR_ZERO_DST, NULL,
  					  NULL, NULL, scribble);
  			tx = async_xor(spare, blocks, offset, disks-2, len, submit);
  			async_tx_quiesce(&tx);
  			p = page_address(p_src) + offset;
  			s = page_address(spare) + offset;
  			*pqres |= !!memcmp(p, s, len) << SUM_CHECK_P;
  		}
  
  		if (q_src) {
  			P(blocks, disks) = NULL;
  			Q(blocks, disks) = spare;
  			init_async_submit(submit, 0, NULL, NULL, NULL, scribble);
  			tx = async_gen_syndrome(blocks, offset, disks, len, submit);
  			async_tx_quiesce(&tx);
  			q = page_address(q_src) + offset;
  			s = page_address(spare) + offset;
  			*pqres |= !!memcmp(q, s, len) << SUM_CHECK_Q;
  		}
  
  		/* restore P, Q and submit */
  		P(blocks, disks) = p_src;
  		Q(blocks, disks) = q_src;
  
  		submit->cb_fn = cb_fn_orig;
  		submit->cb_param = cb_param_orig;
  		submit->flags = flags_orig;
  		async_tx_sync_epilog(submit);
  
  		return NULL;
  	}
  }
  EXPORT_SYMBOL_GPL(async_syndrome_val);
  
  static int __init async_pq_init(void)
  {
030b07720   Dan Williams   async_pq: rename ...
398
  	pq_scribble_page = alloc_page(GFP_KERNEL);
b2f46fd8e   Dan Williams   async_tx: add sup...
399

030b07720   Dan Williams   async_pq: rename ...
400
  	if (pq_scribble_page)
b2f46fd8e   Dan Williams   async_tx: add sup...
401
402
403
404
405
406
407
408
409
410
  		return 0;
  
  	pr_err("%s: failed to allocate required spare page
  ", __func__);
  
  	return -ENOMEM;
  }
  
  static void __exit async_pq_exit(void)
  {
030b07720   Dan Williams   async_pq: rename ...
411
  	put_page(pq_scribble_page);
b2f46fd8e   Dan Williams   async_tx: add sup...
412
413
414
415
416
417
418
  }
  
  module_init(async_pq_init);
  module_exit(async_pq_exit);
  
  MODULE_DESCRIPTION("asynchronous raid6 syndrome generation/validation");
  MODULE_LICENSE("GPL");