diff options
Diffstat (limited to 'drivers/pci/endpoint/functions/pci-epf-test.c')
-rw-r--r-- | drivers/pci/endpoint/functions/pci-epf-test.c | 510 |
1 files changed, 510 insertions, 0 deletions
diff --git a/drivers/pci/endpoint/functions/pci-epf-test.c b/drivers/pci/endpoint/functions/pci-epf-test.c new file mode 100644 index 000000000000..53fff8030337 --- /dev/null +++ b/drivers/pci/endpoint/functions/pci-epf-test.c | |||
@@ -0,0 +1,510 @@ | |||
1 | /** | ||
2 | * Test driver to test endpoint functionality | ||
3 | * | ||
4 | * Copyright (C) 2017 Texas Instruments | ||
5 | * Author: Kishon Vijay Abraham I <kishon@ti.com> | ||
6 | * | ||
7 | * This program is free software: you can redistribute it and/or modify | ||
8 | * it under the terms of the GNU General Public License version 2 of | ||
9 | * the License as published by the Free Software Foundation. | ||
10 | * | ||
11 | * This program is distributed in the hope that it will be useful, | ||
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
14 | * GNU General Public License for more details. | ||
15 | * | ||
16 | * You should have received a copy of the GNU General Public License | ||
17 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
18 | */ | ||
19 | |||
20 | #include <linux/crc32.h> | ||
21 | #include <linux/delay.h> | ||
22 | #include <linux/io.h> | ||
23 | #include <linux/module.h> | ||
24 | #include <linux/slab.h> | ||
25 | #include <linux/pci_ids.h> | ||
26 | #include <linux/random.h> | ||
27 | |||
28 | #include <linux/pci-epc.h> | ||
29 | #include <linux/pci-epf.h> | ||
30 | #include <linux/pci_regs.h> | ||
31 | |||
32 | #define COMMAND_RAISE_LEGACY_IRQ BIT(0) | ||
33 | #define COMMAND_RAISE_MSI_IRQ BIT(1) | ||
34 | #define MSI_NUMBER_SHIFT 2 | ||
35 | #define MSI_NUMBER_MASK (0x3f << MSI_NUMBER_SHIFT) | ||
36 | #define COMMAND_READ BIT(8) | ||
37 | #define COMMAND_WRITE BIT(9) | ||
38 | #define COMMAND_COPY BIT(10) | ||
39 | |||
40 | #define STATUS_READ_SUCCESS BIT(0) | ||
41 | #define STATUS_READ_FAIL BIT(1) | ||
42 | #define STATUS_WRITE_SUCCESS BIT(2) | ||
43 | #define STATUS_WRITE_FAIL BIT(3) | ||
44 | #define STATUS_COPY_SUCCESS BIT(4) | ||
45 | #define STATUS_COPY_FAIL BIT(5) | ||
46 | #define STATUS_IRQ_RAISED BIT(6) | ||
47 | #define STATUS_SRC_ADDR_INVALID BIT(7) | ||
48 | #define STATUS_DST_ADDR_INVALID BIT(8) | ||
49 | |||
50 | #define TIMER_RESOLUTION 1 | ||
51 | |||
52 | static struct workqueue_struct *kpcitest_workqueue; | ||
53 | |||
54 | struct pci_epf_test { | ||
55 | void *reg[6]; | ||
56 | struct pci_epf *epf; | ||
57 | struct delayed_work cmd_handler; | ||
58 | }; | ||
59 | |||
60 | struct pci_epf_test_reg { | ||
61 | u32 magic; | ||
62 | u32 command; | ||
63 | u32 status; | ||
64 | u64 src_addr; | ||
65 | u64 dst_addr; | ||
66 | u32 size; | ||
67 | u32 checksum; | ||
68 | } __packed; | ||
69 | |||
70 | static struct pci_epf_header test_header = { | ||
71 | .vendorid = PCI_ANY_ID, | ||
72 | .deviceid = PCI_ANY_ID, | ||
73 | .baseclass_code = PCI_CLASS_OTHERS, | ||
74 | .interrupt_pin = PCI_INTERRUPT_INTA, | ||
75 | }; | ||
76 | |||
77 | static int bar_size[] = { 512, 1024, 16384, 131072, 1048576 }; | ||
78 | |||
79 | static int pci_epf_test_copy(struct pci_epf_test *epf_test) | ||
80 | { | ||
81 | int ret; | ||
82 | void __iomem *src_addr; | ||
83 | void __iomem *dst_addr; | ||
84 | phys_addr_t src_phys_addr; | ||
85 | phys_addr_t dst_phys_addr; | ||
86 | struct pci_epf *epf = epf_test->epf; | ||
87 | struct device *dev = &epf->dev; | ||
88 | struct pci_epc *epc = epf->epc; | ||
89 | struct pci_epf_test_reg *reg = epf_test->reg[0]; | ||
90 | |||
91 | src_addr = pci_epc_mem_alloc_addr(epc, &src_phys_addr, reg->size); | ||
92 | if (!src_addr) { | ||
93 | dev_err(dev, "failed to allocate source address\n"); | ||
94 | reg->status = STATUS_SRC_ADDR_INVALID; | ||
95 | ret = -ENOMEM; | ||
96 | goto err; | ||
97 | } | ||
98 | |||
99 | ret = pci_epc_map_addr(epc, src_phys_addr, reg->src_addr, reg->size); | ||
100 | if (ret) { | ||
101 | dev_err(dev, "failed to map source address\n"); | ||
102 | reg->status = STATUS_SRC_ADDR_INVALID; | ||
103 | goto err_src_addr; | ||
104 | } | ||
105 | |||
106 | dst_addr = pci_epc_mem_alloc_addr(epc, &dst_phys_addr, reg->size); | ||
107 | if (!dst_addr) { | ||
108 | dev_err(dev, "failed to allocate destination address\n"); | ||
109 | reg->status = STATUS_DST_ADDR_INVALID; | ||
110 | ret = -ENOMEM; | ||
111 | goto err_src_map_addr; | ||
112 | } | ||
113 | |||
114 | ret = pci_epc_map_addr(epc, dst_phys_addr, reg->dst_addr, reg->size); | ||
115 | if (ret) { | ||
116 | dev_err(dev, "failed to map destination address\n"); | ||
117 | reg->status = STATUS_DST_ADDR_INVALID; | ||
118 | goto err_dst_addr; | ||
119 | } | ||
120 | |||
121 | memcpy(dst_addr, src_addr, reg->size); | ||
122 | |||
123 | pci_epc_unmap_addr(epc, dst_phys_addr); | ||
124 | |||
125 | err_dst_addr: | ||
126 | pci_epc_mem_free_addr(epc, dst_phys_addr, dst_addr, reg->size); | ||
127 | |||
128 | err_src_map_addr: | ||
129 | pci_epc_unmap_addr(epc, src_phys_addr); | ||
130 | |||
131 | err_src_addr: | ||
132 | pci_epc_mem_free_addr(epc, src_phys_addr, src_addr, reg->size); | ||
133 | |||
134 | err: | ||
135 | return ret; | ||
136 | } | ||
137 | |||
138 | static int pci_epf_test_read(struct pci_epf_test *epf_test) | ||
139 | { | ||
140 | int ret; | ||
141 | void __iomem *src_addr; | ||
142 | void *buf; | ||
143 | u32 crc32; | ||
144 | phys_addr_t phys_addr; | ||
145 | struct pci_epf *epf = epf_test->epf; | ||
146 | struct device *dev = &epf->dev; | ||
147 | struct pci_epc *epc = epf->epc; | ||
148 | struct pci_epf_test_reg *reg = epf_test->reg[0]; | ||
149 | |||
150 | src_addr = pci_epc_mem_alloc_addr(epc, &phys_addr, reg->size); | ||
151 | if (!src_addr) { | ||
152 | dev_err(dev, "failed to allocate address\n"); | ||
153 | reg->status = STATUS_SRC_ADDR_INVALID; | ||
154 | ret = -ENOMEM; | ||
155 | goto err; | ||
156 | } | ||
157 | |||
158 | ret = pci_epc_map_addr(epc, phys_addr, reg->src_addr, reg->size); | ||
159 | if (ret) { | ||
160 | dev_err(dev, "failed to map address\n"); | ||
161 | reg->status = STATUS_SRC_ADDR_INVALID; | ||
162 | goto err_addr; | ||
163 | } | ||
164 | |||
165 | buf = kzalloc(reg->size, GFP_KERNEL); | ||
166 | if (!buf) { | ||
167 | ret = -ENOMEM; | ||
168 | goto err_map_addr; | ||
169 | } | ||
170 | |||
171 | memcpy(buf, src_addr, reg->size); | ||
172 | |||
173 | crc32 = crc32_le(~0, buf, reg->size); | ||
174 | if (crc32 != reg->checksum) | ||
175 | ret = -EIO; | ||
176 | |||
177 | kfree(buf); | ||
178 | |||
179 | err_map_addr: | ||
180 | pci_epc_unmap_addr(epc, phys_addr); | ||
181 | |||
182 | err_addr: | ||
183 | pci_epc_mem_free_addr(epc, phys_addr, src_addr, reg->size); | ||
184 | |||
185 | err: | ||
186 | return ret; | ||
187 | } | ||
188 | |||
189 | static int pci_epf_test_write(struct pci_epf_test *epf_test) | ||
190 | { | ||
191 | int ret; | ||
192 | void __iomem *dst_addr; | ||
193 | void *buf; | ||
194 | phys_addr_t phys_addr; | ||
195 | struct pci_epf *epf = epf_test->epf; | ||
196 | struct device *dev = &epf->dev; | ||
197 | struct pci_epc *epc = epf->epc; | ||
198 | struct pci_epf_test_reg *reg = epf_test->reg[0]; | ||
199 | |||
200 | dst_addr = pci_epc_mem_alloc_addr(epc, &phys_addr, reg->size); | ||
201 | if (!dst_addr) { | ||
202 | dev_err(dev, "failed to allocate address\n"); | ||
203 | reg->status = STATUS_DST_ADDR_INVALID; | ||
204 | ret = -ENOMEM; | ||
205 | goto err; | ||
206 | } | ||
207 | |||
208 | ret = pci_epc_map_addr(epc, phys_addr, reg->dst_addr, reg->size); | ||
209 | if (ret) { | ||
210 | dev_err(dev, "failed to map address\n"); | ||
211 | reg->status = STATUS_DST_ADDR_INVALID; | ||
212 | goto err_addr; | ||
213 | } | ||
214 | |||
215 | buf = kzalloc(reg->size, GFP_KERNEL); | ||
216 | if (!buf) { | ||
217 | ret = -ENOMEM; | ||
218 | goto err_map_addr; | ||
219 | } | ||
220 | |||
221 | get_random_bytes(buf, reg->size); | ||
222 | reg->checksum = crc32_le(~0, buf, reg->size); | ||
223 | |||
224 | memcpy(dst_addr, buf, reg->size); | ||
225 | |||
226 | /* | ||
227 | * wait 1ms inorder for the write to complete. Without this delay L3 | ||
228 | * error in observed in the host system. | ||
229 | */ | ||
230 | mdelay(1); | ||
231 | |||
232 | kfree(buf); | ||
233 | |||
234 | err_map_addr: | ||
235 | pci_epc_unmap_addr(epc, phys_addr); | ||
236 | |||
237 | err_addr: | ||
238 | pci_epc_mem_free_addr(epc, phys_addr, dst_addr, reg->size); | ||
239 | |||
240 | err: | ||
241 | return ret; | ||
242 | } | ||
243 | |||
244 | static void pci_epf_test_raise_irq(struct pci_epf_test *epf_test) | ||
245 | { | ||
246 | u8 irq; | ||
247 | u8 msi_count; | ||
248 | struct pci_epf *epf = epf_test->epf; | ||
249 | struct pci_epc *epc = epf->epc; | ||
250 | struct pci_epf_test_reg *reg = epf_test->reg[0]; | ||
251 | |||
252 | reg->status |= STATUS_IRQ_RAISED; | ||
253 | msi_count = pci_epc_get_msi(epc); | ||
254 | irq = (reg->command & MSI_NUMBER_MASK) >> MSI_NUMBER_SHIFT; | ||
255 | if (irq > msi_count || msi_count <= 0) | ||
256 | pci_epc_raise_irq(epc, PCI_EPC_IRQ_LEGACY, 0); | ||
257 | else | ||
258 | pci_epc_raise_irq(epc, PCI_EPC_IRQ_MSI, irq); | ||
259 | } | ||
260 | |||
261 | static void pci_epf_test_cmd_handler(struct work_struct *work) | ||
262 | { | ||
263 | int ret; | ||
264 | u8 irq; | ||
265 | u8 msi_count; | ||
266 | struct pci_epf_test *epf_test = container_of(work, struct pci_epf_test, | ||
267 | cmd_handler.work); | ||
268 | struct pci_epf *epf = epf_test->epf; | ||
269 | struct pci_epc *epc = epf->epc; | ||
270 | struct pci_epf_test_reg *reg = epf_test->reg[0]; | ||
271 | |||
272 | if (!reg->command) | ||
273 | goto reset_handler; | ||
274 | |||
275 | if (reg->command & COMMAND_RAISE_LEGACY_IRQ) { | ||
276 | reg->status = STATUS_IRQ_RAISED; | ||
277 | pci_epc_raise_irq(epc, PCI_EPC_IRQ_LEGACY, 0); | ||
278 | goto reset_handler; | ||
279 | } | ||
280 | |||
281 | if (reg->command & COMMAND_WRITE) { | ||
282 | ret = pci_epf_test_write(epf_test); | ||
283 | if (ret) | ||
284 | reg->status |= STATUS_WRITE_FAIL; | ||
285 | else | ||
286 | reg->status |= STATUS_WRITE_SUCCESS; | ||
287 | pci_epf_test_raise_irq(epf_test); | ||
288 | goto reset_handler; | ||
289 | } | ||
290 | |||
291 | if (reg->command & COMMAND_READ) { | ||
292 | ret = pci_epf_test_read(epf_test); | ||
293 | if (!ret) | ||
294 | reg->status |= STATUS_READ_SUCCESS; | ||
295 | else | ||
296 | reg->status |= STATUS_READ_FAIL; | ||
297 | pci_epf_test_raise_irq(epf_test); | ||
298 | goto reset_handler; | ||
299 | } | ||
300 | |||
301 | if (reg->command & COMMAND_COPY) { | ||
302 | ret = pci_epf_test_copy(epf_test); | ||
303 | if (!ret) | ||
304 | reg->status |= STATUS_COPY_SUCCESS; | ||
305 | else | ||
306 | reg->status |= STATUS_COPY_FAIL; | ||
307 | pci_epf_test_raise_irq(epf_test); | ||
308 | goto reset_handler; | ||
309 | } | ||
310 | |||
311 | if (reg->command & COMMAND_RAISE_MSI_IRQ) { | ||
312 | msi_count = pci_epc_get_msi(epc); | ||
313 | irq = (reg->command & MSI_NUMBER_MASK) >> MSI_NUMBER_SHIFT; | ||
314 | if (irq > msi_count || msi_count <= 0) | ||
315 | goto reset_handler; | ||
316 | reg->status = STATUS_IRQ_RAISED; | ||
317 | pci_epc_raise_irq(epc, PCI_EPC_IRQ_MSI, irq); | ||
318 | goto reset_handler; | ||
319 | } | ||
320 | |||
321 | reset_handler: | ||
322 | reg->command = 0; | ||
323 | |||
324 | queue_delayed_work(kpcitest_workqueue, &epf_test->cmd_handler, | ||
325 | msecs_to_jiffies(1)); | ||
326 | } | ||
327 | |||
328 | static void pci_epf_test_linkup(struct pci_epf *epf) | ||
329 | { | ||
330 | struct pci_epf_test *epf_test = epf_get_drvdata(epf); | ||
331 | |||
332 | queue_delayed_work(kpcitest_workqueue, &epf_test->cmd_handler, | ||
333 | msecs_to_jiffies(1)); | ||
334 | } | ||
335 | |||
336 | static void pci_epf_test_unbind(struct pci_epf *epf) | ||
337 | { | ||
338 | struct pci_epf_test *epf_test = epf_get_drvdata(epf); | ||
339 | struct pci_epc *epc = epf->epc; | ||
340 | int bar; | ||
341 | |||
342 | cancel_delayed_work(&epf_test->cmd_handler); | ||
343 | pci_epc_stop(epc); | ||
344 | for (bar = BAR_0; bar <= BAR_5; bar++) { | ||
345 | if (epf_test->reg[bar]) { | ||
346 | pci_epf_free_space(epf, epf_test->reg[bar], bar); | ||
347 | pci_epc_clear_bar(epc, bar); | ||
348 | } | ||
349 | } | ||
350 | } | ||
351 | |||
352 | static int pci_epf_test_set_bar(struct pci_epf *epf) | ||
353 | { | ||
354 | int flags; | ||
355 | int bar; | ||
356 | int ret; | ||
357 | struct pci_epf_bar *epf_bar; | ||
358 | struct pci_epc *epc = epf->epc; | ||
359 | struct device *dev = &epf->dev; | ||
360 | struct pci_epf_test *epf_test = epf_get_drvdata(epf); | ||
361 | |||
362 | flags = PCI_BASE_ADDRESS_SPACE_MEMORY | PCI_BASE_ADDRESS_MEM_TYPE_32; | ||
363 | if (sizeof(dma_addr_t) == 0x8) | ||
364 | flags |= PCI_BASE_ADDRESS_MEM_TYPE_64; | ||
365 | |||
366 | for (bar = BAR_0; bar <= BAR_5; bar++) { | ||
367 | epf_bar = &epf->bar[bar]; | ||
368 | ret = pci_epc_set_bar(epc, bar, epf_bar->phys_addr, | ||
369 | epf_bar->size, flags); | ||
370 | if (ret) { | ||
371 | pci_epf_free_space(epf, epf_test->reg[bar], bar); | ||
372 | dev_err(dev, "failed to set BAR%d\n", bar); | ||
373 | if (bar == BAR_0) | ||
374 | return ret; | ||
375 | } | ||
376 | } | ||
377 | |||
378 | return 0; | ||
379 | } | ||
380 | |||
381 | static int pci_epf_test_alloc_space(struct pci_epf *epf) | ||
382 | { | ||
383 | struct pci_epf_test *epf_test = epf_get_drvdata(epf); | ||
384 | struct device *dev = &epf->dev; | ||
385 | void *base; | ||
386 | int bar; | ||
387 | |||
388 | base = pci_epf_alloc_space(epf, sizeof(struct pci_epf_test_reg), | ||
389 | BAR_0); | ||
390 | if (!base) { | ||
391 | dev_err(dev, "failed to allocated register space\n"); | ||
392 | return -ENOMEM; | ||
393 | } | ||
394 | epf_test->reg[0] = base; | ||
395 | |||
396 | for (bar = BAR_1; bar <= BAR_5; bar++) { | ||
397 | base = pci_epf_alloc_space(epf, bar_size[bar - 1], bar); | ||
398 | if (!base) | ||
399 | dev_err(dev, "failed to allocate space for BAR%d\n", | ||
400 | bar); | ||
401 | epf_test->reg[bar] = base; | ||
402 | } | ||
403 | |||
404 | return 0; | ||
405 | } | ||
406 | |||
407 | static int pci_epf_test_bind(struct pci_epf *epf) | ||
408 | { | ||
409 | int ret; | ||
410 | struct pci_epf_header *header = epf->header; | ||
411 | struct pci_epc *epc = epf->epc; | ||
412 | struct device *dev = &epf->dev; | ||
413 | |||
414 | if (WARN_ON_ONCE(!epc)) | ||
415 | return -EINVAL; | ||
416 | |||
417 | ret = pci_epc_write_header(epc, header); | ||
418 | if (ret) { | ||
419 | dev_err(dev, "configuration header write failed\n"); | ||
420 | return ret; | ||
421 | } | ||
422 | |||
423 | ret = pci_epf_test_alloc_space(epf); | ||
424 | if (ret) | ||
425 | return ret; | ||
426 | |||
427 | ret = pci_epf_test_set_bar(epf); | ||
428 | if (ret) | ||
429 | return ret; | ||
430 | |||
431 | ret = pci_epc_set_msi(epc, epf->msi_interrupts); | ||
432 | if (ret) | ||
433 | return ret; | ||
434 | |||
435 | return 0; | ||
436 | } | ||
437 | |||
438 | static int pci_epf_test_probe(struct pci_epf *epf) | ||
439 | { | ||
440 | struct pci_epf_test *epf_test; | ||
441 | struct device *dev = &epf->dev; | ||
442 | |||
443 | epf_test = devm_kzalloc(dev, sizeof(*epf_test), GFP_KERNEL); | ||
444 | if (!epf_test) | ||
445 | return -ENOMEM; | ||
446 | |||
447 | epf->header = &test_header; | ||
448 | epf_test->epf = epf; | ||
449 | |||
450 | INIT_DELAYED_WORK(&epf_test->cmd_handler, pci_epf_test_cmd_handler); | ||
451 | |||
452 | epf_set_drvdata(epf, epf_test); | ||
453 | return 0; | ||
454 | } | ||
455 | |||
456 | static int pci_epf_test_remove(struct pci_epf *epf) | ||
457 | { | ||
458 | struct pci_epf_test *epf_test = epf_get_drvdata(epf); | ||
459 | |||
460 | kfree(epf_test); | ||
461 | return 0; | ||
462 | } | ||
463 | |||
464 | static struct pci_epf_ops ops = { | ||
465 | .unbind = pci_epf_test_unbind, | ||
466 | .bind = pci_epf_test_bind, | ||
467 | .linkup = pci_epf_test_linkup, | ||
468 | }; | ||
469 | |||
470 | static const struct pci_epf_device_id pci_epf_test_ids[] = { | ||
471 | { | ||
472 | .name = "pci_epf_test", | ||
473 | }, | ||
474 | {}, | ||
475 | }; | ||
476 | |||
477 | static struct pci_epf_driver test_driver = { | ||
478 | .driver.name = "pci_epf_test", | ||
479 | .probe = pci_epf_test_probe, | ||
480 | .remove = pci_epf_test_remove, | ||
481 | .id_table = pci_epf_test_ids, | ||
482 | .ops = &ops, | ||
483 | .owner = THIS_MODULE, | ||
484 | }; | ||
485 | |||
486 | static int __init pci_epf_test_init(void) | ||
487 | { | ||
488 | int ret; | ||
489 | |||
490 | kpcitest_workqueue = alloc_workqueue("kpcitest", | ||
491 | WQ_MEM_RECLAIM | WQ_HIGHPRI, 0); | ||
492 | ret = pci_epf_register_driver(&test_driver); | ||
493 | if (ret) { | ||
494 | pr_err("failed to register pci epf test driver --> %d\n", ret); | ||
495 | return ret; | ||
496 | } | ||
497 | |||
498 | return 0; | ||
499 | } | ||
500 | module_init(pci_epf_test_init); | ||
501 | |||
502 | static void __exit pci_epf_test_exit(void) | ||
503 | { | ||
504 | pci_epf_unregister_driver(&test_driver); | ||
505 | } | ||
506 | module_exit(pci_epf_test_exit); | ||
507 | |||
508 | MODULE_DESCRIPTION("PCI EPF TEST DRIVER"); | ||
509 | MODULE_AUTHOR("Kishon Vijay Abraham I <kishon@ti.com>"); | ||
510 | MODULE_LICENSE("GPL v2"); | ||