diff options
-rw-r--r-- | drivers/dma/Kconfig | 7 | ||||
-rw-r--r-- | drivers/dma/Makefile | 1 | ||||
-rw-r--r-- | drivers/dma/dmatest.c | 444 |
3 files changed, 452 insertions, 0 deletions
diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig index 5af8b1cfc1e9..4b6bd3d099cf 100644 --- a/drivers/dma/Kconfig +++ b/drivers/dma/Kconfig | |||
@@ -72,4 +72,11 @@ config NET_DMA | |||
72 | Say Y here if you enabled INTEL_IOATDMA or FSL_DMA, otherwise | 72 | Say Y here if you enabled INTEL_IOATDMA or FSL_DMA, otherwise |
73 | say N. | 73 | say N. |
74 | 74 | ||
75 | config DMATEST | ||
76 | tristate "DMA Test client" | ||
77 | depends on DMA_ENGINE | ||
78 | help | ||
79 | Simple DMA test client. Say N unless you're debugging a | ||
80 | DMA Device driver. | ||
81 | |||
75 | endif | 82 | endif |
diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile index ee272fd329c9..181e3646fbfe 100644 --- a/drivers/dma/Makefile +++ b/drivers/dma/Makefile | |||
@@ -1,5 +1,6 @@ | |||
1 | obj-$(CONFIG_DMA_ENGINE) += dmaengine.o | 1 | obj-$(CONFIG_DMA_ENGINE) += dmaengine.o |
2 | obj-$(CONFIG_NET_DMA) += iovlock.o | 2 | obj-$(CONFIG_NET_DMA) += iovlock.o |
3 | obj-$(CONFIG_DMATEST) += dmatest.o | ||
3 | obj-$(CONFIG_INTEL_IOATDMA) += ioatdma.o | 4 | obj-$(CONFIG_INTEL_IOATDMA) += ioatdma.o |
4 | ioatdma-objs := ioat.o ioat_dma.o ioat_dca.o | 5 | ioatdma-objs := ioat.o ioat_dma.o ioat_dca.o |
5 | obj-$(CONFIG_INTEL_IOP_ADMA) += iop-adma.o | 6 | obj-$(CONFIG_INTEL_IOP_ADMA) += iop-adma.o |
diff --git a/drivers/dma/dmatest.c b/drivers/dma/dmatest.c new file mode 100644 index 000000000000..a08d19704743 --- /dev/null +++ b/drivers/dma/dmatest.c | |||
@@ -0,0 +1,444 @@ | |||
1 | /* | ||
2 | * DMA Engine test module | ||
3 | * | ||
4 | * Copyright (C) 2007 Atmel Corporation | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU General Public License version 2 as | ||
8 | * published by the Free Software Foundation. | ||
9 | */ | ||
10 | #include <linux/delay.h> | ||
11 | #include <linux/dmaengine.h> | ||
12 | #include <linux/init.h> | ||
13 | #include <linux/kthread.h> | ||
14 | #include <linux/module.h> | ||
15 | #include <linux/moduleparam.h> | ||
16 | #include <linux/random.h> | ||
17 | #include <linux/wait.h> | ||
18 | |||
19 | static unsigned int test_buf_size = 16384; | ||
20 | module_param(test_buf_size, uint, S_IRUGO); | ||
21 | MODULE_PARM_DESC(test_buf_size, "Size of the memcpy test buffer"); | ||
22 | |||
23 | static char test_channel[BUS_ID_SIZE]; | ||
24 | module_param_string(channel, test_channel, sizeof(test_channel), S_IRUGO); | ||
25 | MODULE_PARM_DESC(channel, "Bus ID of the channel to test (default: any)"); | ||
26 | |||
27 | static char test_device[BUS_ID_SIZE]; | ||
28 | module_param_string(device, test_device, sizeof(test_device), S_IRUGO); | ||
29 | MODULE_PARM_DESC(device, "Bus ID of the DMA Engine to test (default: any)"); | ||
30 | |||
31 | static unsigned int threads_per_chan = 1; | ||
32 | module_param(threads_per_chan, uint, S_IRUGO); | ||
33 | MODULE_PARM_DESC(threads_per_chan, | ||
34 | "Number of threads to start per channel (default: 1)"); | ||
35 | |||
36 | static unsigned int max_channels; | ||
37 | module_param(max_channels, uint, S_IRUGO); | ||
38 | MODULE_PARM_DESC(nr_channels, | ||
39 | "Maximum number of channels to use (default: all)"); | ||
40 | |||
41 | /* | ||
42 | * Initialization patterns. All bytes in the source buffer has bit 7 | ||
43 | * set, all bytes in the destination buffer has bit 7 cleared. | ||
44 | * | ||
45 | * Bit 6 is set for all bytes which are to be copied by the DMA | ||
46 | * engine. Bit 5 is set for all bytes which are to be overwritten by | ||
47 | * the DMA engine. | ||
48 | * | ||
49 | * The remaining bits are the inverse of a counter which increments by | ||
50 | * one for each byte address. | ||
51 | */ | ||
52 | #define PATTERN_SRC 0x80 | ||
53 | #define PATTERN_DST 0x00 | ||
54 | #define PATTERN_COPY 0x40 | ||
55 | #define PATTERN_OVERWRITE 0x20 | ||
56 | #define PATTERN_COUNT_MASK 0x1f | ||
57 | |||
58 | struct dmatest_thread { | ||
59 | struct list_head node; | ||
60 | struct task_struct *task; | ||
61 | struct dma_chan *chan; | ||
62 | u8 *srcbuf; | ||
63 | u8 *dstbuf; | ||
64 | }; | ||
65 | |||
66 | struct dmatest_chan { | ||
67 | struct list_head node; | ||
68 | struct dma_chan *chan; | ||
69 | struct list_head threads; | ||
70 | }; | ||
71 | |||
72 | /* | ||
73 | * These are protected by dma_list_mutex since they're only used by | ||
74 | * the DMA client event callback | ||
75 | */ | ||
76 | static LIST_HEAD(dmatest_channels); | ||
77 | static unsigned int nr_channels; | ||
78 | |||
79 | static bool dmatest_match_channel(struct dma_chan *chan) | ||
80 | { | ||
81 | if (test_channel[0] == '\0') | ||
82 | return true; | ||
83 | return strcmp(chan->dev.bus_id, test_channel) == 0; | ||
84 | } | ||
85 | |||
86 | static bool dmatest_match_device(struct dma_device *device) | ||
87 | { | ||
88 | if (test_device[0] == '\0') | ||
89 | return true; | ||
90 | return strcmp(device->dev->bus_id, test_device) == 0; | ||
91 | } | ||
92 | |||
93 | static unsigned long dmatest_random(void) | ||
94 | { | ||
95 | unsigned long buf; | ||
96 | |||
97 | get_random_bytes(&buf, sizeof(buf)); | ||
98 | return buf; | ||
99 | } | ||
100 | |||
101 | static void dmatest_init_srcbuf(u8 *buf, unsigned int start, unsigned int len) | ||
102 | { | ||
103 | unsigned int i; | ||
104 | |||
105 | for (i = 0; i < start; i++) | ||
106 | buf[i] = PATTERN_SRC | (~i & PATTERN_COUNT_MASK); | ||
107 | for ( ; i < start + len; i++) | ||
108 | buf[i] = PATTERN_SRC | PATTERN_COPY | ||
109 | | (~i & PATTERN_COUNT_MASK);; | ||
110 | for ( ; i < test_buf_size; i++) | ||
111 | buf[i] = PATTERN_SRC | (~i & PATTERN_COUNT_MASK); | ||
112 | } | ||
113 | |||
114 | static void dmatest_init_dstbuf(u8 *buf, unsigned int start, unsigned int len) | ||
115 | { | ||
116 | unsigned int i; | ||
117 | |||
118 | for (i = 0; i < start; i++) | ||
119 | buf[i] = PATTERN_DST | (~i & PATTERN_COUNT_MASK); | ||
120 | for ( ; i < start + len; i++) | ||
121 | buf[i] = PATTERN_DST | PATTERN_OVERWRITE | ||
122 | | (~i & PATTERN_COUNT_MASK); | ||
123 | for ( ; i < test_buf_size; i++) | ||
124 | buf[i] = PATTERN_DST | (~i & PATTERN_COUNT_MASK); | ||
125 | } | ||
126 | |||
127 | static void dmatest_mismatch(u8 actual, u8 pattern, unsigned int index, | ||
128 | unsigned int counter, bool is_srcbuf) | ||
129 | { | ||
130 | u8 diff = actual ^ pattern; | ||
131 | u8 expected = pattern | (~counter & PATTERN_COUNT_MASK); | ||
132 | const char *thread_name = current->comm; | ||
133 | |||
134 | if (is_srcbuf) | ||
135 | pr_warning("%s: srcbuf[0x%x] overwritten!" | ||
136 | " Expected %02x, got %02x\n", | ||
137 | thread_name, index, expected, actual); | ||
138 | else if ((pattern & PATTERN_COPY) | ||
139 | && (diff & (PATTERN_COPY | PATTERN_OVERWRITE))) | ||
140 | pr_warning("%s: dstbuf[0x%x] not copied!" | ||
141 | " Expected %02x, got %02x\n", | ||
142 | thread_name, index, expected, actual); | ||
143 | else if (diff & PATTERN_SRC) | ||
144 | pr_warning("%s: dstbuf[0x%x] was copied!" | ||
145 | " Expected %02x, got %02x\n", | ||
146 | thread_name, index, expected, actual); | ||
147 | else | ||
148 | pr_warning("%s: dstbuf[0x%x] mismatch!" | ||
149 | " Expected %02x, got %02x\n", | ||
150 | thread_name, index, expected, actual); | ||
151 | } | ||
152 | |||
153 | static unsigned int dmatest_verify(u8 *buf, unsigned int start, | ||
154 | unsigned int end, unsigned int counter, u8 pattern, | ||
155 | bool is_srcbuf) | ||
156 | { | ||
157 | unsigned int i; | ||
158 | unsigned int error_count = 0; | ||
159 | u8 actual; | ||
160 | |||
161 | for (i = start; i < end; i++) { | ||
162 | actual = buf[i]; | ||
163 | if (actual != (pattern | (~counter & PATTERN_COUNT_MASK))) { | ||
164 | if (error_count < 32) | ||
165 | dmatest_mismatch(actual, pattern, i, counter, | ||
166 | is_srcbuf); | ||
167 | error_count++; | ||
168 | } | ||
169 | counter++; | ||
170 | } | ||
171 | |||
172 | if (error_count > 32) | ||
173 | pr_warning("%s: %u errors suppressed\n", | ||
174 | current->comm, error_count - 32); | ||
175 | |||
176 | return error_count; | ||
177 | } | ||
178 | |||
179 | /* | ||
180 | * This function repeatedly tests DMA transfers of various lengths and | ||
181 | * offsets until it is told to exit by kthread_stop(). There may be | ||
182 | * multiple threads running this function in parallel for a single | ||
183 | * channel, and there may be multiple channels being tested in | ||
184 | * parallel. | ||
185 | * | ||
186 | * Before each test, the source and destination buffer is initialized | ||
187 | * with a known pattern. This pattern is different depending on | ||
188 | * whether it's in an area which is supposed to be copied or | ||
189 | * overwritten, and different in the source and destination buffers. | ||
190 | * So if the DMA engine doesn't copy exactly what we tell it to copy, | ||
191 | * we'll notice. | ||
192 | */ | ||
193 | static int dmatest_func(void *data) | ||
194 | { | ||
195 | struct dmatest_thread *thread = data; | ||
196 | struct dma_chan *chan; | ||
197 | const char *thread_name; | ||
198 | unsigned int src_off, dst_off, len; | ||
199 | unsigned int error_count; | ||
200 | unsigned int failed_tests = 0; | ||
201 | unsigned int total_tests = 0; | ||
202 | dma_cookie_t cookie; | ||
203 | enum dma_status status; | ||
204 | int ret; | ||
205 | |||
206 | thread_name = current->comm; | ||
207 | |||
208 | ret = -ENOMEM; | ||
209 | thread->srcbuf = kmalloc(test_buf_size, GFP_KERNEL); | ||
210 | if (!thread->srcbuf) | ||
211 | goto err_srcbuf; | ||
212 | thread->dstbuf = kmalloc(test_buf_size, GFP_KERNEL); | ||
213 | if (!thread->dstbuf) | ||
214 | goto err_dstbuf; | ||
215 | |||
216 | smp_rmb(); | ||
217 | chan = thread->chan; | ||
218 | dma_chan_get(chan); | ||
219 | |||
220 | while (!kthread_should_stop()) { | ||
221 | total_tests++; | ||
222 | |||
223 | len = dmatest_random() % test_buf_size + 1; | ||
224 | src_off = dmatest_random() % (test_buf_size - len + 1); | ||
225 | dst_off = dmatest_random() % (test_buf_size - len + 1); | ||
226 | |||
227 | dmatest_init_srcbuf(thread->srcbuf, src_off, len); | ||
228 | dmatest_init_dstbuf(thread->dstbuf, dst_off, len); | ||
229 | |||
230 | cookie = dma_async_memcpy_buf_to_buf(chan, | ||
231 | thread->dstbuf + dst_off, | ||
232 | thread->srcbuf + src_off, | ||
233 | len); | ||
234 | if (dma_submit_error(cookie)) { | ||
235 | pr_warning("%s: #%u: submit error %d with src_off=0x%x " | ||
236 | "dst_off=0x%x len=0x%x\n", | ||
237 | thread_name, total_tests - 1, cookie, | ||
238 | src_off, dst_off, len); | ||
239 | msleep(100); | ||
240 | failed_tests++; | ||
241 | continue; | ||
242 | } | ||
243 | dma_async_memcpy_issue_pending(chan); | ||
244 | |||
245 | do { | ||
246 | msleep(1); | ||
247 | status = dma_async_memcpy_complete( | ||
248 | chan, cookie, NULL, NULL); | ||
249 | } while (status == DMA_IN_PROGRESS); | ||
250 | |||
251 | if (status == DMA_ERROR) { | ||
252 | pr_warning("%s: #%u: error during copy\n", | ||
253 | thread_name, total_tests - 1); | ||
254 | failed_tests++; | ||
255 | continue; | ||
256 | } | ||
257 | |||
258 | error_count = 0; | ||
259 | |||
260 | pr_debug("%s: verifying source buffer...\n", thread_name); | ||
261 | error_count += dmatest_verify(thread->srcbuf, 0, src_off, | ||
262 | 0, PATTERN_SRC, true); | ||
263 | error_count += dmatest_verify(thread->srcbuf, src_off, | ||
264 | src_off + len, src_off, | ||
265 | PATTERN_SRC | PATTERN_COPY, true); | ||
266 | error_count += dmatest_verify(thread->srcbuf, src_off + len, | ||
267 | test_buf_size, src_off + len, | ||
268 | PATTERN_SRC, true); | ||
269 | |||
270 | pr_debug("%s: verifying dest buffer...\n", | ||
271 | thread->task->comm); | ||
272 | error_count += dmatest_verify(thread->dstbuf, 0, dst_off, | ||
273 | 0, PATTERN_DST, false); | ||
274 | error_count += dmatest_verify(thread->dstbuf, dst_off, | ||
275 | dst_off + len, src_off, | ||
276 | PATTERN_SRC | PATTERN_COPY, false); | ||
277 | error_count += dmatest_verify(thread->dstbuf, dst_off + len, | ||
278 | test_buf_size, dst_off + len, | ||
279 | PATTERN_DST, false); | ||
280 | |||
281 | if (error_count) { | ||
282 | pr_warning("%s: #%u: %u errors with " | ||
283 | "src_off=0x%x dst_off=0x%x len=0x%x\n", | ||
284 | thread_name, total_tests - 1, error_count, | ||
285 | src_off, dst_off, len); | ||
286 | failed_tests++; | ||
287 | } else { | ||
288 | pr_debug("%s: #%u: No errors with " | ||
289 | "src_off=0x%x dst_off=0x%x len=0x%x\n", | ||
290 | thread_name, total_tests - 1, | ||
291 | src_off, dst_off, len); | ||
292 | } | ||
293 | } | ||
294 | |||
295 | ret = 0; | ||
296 | dma_chan_put(chan); | ||
297 | kfree(thread->dstbuf); | ||
298 | err_dstbuf: | ||
299 | kfree(thread->srcbuf); | ||
300 | err_srcbuf: | ||
301 | pr_notice("%s: terminating after %u tests, %u failures (status %d)\n", | ||
302 | thread_name, total_tests, failed_tests, ret); | ||
303 | return ret; | ||
304 | } | ||
305 | |||
306 | static void dmatest_cleanup_channel(struct dmatest_chan *dtc) | ||
307 | { | ||
308 | struct dmatest_thread *thread; | ||
309 | struct dmatest_thread *_thread; | ||
310 | int ret; | ||
311 | |||
312 | list_for_each_entry_safe(thread, _thread, &dtc->threads, node) { | ||
313 | ret = kthread_stop(thread->task); | ||
314 | pr_debug("dmatest: thread %s exited with status %d\n", | ||
315 | thread->task->comm, ret); | ||
316 | list_del(&thread->node); | ||
317 | kfree(thread); | ||
318 | } | ||
319 | kfree(dtc); | ||
320 | } | ||
321 | |||
322 | static enum dma_state_client dmatest_add_channel(struct dma_chan *chan) | ||
323 | { | ||
324 | struct dmatest_chan *dtc; | ||
325 | struct dmatest_thread *thread; | ||
326 | unsigned int i; | ||
327 | |||
328 | dtc = kmalloc(sizeof(struct dmatest_chan), GFP_ATOMIC); | ||
329 | if (!dtc) { | ||
330 | pr_warning("dmatest: No memory for %s\n", chan->dev.bus_id); | ||
331 | return DMA_NAK; | ||
332 | } | ||
333 | |||
334 | dtc->chan = chan; | ||
335 | INIT_LIST_HEAD(&dtc->threads); | ||
336 | |||
337 | for (i = 0; i < threads_per_chan; i++) { | ||
338 | thread = kzalloc(sizeof(struct dmatest_thread), GFP_KERNEL); | ||
339 | if (!thread) { | ||
340 | pr_warning("dmatest: No memory for %s-test%u\n", | ||
341 | chan->dev.bus_id, i); | ||
342 | break; | ||
343 | } | ||
344 | thread->chan = dtc->chan; | ||
345 | smp_wmb(); | ||
346 | thread->task = kthread_run(dmatest_func, thread, "%s-test%u", | ||
347 | chan->dev.bus_id, i); | ||
348 | if (IS_ERR(thread->task)) { | ||
349 | pr_warning("dmatest: Failed to run thread %s-test%u\n", | ||
350 | chan->dev.bus_id, i); | ||
351 | kfree(thread); | ||
352 | break; | ||
353 | } | ||
354 | |||
355 | /* srcbuf and dstbuf are allocated by the thread itself */ | ||
356 | |||
357 | list_add_tail(&thread->node, &dtc->threads); | ||
358 | } | ||
359 | |||
360 | pr_info("dmatest: Started %u threads using %s\n", i, chan->dev.bus_id); | ||
361 | |||
362 | list_add_tail(&dtc->node, &dmatest_channels); | ||
363 | nr_channels++; | ||
364 | |||
365 | return DMA_ACK; | ||
366 | } | ||
367 | |||
368 | static enum dma_state_client dmatest_remove_channel(struct dma_chan *chan) | ||
369 | { | ||
370 | struct dmatest_chan *dtc, *_dtc; | ||
371 | |||
372 | list_for_each_entry_safe(dtc, _dtc, &dmatest_channels, node) { | ||
373 | if (dtc->chan == chan) { | ||
374 | list_del(&dtc->node); | ||
375 | dmatest_cleanup_channel(dtc); | ||
376 | pr_debug("dmatest: lost channel %s\n", | ||
377 | chan->dev.bus_id); | ||
378 | return DMA_ACK; | ||
379 | } | ||
380 | } | ||
381 | |||
382 | return DMA_DUP; | ||
383 | } | ||
384 | |||
385 | /* | ||
386 | * Start testing threads as new channels are assigned to us, and kill | ||
387 | * them when the channels go away. | ||
388 | * | ||
389 | * When we unregister the client, all channels are removed so this | ||
390 | * will also take care of cleaning things up when the module is | ||
391 | * unloaded. | ||
392 | */ | ||
393 | static enum dma_state_client | ||
394 | dmatest_event(struct dma_client *client, struct dma_chan *chan, | ||
395 | enum dma_state state) | ||
396 | { | ||
397 | enum dma_state_client ack = DMA_NAK; | ||
398 | |||
399 | switch (state) { | ||
400 | case DMA_RESOURCE_AVAILABLE: | ||
401 | if (!dmatest_match_channel(chan) | ||
402 | || !dmatest_match_device(chan->device)) | ||
403 | ack = DMA_DUP; | ||
404 | else if (max_channels && nr_channels >= max_channels) | ||
405 | ack = DMA_NAK; | ||
406 | else | ||
407 | ack = dmatest_add_channel(chan); | ||
408 | break; | ||
409 | |||
410 | case DMA_RESOURCE_REMOVED: | ||
411 | ack = dmatest_remove_channel(chan); | ||
412 | break; | ||
413 | |||
414 | default: | ||
415 | pr_info("dmatest: Unhandled event %u (%s)\n", | ||
416 | state, chan->dev.bus_id); | ||
417 | break; | ||
418 | } | ||
419 | |||
420 | return ack; | ||
421 | } | ||
422 | |||
423 | static struct dma_client dmatest_client = { | ||
424 | .event_callback = dmatest_event, | ||
425 | }; | ||
426 | |||
427 | static int __init dmatest_init(void) | ||
428 | { | ||
429 | dma_cap_set(DMA_MEMCPY, dmatest_client.cap_mask); | ||
430 | dma_async_client_register(&dmatest_client); | ||
431 | dma_async_client_chan_request(&dmatest_client); | ||
432 | |||
433 | return 0; | ||
434 | } | ||
435 | module_init(dmatest_init); | ||
436 | |||
437 | static void __exit dmatest_exit(void) | ||
438 | { | ||
439 | dma_async_client_unregister(&dmatest_client); | ||
440 | } | ||
441 | module_exit(dmatest_exit); | ||
442 | |||
443 | MODULE_AUTHOR("Haavard Skinnemoen <hskinnemoen@atmel.com>"); | ||
444 | MODULE_LICENSE("GPL v2"); | ||