1 | /* |
2 | * This file is provided under a dual BSD/GPLv2 license. When using or |
3 | * redistributing this file, you may do so under either license. |
4 | * |
5 | * GPL LICENSE SUMMARY |
6 | * |
7 | * Copyright (C) 2015 EMC Corporation. All Rights Reserved. |
8 | * Copyright (C) 2017 T-Platforms All Rights Reserved. |
9 | * |
10 | * This program is free software; you can redistribute it and/or modify |
11 | * it under the terms of version 2 of the GNU General Public License as |
12 | * published by the Free Software Foundation. |
13 | * |
14 | * This program is distributed in the hope that it will be useful, but |
15 | * WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
17 | * General Public License for more details. |
18 | * |
19 | * BSD LICENSE |
20 | * |
21 | * Copyright (C) 2015 EMC Corporation. All Rights Reserved. |
22 | * Copyright (C) 2017 T-Platforms All Rights Reserved. |
23 | * |
24 | * Redistribution and use in source and binary forms, with or without |
25 | * modification, are permitted provided that the following conditions |
26 | * are met: |
27 | * |
28 | * * Redistributions of source code must retain the above copyright |
29 | * notice, this list of conditions and the following disclaimer. |
30 | * * Redistributions in binary form must reproduce the above copy |
31 | * notice, this list of conditions and the following disclaimer in |
32 | * the documentation and/or other materials provided with the |
33 | * distribution. |
34 | * * Neither the name of Intel Corporation nor the names of its |
35 | * contributors may be used to endorse or promote products derived |
36 | * from this software without specific prior written permission. |
37 | * |
38 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
39 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
40 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
41 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
42 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
43 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
44 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
45 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
46 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
47 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
48 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
49 | * |
50 | * PCIe NTB Debugging Tool Linux driver |
51 | */ |
52 | |
53 | /* |
54 | * How to use this tool, by example. |
55 | * |
56 | * Assuming $DBG_DIR is something like: |
57 | * '/sys/kernel/debug/ntb_tool/0000:00:03.0' |
58 | * Suppose aside from local device there is at least one remote device |
59 | * connected to NTB with index 0. |
60 | *----------------------------------------------------------------------------- |
61 | * Eg: check local/peer device information. |
62 | * |
63 | * # Get local device port number |
64 | * root@self# cat $DBG_DIR/port |
65 | * |
66 | * # Check local device functionality |
67 | * root@self# ls $DBG_DIR |
68 | * db msg1 msg_sts peer4/ port |
69 | * db_event msg2 peer0/ peer5/ spad0 |
70 | * db_mask msg3 peer1/ peer_db spad1 |
71 | * link msg_event peer2/ peer_db_mask spad2 |
72 | * msg0 msg_mask peer3/ peer_spad spad3 |
73 | * # As one can see it supports: |
74 | * # 1) four inbound message registers |
75 | * # 2) four inbound scratchpads |
76 | * # 3) up to six peer devices |
77 | * |
78 | * # Check peer device port number |
79 | * root@self# cat $DBG_DIR/peer0/port |
80 | * |
81 | * # Check peer device(s) functionality to be used |
82 | * root@self# ls $DBG_DIR/peer0 |
83 | * link mw_trans0 mw_trans6 port |
84 | * link_event mw_trans1 mw_trans7 spad0 |
85 | * msg0 mw_trans2 peer_mw_trans0 spad1 |
86 | * msg1 mw_trans3 peer_mw_trans1 spad2 |
87 | * msg2 mw_trans4 peer_mw_trans2 spad3 |
88 | * msg3 mw_trans5 peer_mw_trans3 |
89 | * # As one can see we got: |
90 | * # 1) four outbound message registers |
91 | * # 2) four outbound scratchpads |
92 | * # 3) eight inbound memory windows |
93 | * # 4) four outbound memory windows |
94 | *----------------------------------------------------------------------------- |
95 | * Eg: NTB link tests |
96 | * |
97 | * # Set local link up/down |
98 | * root@self# echo Y > $DBG_DIR/link |
99 | * root@self# echo N > $DBG_DIR/link |
100 | * |
101 | * # Check if link with peer device is up/down: |
102 | * root@self# cat $DBG_DIR/peer0/link |
103 | * |
104 | * # Block until the link is up/down |
105 | * root@self# echo Y > $DBG_DIR/peer0/link_event |
106 | * root@self# echo N > $DBG_DIR/peer0/link_event |
107 | *----------------------------------------------------------------------------- |
108 | * Eg: Doorbell registers tests (some functionality might be absent) |
109 | * |
110 | * # Set/clear/get local doorbell |
111 | * root@self# echo 's 1' > $DBG_DIR/db |
112 | * root@self# echo 'c 1' > $DBG_DIR/db |
113 | * root@self# cat $DBG_DIR/db |
114 | * |
115 | * # Set/clear/get local doorbell mask |
116 | * root@self# echo 's 1' > $DBG_DIR/db_mask |
117 | * root@self# echo 'c 1' > $DBG_DIR/db_mask |
118 | * root@self# cat $DBG_DIR/db_mask |
119 | * |
120 | * # Ring/clear/get peer doorbell |
121 | * root@peer# echo 's 1' > $DBG_DIR/peer_db |
122 | * root@peer# echo 'c 1' > $DBG_DIR/peer_db |
123 | * root@peer# cat $DBG_DIR/peer_db |
124 | * |
125 | * # Set/clear/get peer doorbell mask |
126 | * root@self# echo 's 1' > $DBG_DIR/peer_db_mask |
127 | * root@self# echo 'c 1' > $DBG_DIR/peer_db_mask |
128 | * root@self# cat $DBG_DIR/peer_db_mask |
129 | * |
130 | * # Block until local doorbell is set with specified value |
131 | * root@self# echo 1 > $DBG_DIR/db_event |
132 | *----------------------------------------------------------------------------- |
133 | * Eg: Message registers tests (functionality might be absent) |
134 | * |
135 | * # Set/clear/get in/out message registers status |
136 | * root@self# echo 's 1' > $DBG_DIR/msg_sts |
137 | * root@self# echo 'c 1' > $DBG_DIR/msg_sts |
138 | * root@self# cat $DBG_DIR/msg_sts |
139 | * |
140 | * # Set/clear in/out message registers mask |
141 | * root@self# echo 's 1' > $DBG_DIR/msg_mask |
142 | * root@self# echo 'c 1' > $DBG_DIR/msg_mask |
143 | * |
144 | * # Get inbound message register #0 value and source of port index |
145 | * root@self# cat $DBG_DIR/msg0 |
146 | * |
147 | * # Send some data to peer over outbound message register #0 |
148 | * root@self# echo 0x01020304 > $DBG_DIR/peer0/msg0 |
149 | *----------------------------------------------------------------------------- |
150 | * Eg: Scratchpad registers tests (functionality might be absent) |
151 | * |
152 | * # Write/read to/from local scratchpad register #0 |
153 | * root@peer# echo 0x01020304 > $DBG_DIR/spad0 |
154 | * root@peer# cat $DBG_DIR/spad0 |
155 | * |
156 | * # Write/read to/from peer scratchpad register #0 |
157 | * root@peer# echo 0x01020304 > $DBG_DIR/peer0/spad0 |
158 | * root@peer# cat $DBG_DIR/peer0/spad0 |
159 | *----------------------------------------------------------------------------- |
160 | * Eg: Memory windows tests |
161 | * |
162 | * # Create inbound memory window buffer of specified size/get its base address |
163 | * root@peer# echo 16384 > $DBG_DIR/peer0/mw_trans0 |
164 | * root@peer# cat $DBG_DIR/peer0/mw_trans0 |
165 | * |
166 | * # Write/read data to/from inbound memory window |
167 | * root@peer# echo Hello > $DBG_DIR/peer0/mw0 |
168 | * root@peer# head -c 7 $DBG_DIR/peer0/mw0 |
169 | * |
170 | * # Map outbound memory window/check it settings (on peer device) |
171 | * root@peer# echo 0xADD0BA5E:16384 > $DBG_DIR/peer0/peer_mw_trans0 |
172 | * root@peer# cat $DBG_DIR/peer0/peer_mw_trans0 |
173 | * |
174 | * # Write/read data to/from outbound memory window (on peer device) |
175 | * root@peer# echo olleH > $DBG_DIR/peer0/peer_mw0 |
176 | * root@peer# head -c 7 $DBG_DIR/peer0/peer_mw0 |
177 | */ |
178 | |
179 | #include <linux/init.h> |
180 | #include <linux/kernel.h> |
181 | #include <linux/module.h> |
182 | |
183 | #include <linux/debugfs.h> |
184 | #include <linux/dma-mapping.h> |
185 | #include <linux/pci.h> |
186 | #include <linux/slab.h> |
187 | #include <linux/uaccess.h> |
188 | |
189 | #include <linux/ntb.h> |
190 | |
191 | #define DRIVER_NAME "ntb_tool" |
192 | #define DRIVER_VERSION "2.0" |
193 | |
194 | MODULE_LICENSE("Dual BSD/GPL" ); |
195 | MODULE_VERSION(DRIVER_VERSION); |
196 | MODULE_AUTHOR("Allen Hubbe <Allen.Hubbe@emc.com>" ); |
197 | MODULE_DESCRIPTION("PCIe NTB Debugging Tool" ); |
198 | |
199 | /* |
200 | * Inbound and outbound memory windows descriptor. Union members selection |
201 | * depends on the MW type the structure describes. mm_base/dma_base are the |
202 | * virtual and DMA address of an inbound MW. io_base/tr_base are the MMIO |
203 | * mapped virtual and xlat addresses of an outbound MW respectively. |
204 | */ |
205 | struct tool_mw { |
206 | int widx; |
207 | int pidx; |
208 | struct tool_ctx *tc; |
209 | union { |
210 | u8 *mm_base; |
211 | u8 __iomem *io_base; |
212 | }; |
213 | union { |
214 | dma_addr_t dma_base; |
215 | u64 tr_base; |
216 | }; |
217 | resource_size_t size; |
218 | struct dentry *dbgfs_file; |
219 | }; |
220 | |
221 | /* |
222 | * Wrapper structure is used to distinguish the outbound MW peers reference |
223 | * within the corresponding DebugFS directory IO operation. |
224 | */ |
225 | struct tool_mw_wrap { |
226 | int pidx; |
227 | struct tool_mw *mw; |
228 | }; |
229 | |
230 | struct tool_msg { |
231 | int midx; |
232 | int pidx; |
233 | struct tool_ctx *tc; |
234 | }; |
235 | |
236 | struct tool_spad { |
237 | int sidx; |
238 | int pidx; |
239 | struct tool_ctx *tc; |
240 | }; |
241 | |
242 | struct tool_peer { |
243 | int pidx; |
244 | struct tool_ctx *tc; |
245 | int inmw_cnt; |
246 | struct tool_mw *inmws; |
247 | int outmw_cnt; |
248 | struct tool_mw_wrap *outmws; |
249 | int outmsg_cnt; |
250 | struct tool_msg *outmsgs; |
251 | int outspad_cnt; |
252 | struct tool_spad *outspads; |
253 | struct dentry *dbgfs_dir; |
254 | }; |
255 | |
256 | struct tool_ctx { |
257 | struct ntb_dev *ntb; |
258 | wait_queue_head_t link_wq; |
259 | wait_queue_head_t db_wq; |
260 | wait_queue_head_t msg_wq; |
261 | int outmw_cnt; |
262 | struct tool_mw *outmws; |
263 | int peer_cnt; |
264 | struct tool_peer *peers; |
265 | int inmsg_cnt; |
266 | struct tool_msg *inmsgs; |
267 | int inspad_cnt; |
268 | struct tool_spad *inspads; |
269 | struct dentry *dbgfs_dir; |
270 | }; |
271 | |
272 | #define TOOL_FOPS_RDWR(__name, __read, __write) \ |
273 | const struct file_operations __name = { \ |
274 | .owner = THIS_MODULE, \ |
275 | .open = simple_open, \ |
276 | .read = __read, \ |
277 | .write = __write, \ |
278 | } |
279 | |
280 | #define TOOL_BUF_LEN 32 |
281 | |
282 | static struct dentry *tool_dbgfs_topdir; |
283 | |
284 | /*============================================================================== |
285 | * NTB events handlers |
286 | *============================================================================== |
287 | */ |
288 | |
289 | static void tool_link_event(void *ctx) |
290 | { |
291 | struct tool_ctx *tc = ctx; |
292 | enum ntb_speed speed; |
293 | enum ntb_width width; |
294 | int up; |
295 | |
296 | up = ntb_link_is_up(ntb: tc->ntb, speed: &speed, width: &width); |
297 | |
298 | dev_dbg(&tc->ntb->dev, "link is %s speed %d width %d\n" , |
299 | up ? "up" : "down" , speed, width); |
300 | |
301 | wake_up(&tc->link_wq); |
302 | } |
303 | |
304 | static void tool_db_event(void *ctx, int vec) |
305 | { |
306 | struct tool_ctx *tc = ctx; |
307 | u64 db_bits, db_mask; |
308 | |
309 | db_mask = ntb_db_vector_mask(ntb: tc->ntb, vector: vec); |
310 | db_bits = ntb_db_read(ntb: tc->ntb); |
311 | |
312 | dev_dbg(&tc->ntb->dev, "doorbell vec %d mask %#llx bits %#llx\n" , |
313 | vec, db_mask, db_bits); |
314 | |
315 | wake_up(&tc->db_wq); |
316 | } |
317 | |
318 | static void tool_msg_event(void *ctx) |
319 | { |
320 | struct tool_ctx *tc = ctx; |
321 | u64 msg_sts; |
322 | |
323 | msg_sts = ntb_msg_read_sts(ntb: tc->ntb); |
324 | |
325 | dev_dbg(&tc->ntb->dev, "message bits %#llx\n" , msg_sts); |
326 | |
327 | wake_up(&tc->msg_wq); |
328 | } |
329 | |
330 | static const struct ntb_ctx_ops tool_ops = { |
331 | .link_event = tool_link_event, |
332 | .db_event = tool_db_event, |
333 | .msg_event = tool_msg_event |
334 | }; |
335 | |
336 | /*============================================================================== |
337 | * Common read/write methods |
338 | *============================================================================== |
339 | */ |
340 | |
341 | static ssize_t tool_fn_read(struct tool_ctx *tc, char __user *ubuf, |
342 | size_t size, loff_t *offp, |
343 | u64 (*fn_read)(struct ntb_dev *)) |
344 | { |
345 | size_t buf_size; |
346 | char buf[TOOL_BUF_LEN]; |
347 | ssize_t pos; |
348 | |
349 | if (!fn_read) |
350 | return -EINVAL; |
351 | |
352 | buf_size = min(size, sizeof(buf)); |
353 | |
354 | pos = scnprintf(buf, size: buf_size, fmt: "%#llx\n" , fn_read(tc->ntb)); |
355 | |
356 | return simple_read_from_buffer(to: ubuf, count: size, ppos: offp, from: buf, available: pos); |
357 | } |
358 | |
359 | static ssize_t tool_fn_write(struct tool_ctx *tc, |
360 | const char __user *ubuf, |
361 | size_t size, loff_t *offp, |
362 | int (*fn_set)(struct ntb_dev *, u64), |
363 | int (*fn_clear)(struct ntb_dev *, u64)) |
364 | { |
365 | char *buf, cmd; |
366 | ssize_t ret; |
367 | u64 bits; |
368 | int n; |
369 | |
370 | if (*offp) |
371 | return 0; |
372 | |
373 | buf = memdup_user_nul(ubuf, size); |
374 | if (IS_ERR(ptr: buf)) |
375 | return PTR_ERR(ptr: buf); |
376 | |
377 | n = sscanf(buf, "%c %lli" , &cmd, &bits); |
378 | |
379 | kfree(objp: buf); |
380 | |
381 | if (n != 2) { |
382 | ret = -EINVAL; |
383 | } else if (cmd == 's') { |
384 | if (!fn_set) |
385 | ret = -EINVAL; |
386 | else |
387 | ret = fn_set(tc->ntb, bits); |
388 | } else if (cmd == 'c') { |
389 | if (!fn_clear) |
390 | ret = -EINVAL; |
391 | else |
392 | ret = fn_clear(tc->ntb, bits); |
393 | } else { |
394 | ret = -EINVAL; |
395 | } |
396 | |
397 | return ret ? : size; |
398 | } |
399 | |
400 | /*============================================================================== |
401 | * Port read/write methods |
402 | *============================================================================== |
403 | */ |
404 | |
405 | static ssize_t tool_port_read(struct file *filep, char __user *ubuf, |
406 | size_t size, loff_t *offp) |
407 | { |
408 | struct tool_ctx *tc = filep->private_data; |
409 | char buf[TOOL_BUF_LEN]; |
410 | int pos; |
411 | |
412 | pos = scnprintf(buf, size: sizeof(buf), fmt: "%d\n" , ntb_port_number(ntb: tc->ntb)); |
413 | |
414 | return simple_read_from_buffer(to: ubuf, count: size, ppos: offp, from: buf, available: pos); |
415 | } |
416 | |
417 | static TOOL_FOPS_RDWR(tool_port_fops, |
418 | tool_port_read, |
419 | NULL); |
420 | |
421 | static ssize_t tool_peer_port_read(struct file *filep, char __user *ubuf, |
422 | size_t size, loff_t *offp) |
423 | { |
424 | struct tool_peer *peer = filep->private_data; |
425 | struct tool_ctx *tc = peer->tc; |
426 | char buf[TOOL_BUF_LEN]; |
427 | int pos; |
428 | |
429 | pos = scnprintf(buf, size: sizeof(buf), fmt: "%d\n" , |
430 | ntb_peer_port_number(ntb: tc->ntb, pidx: peer->pidx)); |
431 | |
432 | return simple_read_from_buffer(to: ubuf, count: size, ppos: offp, from: buf, available: pos); |
433 | } |
434 | |
435 | static TOOL_FOPS_RDWR(tool_peer_port_fops, |
436 | tool_peer_port_read, |
437 | NULL); |
438 | |
439 | static int tool_init_peers(struct tool_ctx *tc) |
440 | { |
441 | int pidx; |
442 | |
443 | tc->peer_cnt = ntb_peer_port_count(ntb: tc->ntb); |
444 | tc->peers = devm_kcalloc(dev: &tc->ntb->dev, n: tc->peer_cnt, |
445 | size: sizeof(*tc->peers), GFP_KERNEL); |
446 | if (tc->peers == NULL) |
447 | return -ENOMEM; |
448 | |
449 | for (pidx = 0; pidx < tc->peer_cnt; pidx++) { |
450 | tc->peers[pidx].pidx = pidx; |
451 | tc->peers[pidx].tc = tc; |
452 | } |
453 | |
454 | return 0; |
455 | } |
456 | |
457 | /*============================================================================== |
458 | * Link state read/write methods |
459 | *============================================================================== |
460 | */ |
461 | |
462 | static ssize_t tool_link_write(struct file *filep, const char __user *ubuf, |
463 | size_t size, loff_t *offp) |
464 | { |
465 | struct tool_ctx *tc = filep->private_data; |
466 | bool val; |
467 | int ret; |
468 | |
469 | ret = kstrtobool_from_user(s: ubuf, count: size, res: &val); |
470 | if (ret) |
471 | return ret; |
472 | |
473 | if (val) |
474 | ret = ntb_link_enable(ntb: tc->ntb, max_speed: NTB_SPEED_AUTO, max_width: NTB_WIDTH_AUTO); |
475 | else |
476 | ret = ntb_link_disable(ntb: tc->ntb); |
477 | |
478 | if (ret) |
479 | return ret; |
480 | |
481 | return size; |
482 | } |
483 | |
484 | static TOOL_FOPS_RDWR(tool_link_fops, |
485 | NULL, |
486 | tool_link_write); |
487 | |
488 | static ssize_t tool_peer_link_read(struct file *filep, char __user *ubuf, |
489 | size_t size, loff_t *offp) |
490 | { |
491 | struct tool_peer *peer = filep->private_data; |
492 | struct tool_ctx *tc = peer->tc; |
493 | char buf[3]; |
494 | |
495 | if (ntb_link_is_up(ntb: tc->ntb, NULL, NULL) & BIT(peer->pidx)) |
496 | buf[0] = 'Y'; |
497 | else |
498 | buf[0] = 'N'; |
499 | buf[1] = '\n'; |
500 | buf[2] = '\0'; |
501 | |
502 | return simple_read_from_buffer(to: ubuf, count: size, ppos: offp, from: buf, available: 2); |
503 | } |
504 | |
505 | static TOOL_FOPS_RDWR(tool_peer_link_fops, |
506 | tool_peer_link_read, |
507 | NULL); |
508 | |
509 | static ssize_t tool_peer_link_event_write(struct file *filep, |
510 | const char __user *ubuf, |
511 | size_t size, loff_t *offp) |
512 | { |
513 | struct tool_peer *peer = filep->private_data; |
514 | struct tool_ctx *tc = peer->tc; |
515 | u64 link_msk; |
516 | bool val; |
517 | int ret; |
518 | |
519 | ret = kstrtobool_from_user(s: ubuf, count: size, res: &val); |
520 | if (ret) |
521 | return ret; |
522 | |
523 | link_msk = BIT_ULL_MASK(peer->pidx); |
524 | |
525 | if (wait_event_interruptible(tc->link_wq, |
526 | !!(ntb_link_is_up(tc->ntb, NULL, NULL) & link_msk) == val)) |
527 | return -ERESTART; |
528 | |
529 | return size; |
530 | } |
531 | |
532 | static TOOL_FOPS_RDWR(tool_peer_link_event_fops, |
533 | NULL, |
534 | tool_peer_link_event_write); |
535 | |
536 | /*============================================================================== |
537 | * Memory windows read/write/setting methods |
538 | *============================================================================== |
539 | */ |
540 | |
541 | static ssize_t tool_mw_read(struct file *filep, char __user *ubuf, |
542 | size_t size, loff_t *offp) |
543 | { |
544 | struct tool_mw *inmw = filep->private_data; |
545 | |
546 | if (inmw->mm_base == NULL) |
547 | return -ENXIO; |
548 | |
549 | return simple_read_from_buffer(to: ubuf, count: size, ppos: offp, |
550 | from: inmw->mm_base, available: inmw->size); |
551 | } |
552 | |
553 | static ssize_t tool_mw_write(struct file *filep, const char __user *ubuf, |
554 | size_t size, loff_t *offp) |
555 | { |
556 | struct tool_mw *inmw = filep->private_data; |
557 | |
558 | if (inmw->mm_base == NULL) |
559 | return -ENXIO; |
560 | |
561 | return simple_write_to_buffer(to: inmw->mm_base, available: inmw->size, ppos: offp, |
562 | from: ubuf, count: size); |
563 | } |
564 | |
565 | static TOOL_FOPS_RDWR(tool_mw_fops, |
566 | tool_mw_read, |
567 | tool_mw_write); |
568 | |
569 | static int tool_setup_mw(struct tool_ctx *tc, int pidx, int widx, |
570 | size_t req_size) |
571 | { |
572 | resource_size_t size, addr_align, size_align; |
573 | struct tool_mw *inmw = &tc->peers[pidx].inmws[widx]; |
574 | char buf[TOOL_BUF_LEN]; |
575 | int ret; |
576 | |
577 | if (inmw->mm_base != NULL) |
578 | return 0; |
579 | |
580 | ret = ntb_mw_get_align(ntb: tc->ntb, pidx, widx, addr_align: &addr_align, |
581 | size_align: &size_align, size_max: &size); |
582 | if (ret) |
583 | return ret; |
584 | |
585 | inmw->size = min_t(resource_size_t, req_size, size); |
586 | inmw->size = round_up(inmw->size, addr_align); |
587 | inmw->size = round_up(inmw->size, size_align); |
588 | inmw->mm_base = dma_alloc_coherent(dev: &tc->ntb->pdev->dev, size: inmw->size, |
589 | dma_handle: &inmw->dma_base, GFP_KERNEL); |
590 | if (!inmw->mm_base) |
591 | return -ENOMEM; |
592 | |
593 | if (!IS_ALIGNED(inmw->dma_base, addr_align)) { |
594 | ret = -ENOMEM; |
595 | goto err_free_dma; |
596 | } |
597 | |
598 | ret = ntb_mw_set_trans(ntb: tc->ntb, pidx, widx, addr: inmw->dma_base, size: inmw->size); |
599 | if (ret) |
600 | goto err_free_dma; |
601 | |
602 | snprintf(buf, size: sizeof(buf), fmt: "mw%d" , widx); |
603 | inmw->dbgfs_file = debugfs_create_file(name: buf, mode: 0600, |
604 | parent: tc->peers[pidx].dbgfs_dir, data: inmw, |
605 | fops: &tool_mw_fops); |
606 | |
607 | return 0; |
608 | |
609 | err_free_dma: |
610 | dma_free_coherent(dev: &tc->ntb->pdev->dev, size: inmw->size, cpu_addr: inmw->mm_base, |
611 | dma_handle: inmw->dma_base); |
612 | inmw->mm_base = NULL; |
613 | inmw->dma_base = 0; |
614 | inmw->size = 0; |
615 | |
616 | return ret; |
617 | } |
618 | |
619 | static void tool_free_mw(struct tool_ctx *tc, int pidx, int widx) |
620 | { |
621 | struct tool_mw *inmw = &tc->peers[pidx].inmws[widx]; |
622 | |
623 | debugfs_remove(dentry: inmw->dbgfs_file); |
624 | |
625 | if (inmw->mm_base != NULL) { |
626 | ntb_mw_clear_trans(ntb: tc->ntb, pidx, widx); |
627 | dma_free_coherent(dev: &tc->ntb->pdev->dev, size: inmw->size, |
628 | cpu_addr: inmw->mm_base, dma_handle: inmw->dma_base); |
629 | } |
630 | |
631 | inmw->mm_base = NULL; |
632 | inmw->dma_base = 0; |
633 | inmw->size = 0; |
634 | inmw->dbgfs_file = NULL; |
635 | } |
636 | |
637 | static ssize_t tool_mw_trans_read(struct file *filep, char __user *ubuf, |
638 | size_t size, loff_t *offp) |
639 | { |
640 | struct tool_mw *inmw = filep->private_data; |
641 | resource_size_t addr_align; |
642 | resource_size_t size_align; |
643 | resource_size_t size_max; |
644 | ssize_t ret, off = 0; |
645 | size_t buf_size; |
646 | char *buf; |
647 | |
648 | buf_size = min_t(size_t, size, 512); |
649 | |
650 | buf = kmalloc(size: buf_size, GFP_KERNEL); |
651 | if (!buf) |
652 | return -ENOMEM; |
653 | |
654 | ret = ntb_mw_get_align(ntb: inmw->tc->ntb, pidx: inmw->pidx, widx: inmw->widx, |
655 | addr_align: &addr_align, size_align: &size_align, size_max: &size_max); |
656 | if (ret) |
657 | goto err; |
658 | |
659 | off += scnprintf(buf: buf + off, size: buf_size - off, |
660 | fmt: "Inbound MW \t%d\n" , |
661 | inmw->widx); |
662 | |
663 | off += scnprintf(buf: buf + off, size: buf_size - off, |
664 | fmt: "Port \t%d (%d)\n" , |
665 | ntb_peer_port_number(ntb: inmw->tc->ntb, pidx: inmw->pidx), |
666 | inmw->pidx); |
667 | |
668 | off += scnprintf(buf: buf + off, size: buf_size - off, |
669 | fmt: "Window Address \t0x%pK\n" , inmw->mm_base); |
670 | |
671 | off += scnprintf(buf: buf + off, size: buf_size - off, |
672 | fmt: "DMA Address \t%pad\n" , |
673 | &inmw->dma_base); |
674 | |
675 | off += scnprintf(buf: buf + off, size: buf_size - off, |
676 | fmt: "Window Size \t%pap\n" , |
677 | &inmw->size); |
678 | |
679 | off += scnprintf(buf: buf + off, size: buf_size - off, |
680 | fmt: "Alignment \t%pap\n" , |
681 | &addr_align); |
682 | |
683 | off += scnprintf(buf: buf + off, size: buf_size - off, |
684 | fmt: "Size Alignment \t%pap\n" , |
685 | &size_align); |
686 | |
687 | off += scnprintf(buf: buf + off, size: buf_size - off, |
688 | fmt: "Size Max \t%pap\n" , |
689 | &size_max); |
690 | |
691 | ret = simple_read_from_buffer(to: ubuf, count: size, ppos: offp, from: buf, available: off); |
692 | |
693 | err: |
694 | kfree(objp: buf); |
695 | |
696 | return ret; |
697 | } |
698 | |
699 | static ssize_t tool_mw_trans_write(struct file *filep, const char __user *ubuf, |
700 | size_t size, loff_t *offp) |
701 | { |
702 | struct tool_mw *inmw = filep->private_data; |
703 | unsigned int val; |
704 | int ret; |
705 | |
706 | ret = kstrtouint_from_user(s: ubuf, count: size, base: 0, res: &val); |
707 | if (ret) |
708 | return ret; |
709 | |
710 | tool_free_mw(tc: inmw->tc, pidx: inmw->pidx, widx: inmw->widx); |
711 | if (val) { |
712 | ret = tool_setup_mw(tc: inmw->tc, pidx: inmw->pidx, widx: inmw->widx, req_size: val); |
713 | if (ret) |
714 | return ret; |
715 | } |
716 | |
717 | return size; |
718 | } |
719 | |
720 | static TOOL_FOPS_RDWR(tool_mw_trans_fops, |
721 | tool_mw_trans_read, |
722 | tool_mw_trans_write); |
723 | |
724 | static ssize_t tool_peer_mw_read(struct file *filep, char __user *ubuf, |
725 | size_t size, loff_t *offp) |
726 | { |
727 | struct tool_mw *outmw = filep->private_data; |
728 | loff_t pos = *offp; |
729 | ssize_t ret; |
730 | void *buf; |
731 | |
732 | if (outmw->io_base == NULL) |
733 | return -EIO; |
734 | |
735 | if (pos >= outmw->size || !size) |
736 | return 0; |
737 | |
738 | if (size > outmw->size - pos) |
739 | size = outmw->size - pos; |
740 | |
741 | buf = kmalloc(size, GFP_KERNEL); |
742 | if (!buf) |
743 | return -ENOMEM; |
744 | |
745 | memcpy_fromio(buf, outmw->io_base + pos, size); |
746 | ret = copy_to_user(to: ubuf, from: buf, n: size); |
747 | if (ret == size) { |
748 | ret = -EFAULT; |
749 | goto err_free; |
750 | } |
751 | |
752 | size -= ret; |
753 | *offp = pos + size; |
754 | ret = size; |
755 | |
756 | err_free: |
757 | kfree(objp: buf); |
758 | |
759 | return ret; |
760 | } |
761 | |
762 | static ssize_t tool_peer_mw_write(struct file *filep, const char __user *ubuf, |
763 | size_t size, loff_t *offp) |
764 | { |
765 | struct tool_mw *outmw = filep->private_data; |
766 | ssize_t ret; |
767 | loff_t pos = *offp; |
768 | void *buf; |
769 | |
770 | if (outmw->io_base == NULL) |
771 | return -EIO; |
772 | |
773 | if (pos >= outmw->size || !size) |
774 | return 0; |
775 | if (size > outmw->size - pos) |
776 | size = outmw->size - pos; |
777 | |
778 | buf = kmalloc(size, GFP_KERNEL); |
779 | if (!buf) |
780 | return -ENOMEM; |
781 | |
782 | ret = copy_from_user(to: buf, from: ubuf, n: size); |
783 | if (ret == size) { |
784 | ret = -EFAULT; |
785 | goto err_free; |
786 | } |
787 | |
788 | size -= ret; |
789 | *offp = pos + size; |
790 | ret = size; |
791 | |
792 | memcpy_toio(outmw->io_base + pos, buf, size); |
793 | |
794 | err_free: |
795 | kfree(objp: buf); |
796 | |
797 | return ret; |
798 | } |
799 | |
800 | static TOOL_FOPS_RDWR(tool_peer_mw_fops, |
801 | tool_peer_mw_read, |
802 | tool_peer_mw_write); |
803 | |
804 | static int tool_setup_peer_mw(struct tool_ctx *tc, int pidx, int widx, |
805 | u64 req_addr, size_t req_size) |
806 | { |
807 | struct tool_mw *outmw = &tc->outmws[widx]; |
808 | resource_size_t map_size; |
809 | phys_addr_t map_base; |
810 | char buf[TOOL_BUF_LEN]; |
811 | int ret; |
812 | |
813 | if (outmw->io_base != NULL) |
814 | return 0; |
815 | |
816 | ret = ntb_peer_mw_get_addr(ntb: tc->ntb, widx, base: &map_base, size: &map_size); |
817 | if (ret) |
818 | return ret; |
819 | |
820 | ret = ntb_peer_mw_set_trans(ntb: tc->ntb, pidx, widx, addr: req_addr, size: req_size); |
821 | if (ret) |
822 | return ret; |
823 | |
824 | outmw->io_base = ioremap_wc(offset: map_base, size: map_size); |
825 | if (outmw->io_base == NULL) { |
826 | ret = -EFAULT; |
827 | goto err_clear_trans; |
828 | } |
829 | |
830 | outmw->tr_base = req_addr; |
831 | outmw->size = req_size; |
832 | outmw->pidx = pidx; |
833 | |
834 | snprintf(buf, size: sizeof(buf), fmt: "peer_mw%d" , widx); |
835 | outmw->dbgfs_file = debugfs_create_file(name: buf, mode: 0600, |
836 | parent: tc->peers[pidx].dbgfs_dir, data: outmw, |
837 | fops: &tool_peer_mw_fops); |
838 | |
839 | return 0; |
840 | |
841 | err_clear_trans: |
842 | ntb_peer_mw_clear_trans(ntb: tc->ntb, pidx, widx); |
843 | |
844 | return ret; |
845 | } |
846 | |
847 | static void tool_free_peer_mw(struct tool_ctx *tc, int widx) |
848 | { |
849 | struct tool_mw *outmw = &tc->outmws[widx]; |
850 | |
851 | debugfs_remove(dentry: outmw->dbgfs_file); |
852 | |
853 | if (outmw->io_base != NULL) { |
854 | iounmap(addr: tc->outmws[widx].io_base); |
855 | ntb_peer_mw_clear_trans(ntb: tc->ntb, pidx: outmw->pidx, widx); |
856 | } |
857 | |
858 | outmw->io_base = NULL; |
859 | outmw->tr_base = 0; |
860 | outmw->size = 0; |
861 | outmw->pidx = -1; |
862 | outmw->dbgfs_file = NULL; |
863 | } |
864 | |
865 | static ssize_t tool_peer_mw_trans_read(struct file *filep, char __user *ubuf, |
866 | size_t size, loff_t *offp) |
867 | { |
868 | struct tool_mw_wrap *outmw_wrap = filep->private_data; |
869 | struct tool_mw *outmw = outmw_wrap->mw; |
870 | resource_size_t map_size; |
871 | phys_addr_t map_base; |
872 | ssize_t off = 0; |
873 | size_t buf_size; |
874 | char *buf; |
875 | int ret; |
876 | |
877 | ret = ntb_peer_mw_get_addr(ntb: outmw->tc->ntb, widx: outmw->widx, |
878 | base: &map_base, size: &map_size); |
879 | if (ret) |
880 | return ret; |
881 | |
882 | buf_size = min_t(size_t, size, 512); |
883 | |
884 | buf = kmalloc(size: buf_size, GFP_KERNEL); |
885 | if (!buf) |
886 | return -ENOMEM; |
887 | |
888 | off += scnprintf(buf: buf + off, size: buf_size - off, |
889 | fmt: "Outbound MW: \t%d\n" , outmw->widx); |
890 | |
891 | if (outmw->io_base != NULL) { |
892 | off += scnprintf(buf: buf + off, size: buf_size - off, |
893 | fmt: "Port attached \t%d (%d)\n" , |
894 | ntb_peer_port_number(ntb: outmw->tc->ntb, pidx: outmw->pidx), |
895 | outmw->pidx); |
896 | } else { |
897 | off += scnprintf(buf: buf + off, size: buf_size - off, |
898 | fmt: "Port attached \t-1 (-1)\n" ); |
899 | } |
900 | |
901 | off += scnprintf(buf: buf + off, size: buf_size - off, |
902 | fmt: "Virtual address \t0x%pK\n" , outmw->io_base); |
903 | |
904 | off += scnprintf(buf: buf + off, size: buf_size - off, |
905 | fmt: "Phys Address \t%pap\n" , &map_base); |
906 | |
907 | off += scnprintf(buf: buf + off, size: buf_size - off, |
908 | fmt: "Mapping Size \t%pap\n" , &map_size); |
909 | |
910 | off += scnprintf(buf: buf + off, size: buf_size - off, |
911 | fmt: "Translation Address \t0x%016llx\n" , outmw->tr_base); |
912 | |
913 | off += scnprintf(buf: buf + off, size: buf_size - off, |
914 | fmt: "Window Size \t%pap\n" , &outmw->size); |
915 | |
916 | ret = simple_read_from_buffer(to: ubuf, count: size, ppos: offp, from: buf, available: off); |
917 | kfree(objp: buf); |
918 | |
919 | return ret; |
920 | } |
921 | |
922 | static ssize_t tool_peer_mw_trans_write(struct file *filep, |
923 | const char __user *ubuf, |
924 | size_t size, loff_t *offp) |
925 | { |
926 | struct tool_mw_wrap *outmw_wrap = filep->private_data; |
927 | struct tool_mw *outmw = outmw_wrap->mw; |
928 | size_t buf_size, wsize; |
929 | char buf[TOOL_BUF_LEN]; |
930 | int ret, n; |
931 | u64 addr; |
932 | |
933 | buf_size = min(size, (sizeof(buf) - 1)); |
934 | if (copy_from_user(to: buf, from: ubuf, n: buf_size)) |
935 | return -EFAULT; |
936 | |
937 | buf[buf_size] = '\0'; |
938 | |
939 | n = sscanf(buf, "%lli:%zi" , &addr, &wsize); |
940 | if (n != 2) |
941 | return -EINVAL; |
942 | |
943 | tool_free_peer_mw(tc: outmw->tc, widx: outmw->widx); |
944 | if (wsize) { |
945 | ret = tool_setup_peer_mw(tc: outmw->tc, pidx: outmw_wrap->pidx, |
946 | widx: outmw->widx, req_addr: addr, req_size: wsize); |
947 | if (ret) |
948 | return ret; |
949 | } |
950 | |
951 | return size; |
952 | } |
953 | |
954 | static TOOL_FOPS_RDWR(tool_peer_mw_trans_fops, |
955 | tool_peer_mw_trans_read, |
956 | tool_peer_mw_trans_write); |
957 | |
958 | static int tool_init_mws(struct tool_ctx *tc) |
959 | { |
960 | int widx, pidx; |
961 | |
962 | /* Initialize outbound memory windows */ |
963 | tc->outmw_cnt = ntb_peer_mw_count(ntb: tc->ntb); |
964 | tc->outmws = devm_kcalloc(dev: &tc->ntb->dev, n: tc->outmw_cnt, |
965 | size: sizeof(*tc->outmws), GFP_KERNEL); |
966 | if (tc->outmws == NULL) |
967 | return -ENOMEM; |
968 | |
969 | for (widx = 0; widx < tc->outmw_cnt; widx++) { |
970 | tc->outmws[widx].widx = widx; |
971 | tc->outmws[widx].pidx = -1; |
972 | tc->outmws[widx].tc = tc; |
973 | } |
974 | |
975 | /* Initialize inbound memory windows and outbound MWs wrapper */ |
976 | for (pidx = 0; pidx < tc->peer_cnt; pidx++) { |
977 | tc->peers[pidx].inmw_cnt = ntb_mw_count(ntb: tc->ntb, pidx); |
978 | tc->peers[pidx].inmws = |
979 | devm_kcalloc(dev: &tc->ntb->dev, n: tc->peers[pidx].inmw_cnt, |
980 | size: sizeof(*tc->peers[pidx].inmws), GFP_KERNEL); |
981 | if (tc->peers[pidx].inmws == NULL) |
982 | return -ENOMEM; |
983 | |
984 | for (widx = 0; widx < tc->peers[pidx].inmw_cnt; widx++) { |
985 | tc->peers[pidx].inmws[widx].widx = widx; |
986 | tc->peers[pidx].inmws[widx].pidx = pidx; |
987 | tc->peers[pidx].inmws[widx].tc = tc; |
988 | } |
989 | |
990 | tc->peers[pidx].outmw_cnt = ntb_peer_mw_count(ntb: tc->ntb); |
991 | tc->peers[pidx].outmws = |
992 | devm_kcalloc(dev: &tc->ntb->dev, n: tc->peers[pidx].outmw_cnt, |
993 | size: sizeof(*tc->peers[pidx].outmws), GFP_KERNEL); |
994 | if (tc->peers[pidx].outmws == NULL) |
995 | return -ENOMEM; |
996 | |
997 | for (widx = 0; widx < tc->peers[pidx].outmw_cnt; widx++) { |
998 | tc->peers[pidx].outmws[widx].pidx = pidx; |
999 | tc->peers[pidx].outmws[widx].mw = &tc->outmws[widx]; |
1000 | } |
1001 | } |
1002 | |
1003 | return 0; |
1004 | } |
1005 | |
1006 | static void tool_clear_mws(struct tool_ctx *tc) |
1007 | { |
1008 | int widx, pidx; |
1009 | |
1010 | /* Free outbound memory windows */ |
1011 | for (widx = 0; widx < tc->outmw_cnt; widx++) |
1012 | tool_free_peer_mw(tc, widx); |
1013 | |
1014 | /* Free outbound memory windows */ |
1015 | for (pidx = 0; pidx < tc->peer_cnt; pidx++) |
1016 | for (widx = 0; widx < tc->peers[pidx].inmw_cnt; widx++) |
1017 | tool_free_mw(tc, pidx, widx); |
1018 | } |
1019 | |
1020 | /*============================================================================== |
1021 | * Doorbell read/write methods |
1022 | *============================================================================== |
1023 | */ |
1024 | |
1025 | static ssize_t tool_db_read(struct file *filep, char __user *ubuf, |
1026 | size_t size, loff_t *offp) |
1027 | { |
1028 | struct tool_ctx *tc = filep->private_data; |
1029 | |
1030 | return tool_fn_read(tc, ubuf, size, offp, fn_read: tc->ntb->ops->db_read); |
1031 | } |
1032 | |
1033 | static ssize_t tool_db_write(struct file *filep, const char __user *ubuf, |
1034 | size_t size, loff_t *offp) |
1035 | { |
1036 | struct tool_ctx *tc = filep->private_data; |
1037 | |
1038 | return tool_fn_write(tc, ubuf, size, offp, fn_set: tc->ntb->ops->db_set, |
1039 | fn_clear: tc->ntb->ops->db_clear); |
1040 | } |
1041 | |
1042 | static TOOL_FOPS_RDWR(tool_db_fops, |
1043 | tool_db_read, |
1044 | tool_db_write); |
1045 | |
1046 | static ssize_t tool_db_valid_mask_read(struct file *filep, char __user *ubuf, |
1047 | size_t size, loff_t *offp) |
1048 | { |
1049 | struct tool_ctx *tc = filep->private_data; |
1050 | |
1051 | return tool_fn_read(tc, ubuf, size, offp, fn_read: tc->ntb->ops->db_valid_mask); |
1052 | } |
1053 | |
1054 | static TOOL_FOPS_RDWR(tool_db_valid_mask_fops, |
1055 | tool_db_valid_mask_read, |
1056 | NULL); |
1057 | |
1058 | static ssize_t tool_db_mask_read(struct file *filep, char __user *ubuf, |
1059 | size_t size, loff_t *offp) |
1060 | { |
1061 | struct tool_ctx *tc = filep->private_data; |
1062 | |
1063 | return tool_fn_read(tc, ubuf, size, offp, fn_read: tc->ntb->ops->db_read_mask); |
1064 | } |
1065 | |
1066 | static ssize_t tool_db_mask_write(struct file *filep, const char __user *ubuf, |
1067 | size_t size, loff_t *offp) |
1068 | { |
1069 | struct tool_ctx *tc = filep->private_data; |
1070 | |
1071 | return tool_fn_write(tc, ubuf, size, offp, fn_set: tc->ntb->ops->db_set_mask, |
1072 | fn_clear: tc->ntb->ops->db_clear_mask); |
1073 | } |
1074 | |
1075 | static TOOL_FOPS_RDWR(tool_db_mask_fops, |
1076 | tool_db_mask_read, |
1077 | tool_db_mask_write); |
1078 | |
1079 | static ssize_t tool_peer_db_read(struct file *filep, char __user *ubuf, |
1080 | size_t size, loff_t *offp) |
1081 | { |
1082 | struct tool_ctx *tc = filep->private_data; |
1083 | |
1084 | return tool_fn_read(tc, ubuf, size, offp, fn_read: tc->ntb->ops->peer_db_read); |
1085 | } |
1086 | |
1087 | static ssize_t tool_peer_db_write(struct file *filep, const char __user *ubuf, |
1088 | size_t size, loff_t *offp) |
1089 | { |
1090 | struct tool_ctx *tc = filep->private_data; |
1091 | |
1092 | return tool_fn_write(tc, ubuf, size, offp, fn_set: tc->ntb->ops->peer_db_set, |
1093 | fn_clear: tc->ntb->ops->peer_db_clear); |
1094 | } |
1095 | |
1096 | static TOOL_FOPS_RDWR(tool_peer_db_fops, |
1097 | tool_peer_db_read, |
1098 | tool_peer_db_write); |
1099 | |
1100 | static ssize_t tool_peer_db_mask_read(struct file *filep, char __user *ubuf, |
1101 | size_t size, loff_t *offp) |
1102 | { |
1103 | struct tool_ctx *tc = filep->private_data; |
1104 | |
1105 | return tool_fn_read(tc, ubuf, size, offp, |
1106 | fn_read: tc->ntb->ops->peer_db_read_mask); |
1107 | } |
1108 | |
1109 | static ssize_t tool_peer_db_mask_write(struct file *filep, |
1110 | const char __user *ubuf, |
1111 | size_t size, loff_t *offp) |
1112 | { |
1113 | struct tool_ctx *tc = filep->private_data; |
1114 | |
1115 | return tool_fn_write(tc, ubuf, size, offp, |
1116 | fn_set: tc->ntb->ops->peer_db_set_mask, |
1117 | fn_clear: tc->ntb->ops->peer_db_clear_mask); |
1118 | } |
1119 | |
1120 | static TOOL_FOPS_RDWR(tool_peer_db_mask_fops, |
1121 | tool_peer_db_mask_read, |
1122 | tool_peer_db_mask_write); |
1123 | |
1124 | static ssize_t tool_db_event_write(struct file *filep, |
1125 | const char __user *ubuf, |
1126 | size_t size, loff_t *offp) |
1127 | { |
1128 | struct tool_ctx *tc = filep->private_data; |
1129 | u64 val; |
1130 | int ret; |
1131 | |
1132 | ret = kstrtou64_from_user(s: ubuf, count: size, base: 0, res: &val); |
1133 | if (ret) |
1134 | return ret; |
1135 | |
1136 | if (wait_event_interruptible(tc->db_wq, ntb_db_read(tc->ntb) == val)) |
1137 | return -ERESTART; |
1138 | |
1139 | return size; |
1140 | } |
1141 | |
1142 | static TOOL_FOPS_RDWR(tool_db_event_fops, |
1143 | NULL, |
1144 | tool_db_event_write); |
1145 | |
1146 | /*============================================================================== |
1147 | * Scratchpads read/write methods |
1148 | *============================================================================== |
1149 | */ |
1150 | |
1151 | static ssize_t tool_spad_read(struct file *filep, char __user *ubuf, |
1152 | size_t size, loff_t *offp) |
1153 | { |
1154 | struct tool_spad *spad = filep->private_data; |
1155 | char buf[TOOL_BUF_LEN]; |
1156 | ssize_t pos; |
1157 | |
1158 | if (!spad->tc->ntb->ops->spad_read) |
1159 | return -EINVAL; |
1160 | |
1161 | pos = scnprintf(buf, size: sizeof(buf), fmt: "%#x\n" , |
1162 | ntb_spad_read(ntb: spad->tc->ntb, sidx: spad->sidx)); |
1163 | |
1164 | return simple_read_from_buffer(to: ubuf, count: size, ppos: offp, from: buf, available: pos); |
1165 | } |
1166 | |
1167 | static ssize_t tool_spad_write(struct file *filep, const char __user *ubuf, |
1168 | size_t size, loff_t *offp) |
1169 | { |
1170 | struct tool_spad *spad = filep->private_data; |
1171 | u32 val; |
1172 | int ret; |
1173 | |
1174 | if (!spad->tc->ntb->ops->spad_write) { |
1175 | dev_dbg(&spad->tc->ntb->dev, "no spad write fn\n" ); |
1176 | return -EINVAL; |
1177 | } |
1178 | |
1179 | ret = kstrtou32_from_user(s: ubuf, count: size, base: 0, res: &val); |
1180 | if (ret) |
1181 | return ret; |
1182 | |
1183 | ret = ntb_spad_write(ntb: spad->tc->ntb, sidx: spad->sidx, val); |
1184 | |
1185 | return ret ?: size; |
1186 | } |
1187 | |
1188 | static TOOL_FOPS_RDWR(tool_spad_fops, |
1189 | tool_spad_read, |
1190 | tool_spad_write); |
1191 | |
1192 | static ssize_t tool_peer_spad_read(struct file *filep, char __user *ubuf, |
1193 | size_t size, loff_t *offp) |
1194 | { |
1195 | struct tool_spad *spad = filep->private_data; |
1196 | char buf[TOOL_BUF_LEN]; |
1197 | ssize_t pos; |
1198 | |
1199 | if (!spad->tc->ntb->ops->peer_spad_read) |
1200 | return -EINVAL; |
1201 | |
1202 | pos = scnprintf(buf, size: sizeof(buf), fmt: "%#x\n" , |
1203 | ntb_peer_spad_read(ntb: spad->tc->ntb, pidx: spad->pidx, sidx: spad->sidx)); |
1204 | |
1205 | return simple_read_from_buffer(to: ubuf, count: size, ppos: offp, from: buf, available: pos); |
1206 | } |
1207 | |
1208 | static ssize_t tool_peer_spad_write(struct file *filep, const char __user *ubuf, |
1209 | size_t size, loff_t *offp) |
1210 | { |
1211 | struct tool_spad *spad = filep->private_data; |
1212 | u32 val; |
1213 | int ret; |
1214 | |
1215 | if (!spad->tc->ntb->ops->peer_spad_write) { |
1216 | dev_dbg(&spad->tc->ntb->dev, "no spad write fn\n" ); |
1217 | return -EINVAL; |
1218 | } |
1219 | |
1220 | ret = kstrtou32_from_user(s: ubuf, count: size, base: 0, res: &val); |
1221 | if (ret) |
1222 | return ret; |
1223 | |
1224 | ret = ntb_peer_spad_write(ntb: spad->tc->ntb, pidx: spad->pidx, sidx: spad->sidx, val); |
1225 | |
1226 | return ret ?: size; |
1227 | } |
1228 | |
1229 | static TOOL_FOPS_RDWR(tool_peer_spad_fops, |
1230 | tool_peer_spad_read, |
1231 | tool_peer_spad_write); |
1232 | |
1233 | static int tool_init_spads(struct tool_ctx *tc) |
1234 | { |
1235 | int sidx, pidx; |
1236 | |
1237 | /* Initialize inbound scratchpad structures */ |
1238 | tc->inspad_cnt = ntb_spad_count(ntb: tc->ntb); |
1239 | tc->inspads = devm_kcalloc(dev: &tc->ntb->dev, n: tc->inspad_cnt, |
1240 | size: sizeof(*tc->inspads), GFP_KERNEL); |
1241 | if (tc->inspads == NULL) |
1242 | return -ENOMEM; |
1243 | |
1244 | for (sidx = 0; sidx < tc->inspad_cnt; sidx++) { |
1245 | tc->inspads[sidx].sidx = sidx; |
1246 | tc->inspads[sidx].pidx = -1; |
1247 | tc->inspads[sidx].tc = tc; |
1248 | } |
1249 | |
1250 | /* Initialize outbound scratchpad structures */ |
1251 | for (pidx = 0; pidx < tc->peer_cnt; pidx++) { |
1252 | tc->peers[pidx].outspad_cnt = ntb_spad_count(ntb: tc->ntb); |
1253 | tc->peers[pidx].outspads = |
1254 | devm_kcalloc(dev: &tc->ntb->dev, n: tc->peers[pidx].outspad_cnt, |
1255 | size: sizeof(*tc->peers[pidx].outspads), GFP_KERNEL); |
1256 | if (tc->peers[pidx].outspads == NULL) |
1257 | return -ENOMEM; |
1258 | |
1259 | for (sidx = 0; sidx < tc->peers[pidx].outspad_cnt; sidx++) { |
1260 | tc->peers[pidx].outspads[sidx].sidx = sidx; |
1261 | tc->peers[pidx].outspads[sidx].pidx = pidx; |
1262 | tc->peers[pidx].outspads[sidx].tc = tc; |
1263 | } |
1264 | } |
1265 | |
1266 | return 0; |
1267 | } |
1268 | |
1269 | /*============================================================================== |
1270 | * Messages read/write methods |
1271 | *============================================================================== |
1272 | */ |
1273 | |
1274 | static ssize_t tool_inmsg_read(struct file *filep, char __user *ubuf, |
1275 | size_t size, loff_t *offp) |
1276 | { |
1277 | struct tool_msg *msg = filep->private_data; |
1278 | char buf[TOOL_BUF_LEN]; |
1279 | ssize_t pos; |
1280 | u32 data; |
1281 | int pidx; |
1282 | |
1283 | data = ntb_msg_read(ntb: msg->tc->ntb, pidx: &pidx, midx: msg->midx); |
1284 | |
1285 | pos = scnprintf(buf, size: sizeof(buf), fmt: "0x%08x<-%d\n" , data, pidx); |
1286 | |
1287 | return simple_read_from_buffer(to: ubuf, count: size, ppos: offp, from: buf, available: pos); |
1288 | } |
1289 | |
1290 | static TOOL_FOPS_RDWR(tool_inmsg_fops, |
1291 | tool_inmsg_read, |
1292 | NULL); |
1293 | |
1294 | static ssize_t tool_outmsg_write(struct file *filep, |
1295 | const char __user *ubuf, |
1296 | size_t size, loff_t *offp) |
1297 | { |
1298 | struct tool_msg *msg = filep->private_data; |
1299 | u32 val; |
1300 | int ret; |
1301 | |
1302 | ret = kstrtou32_from_user(s: ubuf, count: size, base: 0, res: &val); |
1303 | if (ret) |
1304 | return ret; |
1305 | |
1306 | ret = ntb_peer_msg_write(ntb: msg->tc->ntb, pidx: msg->pidx, midx: msg->midx, msg: val); |
1307 | |
1308 | return ret ? : size; |
1309 | } |
1310 | |
1311 | static TOOL_FOPS_RDWR(tool_outmsg_fops, |
1312 | NULL, |
1313 | tool_outmsg_write); |
1314 | |
1315 | static ssize_t tool_msg_sts_read(struct file *filep, char __user *ubuf, |
1316 | size_t size, loff_t *offp) |
1317 | { |
1318 | struct tool_ctx *tc = filep->private_data; |
1319 | |
1320 | return tool_fn_read(tc, ubuf, size, offp, fn_read: tc->ntb->ops->msg_read_sts); |
1321 | } |
1322 | |
1323 | static ssize_t tool_msg_sts_write(struct file *filep, const char __user *ubuf, |
1324 | size_t size, loff_t *offp) |
1325 | { |
1326 | struct tool_ctx *tc = filep->private_data; |
1327 | |
1328 | return tool_fn_write(tc, ubuf, size, offp, NULL, |
1329 | fn_clear: tc->ntb->ops->msg_clear_sts); |
1330 | } |
1331 | |
1332 | static TOOL_FOPS_RDWR(tool_msg_sts_fops, |
1333 | tool_msg_sts_read, |
1334 | tool_msg_sts_write); |
1335 | |
1336 | static ssize_t tool_msg_inbits_read(struct file *filep, char __user *ubuf, |
1337 | size_t size, loff_t *offp) |
1338 | { |
1339 | struct tool_ctx *tc = filep->private_data; |
1340 | |
1341 | return tool_fn_read(tc, ubuf, size, offp, fn_read: tc->ntb->ops->msg_inbits); |
1342 | } |
1343 | |
1344 | static TOOL_FOPS_RDWR(tool_msg_inbits_fops, |
1345 | tool_msg_inbits_read, |
1346 | NULL); |
1347 | |
1348 | static ssize_t tool_msg_outbits_read(struct file *filep, char __user *ubuf, |
1349 | size_t size, loff_t *offp) |
1350 | { |
1351 | struct tool_ctx *tc = filep->private_data; |
1352 | |
1353 | return tool_fn_read(tc, ubuf, size, offp, fn_read: tc->ntb->ops->msg_outbits); |
1354 | } |
1355 | |
1356 | static TOOL_FOPS_RDWR(tool_msg_outbits_fops, |
1357 | tool_msg_outbits_read, |
1358 | NULL); |
1359 | |
1360 | static ssize_t tool_msg_mask_write(struct file *filep, const char __user *ubuf, |
1361 | size_t size, loff_t *offp) |
1362 | { |
1363 | struct tool_ctx *tc = filep->private_data; |
1364 | |
1365 | return tool_fn_write(tc, ubuf, size, offp, |
1366 | fn_set: tc->ntb->ops->msg_set_mask, |
1367 | fn_clear: tc->ntb->ops->msg_clear_mask); |
1368 | } |
1369 | |
1370 | static TOOL_FOPS_RDWR(tool_msg_mask_fops, |
1371 | NULL, |
1372 | tool_msg_mask_write); |
1373 | |
1374 | static ssize_t tool_msg_event_write(struct file *filep, |
1375 | const char __user *ubuf, |
1376 | size_t size, loff_t *offp) |
1377 | { |
1378 | struct tool_ctx *tc = filep->private_data; |
1379 | u64 val; |
1380 | int ret; |
1381 | |
1382 | ret = kstrtou64_from_user(s: ubuf, count: size, base: 0, res: &val); |
1383 | if (ret) |
1384 | return ret; |
1385 | |
1386 | if (wait_event_interruptible(tc->msg_wq, |
1387 | ntb_msg_read_sts(tc->ntb) == val)) |
1388 | return -ERESTART; |
1389 | |
1390 | return size; |
1391 | } |
1392 | |
1393 | static TOOL_FOPS_RDWR(tool_msg_event_fops, |
1394 | NULL, |
1395 | tool_msg_event_write); |
1396 | |
1397 | static int tool_init_msgs(struct tool_ctx *tc) |
1398 | { |
1399 | int midx, pidx; |
1400 | |
1401 | /* Initialize inbound message structures */ |
1402 | tc->inmsg_cnt = ntb_msg_count(ntb: tc->ntb); |
1403 | tc->inmsgs = devm_kcalloc(dev: &tc->ntb->dev, n: tc->inmsg_cnt, |
1404 | size: sizeof(*tc->inmsgs), GFP_KERNEL); |
1405 | if (tc->inmsgs == NULL) |
1406 | return -ENOMEM; |
1407 | |
1408 | for (midx = 0; midx < tc->inmsg_cnt; midx++) { |
1409 | tc->inmsgs[midx].midx = midx; |
1410 | tc->inmsgs[midx].pidx = -1; |
1411 | tc->inmsgs[midx].tc = tc; |
1412 | } |
1413 | |
1414 | /* Initialize outbound message structures */ |
1415 | for (pidx = 0; pidx < tc->peer_cnt; pidx++) { |
1416 | tc->peers[pidx].outmsg_cnt = ntb_msg_count(ntb: tc->ntb); |
1417 | tc->peers[pidx].outmsgs = |
1418 | devm_kcalloc(dev: &tc->ntb->dev, n: tc->peers[pidx].outmsg_cnt, |
1419 | size: sizeof(*tc->peers[pidx].outmsgs), GFP_KERNEL); |
1420 | if (tc->peers[pidx].outmsgs == NULL) |
1421 | return -ENOMEM; |
1422 | |
1423 | for (midx = 0; midx < tc->peers[pidx].outmsg_cnt; midx++) { |
1424 | tc->peers[pidx].outmsgs[midx].midx = midx; |
1425 | tc->peers[pidx].outmsgs[midx].pidx = pidx; |
1426 | tc->peers[pidx].outmsgs[midx].tc = tc; |
1427 | } |
1428 | } |
1429 | |
1430 | return 0; |
1431 | } |
1432 | |
1433 | /*============================================================================== |
1434 | * Initialization methods |
1435 | *============================================================================== |
1436 | */ |
1437 | |
1438 | static struct tool_ctx *tool_create_data(struct ntb_dev *ntb) |
1439 | { |
1440 | struct tool_ctx *tc; |
1441 | |
1442 | tc = devm_kzalloc(dev: &ntb->dev, size: sizeof(*tc), GFP_KERNEL); |
1443 | if (tc == NULL) |
1444 | return ERR_PTR(error: -ENOMEM); |
1445 | |
1446 | tc->ntb = ntb; |
1447 | init_waitqueue_head(&tc->link_wq); |
1448 | init_waitqueue_head(&tc->db_wq); |
1449 | init_waitqueue_head(&tc->msg_wq); |
1450 | |
1451 | if (ntb_db_is_unsafe(ntb)) |
1452 | dev_dbg(&ntb->dev, "doorbell is unsafe\n" ); |
1453 | |
1454 | if (ntb_spad_is_unsafe(ntb)) |
1455 | dev_dbg(&ntb->dev, "scratchpad is unsafe\n" ); |
1456 | |
1457 | return tc; |
1458 | } |
1459 | |
1460 | static void tool_clear_data(struct tool_ctx *tc) |
1461 | { |
1462 | wake_up(&tc->link_wq); |
1463 | wake_up(&tc->db_wq); |
1464 | wake_up(&tc->msg_wq); |
1465 | } |
1466 | |
1467 | static int tool_init_ntb(struct tool_ctx *tc) |
1468 | { |
1469 | return ntb_set_ctx(ntb: tc->ntb, ctx: tc, ctx_ops: &tool_ops); |
1470 | } |
1471 | |
1472 | static void tool_clear_ntb(struct tool_ctx *tc) |
1473 | { |
1474 | ntb_clear_ctx(ntb: tc->ntb); |
1475 | ntb_link_disable(ntb: tc->ntb); |
1476 | } |
1477 | |
1478 | static void tool_setup_dbgfs(struct tool_ctx *tc) |
1479 | { |
1480 | int pidx, widx, sidx, midx; |
1481 | char buf[TOOL_BUF_LEN]; |
1482 | |
1483 | /* This modules is useless without dbgfs... */ |
1484 | if (!tool_dbgfs_topdir) { |
1485 | tc->dbgfs_dir = NULL; |
1486 | return; |
1487 | } |
1488 | |
1489 | tc->dbgfs_dir = debugfs_create_dir(name: dev_name(dev: &tc->ntb->dev), |
1490 | parent: tool_dbgfs_topdir); |
1491 | |
1492 | debugfs_create_file(name: "port" , mode: 0600, parent: tc->dbgfs_dir, |
1493 | data: tc, fops: &tool_port_fops); |
1494 | |
1495 | debugfs_create_file(name: "link" , mode: 0600, parent: tc->dbgfs_dir, |
1496 | data: tc, fops: &tool_link_fops); |
1497 | |
1498 | debugfs_create_file(name: "db" , mode: 0600, parent: tc->dbgfs_dir, |
1499 | data: tc, fops: &tool_db_fops); |
1500 | |
1501 | debugfs_create_file(name: "db_valid_mask" , mode: 0600, parent: tc->dbgfs_dir, |
1502 | data: tc, fops: &tool_db_valid_mask_fops); |
1503 | |
1504 | debugfs_create_file(name: "db_mask" , mode: 0600, parent: tc->dbgfs_dir, |
1505 | data: tc, fops: &tool_db_mask_fops); |
1506 | |
1507 | debugfs_create_file(name: "db_event" , mode: 0600, parent: tc->dbgfs_dir, |
1508 | data: tc, fops: &tool_db_event_fops); |
1509 | |
1510 | debugfs_create_file(name: "peer_db" , mode: 0600, parent: tc->dbgfs_dir, |
1511 | data: tc, fops: &tool_peer_db_fops); |
1512 | |
1513 | debugfs_create_file(name: "peer_db_mask" , mode: 0600, parent: tc->dbgfs_dir, |
1514 | data: tc, fops: &tool_peer_db_mask_fops); |
1515 | |
1516 | if (tc->inspad_cnt != 0) { |
1517 | for (sidx = 0; sidx < tc->inspad_cnt; sidx++) { |
1518 | snprintf(buf, size: sizeof(buf), fmt: "spad%d" , sidx); |
1519 | |
1520 | debugfs_create_file(name: buf, mode: 0600, parent: tc->dbgfs_dir, |
1521 | data: &tc->inspads[sidx], fops: &tool_spad_fops); |
1522 | } |
1523 | } |
1524 | |
1525 | if (tc->inmsg_cnt != 0) { |
1526 | for (midx = 0; midx < tc->inmsg_cnt; midx++) { |
1527 | snprintf(buf, size: sizeof(buf), fmt: "msg%d" , midx); |
1528 | debugfs_create_file(name: buf, mode: 0600, parent: tc->dbgfs_dir, |
1529 | data: &tc->inmsgs[midx], fops: &tool_inmsg_fops); |
1530 | } |
1531 | |
1532 | debugfs_create_file(name: "msg_sts" , mode: 0600, parent: tc->dbgfs_dir, |
1533 | data: tc, fops: &tool_msg_sts_fops); |
1534 | |
1535 | debugfs_create_file(name: "msg_inbits" , mode: 0600, parent: tc->dbgfs_dir, |
1536 | data: tc, fops: &tool_msg_inbits_fops); |
1537 | |
1538 | debugfs_create_file(name: "msg_outbits" , mode: 0600, parent: tc->dbgfs_dir, |
1539 | data: tc, fops: &tool_msg_outbits_fops); |
1540 | |
1541 | debugfs_create_file(name: "msg_mask" , mode: 0600, parent: tc->dbgfs_dir, |
1542 | data: tc, fops: &tool_msg_mask_fops); |
1543 | |
1544 | debugfs_create_file(name: "msg_event" , mode: 0600, parent: tc->dbgfs_dir, |
1545 | data: tc, fops: &tool_msg_event_fops); |
1546 | } |
1547 | |
1548 | for (pidx = 0; pidx < tc->peer_cnt; pidx++) { |
1549 | snprintf(buf, size: sizeof(buf), fmt: "peer%d" , pidx); |
1550 | tc->peers[pidx].dbgfs_dir = |
1551 | debugfs_create_dir(name: buf, parent: tc->dbgfs_dir); |
1552 | |
1553 | debugfs_create_file(name: "port" , mode: 0600, |
1554 | parent: tc->peers[pidx].dbgfs_dir, |
1555 | data: &tc->peers[pidx], fops: &tool_peer_port_fops); |
1556 | |
1557 | debugfs_create_file(name: "link" , mode: 0200, |
1558 | parent: tc->peers[pidx].dbgfs_dir, |
1559 | data: &tc->peers[pidx], fops: &tool_peer_link_fops); |
1560 | |
1561 | debugfs_create_file(name: "link_event" , mode: 0200, |
1562 | parent: tc->peers[pidx].dbgfs_dir, |
1563 | data: &tc->peers[pidx], fops: &tool_peer_link_event_fops); |
1564 | |
1565 | for (widx = 0; widx < tc->peers[pidx].inmw_cnt; widx++) { |
1566 | snprintf(buf, size: sizeof(buf), fmt: "mw_trans%d" , widx); |
1567 | debugfs_create_file(name: buf, mode: 0600, |
1568 | parent: tc->peers[pidx].dbgfs_dir, |
1569 | data: &tc->peers[pidx].inmws[widx], |
1570 | fops: &tool_mw_trans_fops); |
1571 | } |
1572 | |
1573 | for (widx = 0; widx < tc->peers[pidx].outmw_cnt; widx++) { |
1574 | snprintf(buf, size: sizeof(buf), fmt: "peer_mw_trans%d" , widx); |
1575 | debugfs_create_file(name: buf, mode: 0600, |
1576 | parent: tc->peers[pidx].dbgfs_dir, |
1577 | data: &tc->peers[pidx].outmws[widx], |
1578 | fops: &tool_peer_mw_trans_fops); |
1579 | } |
1580 | |
1581 | for (sidx = 0; sidx < tc->peers[pidx].outspad_cnt; sidx++) { |
1582 | snprintf(buf, size: sizeof(buf), fmt: "spad%d" , sidx); |
1583 | |
1584 | debugfs_create_file(name: buf, mode: 0600, |
1585 | parent: tc->peers[pidx].dbgfs_dir, |
1586 | data: &tc->peers[pidx].outspads[sidx], |
1587 | fops: &tool_peer_spad_fops); |
1588 | } |
1589 | |
1590 | for (midx = 0; midx < tc->peers[pidx].outmsg_cnt; midx++) { |
1591 | snprintf(buf, size: sizeof(buf), fmt: "msg%d" , midx); |
1592 | debugfs_create_file(name: buf, mode: 0600, |
1593 | parent: tc->peers[pidx].dbgfs_dir, |
1594 | data: &tc->peers[pidx].outmsgs[midx], |
1595 | fops: &tool_outmsg_fops); |
1596 | } |
1597 | } |
1598 | } |
1599 | |
1600 | static void tool_clear_dbgfs(struct tool_ctx *tc) |
1601 | { |
1602 | debugfs_remove_recursive(dentry: tc->dbgfs_dir); |
1603 | } |
1604 | |
1605 | static int tool_probe(struct ntb_client *self, struct ntb_dev *ntb) |
1606 | { |
1607 | struct tool_ctx *tc; |
1608 | int ret; |
1609 | |
1610 | tc = tool_create_data(ntb); |
1611 | if (IS_ERR(ptr: tc)) |
1612 | return PTR_ERR(ptr: tc); |
1613 | |
1614 | ret = tool_init_peers(tc); |
1615 | if (ret != 0) |
1616 | goto err_clear_data; |
1617 | |
1618 | ret = tool_init_mws(tc); |
1619 | if (ret != 0) |
1620 | goto err_clear_data; |
1621 | |
1622 | ret = tool_init_spads(tc); |
1623 | if (ret != 0) |
1624 | goto err_clear_mws; |
1625 | |
1626 | ret = tool_init_msgs(tc); |
1627 | if (ret != 0) |
1628 | goto err_clear_mws; |
1629 | |
1630 | ret = tool_init_ntb(tc); |
1631 | if (ret != 0) |
1632 | goto err_clear_mws; |
1633 | |
1634 | tool_setup_dbgfs(tc); |
1635 | |
1636 | return 0; |
1637 | |
1638 | err_clear_mws: |
1639 | tool_clear_mws(tc); |
1640 | |
1641 | err_clear_data: |
1642 | tool_clear_data(tc); |
1643 | |
1644 | return ret; |
1645 | } |
1646 | |
1647 | static void tool_remove(struct ntb_client *self, struct ntb_dev *ntb) |
1648 | { |
1649 | struct tool_ctx *tc = ntb->ctx; |
1650 | |
1651 | tool_clear_dbgfs(tc); |
1652 | |
1653 | tool_clear_ntb(tc); |
1654 | |
1655 | tool_clear_mws(tc); |
1656 | |
1657 | tool_clear_data(tc); |
1658 | } |
1659 | |
1660 | static struct ntb_client tool_client = { |
1661 | .ops = { |
1662 | .probe = tool_probe, |
1663 | .remove = tool_remove, |
1664 | } |
1665 | }; |
1666 | |
1667 | static int __init tool_init(void) |
1668 | { |
1669 | int ret; |
1670 | |
1671 | if (debugfs_initialized()) |
1672 | tool_dbgfs_topdir = debugfs_create_dir(KBUILD_MODNAME, NULL); |
1673 | |
1674 | ret = ntb_register_client(&tool_client); |
1675 | if (ret) |
1676 | debugfs_remove_recursive(dentry: tool_dbgfs_topdir); |
1677 | |
1678 | return ret; |
1679 | } |
1680 | module_init(tool_init); |
1681 | |
1682 | static void __exit tool_exit(void) |
1683 | { |
1684 | ntb_unregister_client(client: &tool_client); |
1685 | debugfs_remove_recursive(dentry: tool_dbgfs_topdir); |
1686 | } |
1687 | module_exit(tool_exit); |
1688 | |