1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (c) 2017, NVIDIA CORPORATION. All rights reserved. |
4 | */ |
5 | #include <linux/debugfs.h> |
6 | #include <linux/dma-mapping.h> |
7 | #include <linux/slab.h> |
8 | #include <linux/uaccess.h> |
9 | |
10 | #include <soc/tegra/bpmp.h> |
11 | #include <soc/tegra/bpmp-abi.h> |
12 | |
13 | static DEFINE_MUTEX(bpmp_debug_lock); |
14 | |
15 | struct seqbuf { |
16 | char *buf; |
17 | size_t pos; |
18 | size_t size; |
19 | }; |
20 | |
21 | static void seqbuf_init(struct seqbuf *seqbuf, void *buf, size_t size) |
22 | { |
23 | seqbuf->buf = buf; |
24 | seqbuf->size = size; |
25 | seqbuf->pos = 0; |
26 | } |
27 | |
28 | static size_t seqbuf_avail(struct seqbuf *seqbuf) |
29 | { |
30 | return seqbuf->pos < seqbuf->size ? seqbuf->size - seqbuf->pos : 0; |
31 | } |
32 | |
33 | static size_t seqbuf_status(struct seqbuf *seqbuf) |
34 | { |
35 | return seqbuf->pos <= seqbuf->size ? 0 : -EOVERFLOW; |
36 | } |
37 | |
38 | static int seqbuf_eof(struct seqbuf *seqbuf) |
39 | { |
40 | return seqbuf->pos >= seqbuf->size; |
41 | } |
42 | |
43 | static int seqbuf_read(struct seqbuf *seqbuf, void *buf, size_t nbyte) |
44 | { |
45 | nbyte = min(nbyte, seqbuf_avail(seqbuf)); |
46 | memcpy(buf, seqbuf->buf + seqbuf->pos, nbyte); |
47 | seqbuf->pos += nbyte; |
48 | return seqbuf_status(seqbuf); |
49 | } |
50 | |
51 | static int seqbuf_read_u32(struct seqbuf *seqbuf, u32 *v) |
52 | { |
53 | return seqbuf_read(seqbuf, buf: v, nbyte: 4); |
54 | } |
55 | |
56 | static int seqbuf_read_str(struct seqbuf *seqbuf, const char **str) |
57 | { |
58 | *str = seqbuf->buf + seqbuf->pos; |
59 | seqbuf->pos += strnlen(p: *str, maxlen: seqbuf_avail(seqbuf)); |
60 | seqbuf->pos++; |
61 | return seqbuf_status(seqbuf); |
62 | } |
63 | |
64 | static void seqbuf_seek(struct seqbuf *seqbuf, ssize_t offset) |
65 | { |
66 | seqbuf->pos += offset; |
67 | } |
68 | |
69 | /* map filename in Linux debugfs to corresponding entry in BPMP */ |
70 | static const char *get_filename(struct tegra_bpmp *bpmp, |
71 | const struct file *file, char *buf, int size) |
72 | { |
73 | const char *root_path, *filename = NULL; |
74 | char *root_path_buf; |
75 | size_t root_len; |
76 | size_t root_path_buf_len = 512; |
77 | |
78 | root_path_buf = kzalloc(size: root_path_buf_len, GFP_KERNEL); |
79 | if (!root_path_buf) |
80 | return NULL; |
81 | |
82 | root_path = dentry_path(bpmp->debugfs_mirror, root_path_buf, |
83 | root_path_buf_len); |
84 | if (IS_ERR(ptr: root_path)) |
85 | goto out; |
86 | |
87 | root_len = strlen(root_path); |
88 | |
89 | filename = dentry_path(file->f_path.dentry, buf, size); |
90 | if (IS_ERR(ptr: filename)) { |
91 | filename = NULL; |
92 | goto out; |
93 | } |
94 | |
95 | if (strlen(filename) < root_len || strncmp(filename, root_path, root_len)) { |
96 | filename = NULL; |
97 | goto out; |
98 | } |
99 | |
100 | filename += root_len; |
101 | |
102 | out: |
103 | kfree(objp: root_path_buf); |
104 | return filename; |
105 | } |
106 | |
107 | static int mrq_debug_open(struct tegra_bpmp *bpmp, const char *name, |
108 | u32 *fd, u32 *len, bool write) |
109 | { |
110 | struct mrq_debug_request req = { |
111 | .cmd = write ? CMD_DEBUG_OPEN_WO : CMD_DEBUG_OPEN_RO, |
112 | }; |
113 | struct mrq_debug_response resp; |
114 | struct tegra_bpmp_message msg = { |
115 | .mrq = MRQ_DEBUG, |
116 | .tx = { |
117 | .data = &req, |
118 | .size = sizeof(req), |
119 | }, |
120 | .rx = { |
121 | .data = &resp, |
122 | .size = sizeof(resp), |
123 | }, |
124 | }; |
125 | ssize_t sz_name; |
126 | int err = 0; |
127 | |
128 | sz_name = strscpy(req.fop.name, name, sizeof(req.fop.name)); |
129 | if (sz_name < 0) { |
130 | pr_err("File name too large: %s\n" , name); |
131 | return -EINVAL; |
132 | } |
133 | |
134 | err = tegra_bpmp_transfer(bpmp, msg: &msg); |
135 | if (err < 0) |
136 | return err; |
137 | else if (msg.rx.ret < 0) |
138 | return -EINVAL; |
139 | |
140 | *len = resp.fop.datalen; |
141 | *fd = resp.fop.fd; |
142 | |
143 | return 0; |
144 | } |
145 | |
146 | static int mrq_debug_close(struct tegra_bpmp *bpmp, u32 fd) |
147 | { |
148 | struct mrq_debug_request req = { |
149 | .cmd = CMD_DEBUG_CLOSE, |
150 | .frd = { |
151 | .fd = fd, |
152 | }, |
153 | }; |
154 | struct mrq_debug_response resp; |
155 | struct tegra_bpmp_message msg = { |
156 | .mrq = MRQ_DEBUG, |
157 | .tx = { |
158 | .data = &req, |
159 | .size = sizeof(req), |
160 | }, |
161 | .rx = { |
162 | .data = &resp, |
163 | .size = sizeof(resp), |
164 | }, |
165 | }; |
166 | int err = 0; |
167 | |
168 | err = tegra_bpmp_transfer(bpmp, msg: &msg); |
169 | if (err < 0) |
170 | return err; |
171 | else if (msg.rx.ret < 0) |
172 | return -EINVAL; |
173 | |
174 | return 0; |
175 | } |
176 | |
177 | static int mrq_debug_read(struct tegra_bpmp *bpmp, const char *name, |
178 | char *data, size_t sz_data, u32 *nbytes) |
179 | { |
180 | struct mrq_debug_request req = { |
181 | .cmd = CMD_DEBUG_READ, |
182 | }; |
183 | struct mrq_debug_response resp; |
184 | struct tegra_bpmp_message msg = { |
185 | .mrq = MRQ_DEBUG, |
186 | .tx = { |
187 | .data = &req, |
188 | .size = sizeof(req), |
189 | }, |
190 | .rx = { |
191 | .data = &resp, |
192 | .size = sizeof(resp), |
193 | }, |
194 | }; |
195 | u32 fd = 0, len = 0; |
196 | int remaining, err, close_err; |
197 | |
198 | mutex_lock(&bpmp_debug_lock); |
199 | err = mrq_debug_open(bpmp, name, fd: &fd, len: &len, write: 0); |
200 | if (err) |
201 | goto out; |
202 | |
203 | if (len > sz_data) { |
204 | err = -EFBIG; |
205 | goto close; |
206 | } |
207 | |
208 | req.frd.fd = fd; |
209 | remaining = len; |
210 | |
211 | while (remaining > 0) { |
212 | err = tegra_bpmp_transfer(bpmp, msg: &msg); |
213 | if (err < 0) { |
214 | goto close; |
215 | } else if (msg.rx.ret < 0) { |
216 | err = -EINVAL; |
217 | goto close; |
218 | } |
219 | |
220 | if (resp.frd.readlen > remaining) { |
221 | pr_err("%s: read data length invalid\n" , __func__); |
222 | err = -EINVAL; |
223 | goto close; |
224 | } |
225 | |
226 | memcpy(data, resp.frd.data, resp.frd.readlen); |
227 | data += resp.frd.readlen; |
228 | remaining -= resp.frd.readlen; |
229 | } |
230 | |
231 | *nbytes = len; |
232 | |
233 | close: |
234 | close_err = mrq_debug_close(bpmp, fd); |
235 | if (!err) |
236 | err = close_err; |
237 | out: |
238 | mutex_unlock(lock: &bpmp_debug_lock); |
239 | return err; |
240 | } |
241 | |
242 | static int mrq_debug_write(struct tegra_bpmp *bpmp, const char *name, |
243 | uint8_t *data, size_t sz_data) |
244 | { |
245 | struct mrq_debug_request req = { |
246 | .cmd = CMD_DEBUG_WRITE |
247 | }; |
248 | struct mrq_debug_response resp; |
249 | struct tegra_bpmp_message msg = { |
250 | .mrq = MRQ_DEBUG, |
251 | .tx = { |
252 | .data = &req, |
253 | .size = sizeof(req), |
254 | }, |
255 | .rx = { |
256 | .data = &resp, |
257 | .size = sizeof(resp), |
258 | }, |
259 | }; |
260 | u32 fd = 0, len = 0; |
261 | size_t remaining; |
262 | int err; |
263 | |
264 | mutex_lock(&bpmp_debug_lock); |
265 | err = mrq_debug_open(bpmp, name, fd: &fd, len: &len, write: 1); |
266 | if (err) |
267 | goto out; |
268 | |
269 | if (sz_data > len) { |
270 | err = -EINVAL; |
271 | goto close; |
272 | } |
273 | |
274 | req.fwr.fd = fd; |
275 | remaining = sz_data; |
276 | |
277 | while (remaining > 0) { |
278 | len = min(remaining, sizeof(req.fwr.data)); |
279 | memcpy(req.fwr.data, data, len); |
280 | req.fwr.datalen = len; |
281 | |
282 | err = tegra_bpmp_transfer(bpmp, msg: &msg); |
283 | if (err < 0) { |
284 | goto close; |
285 | } else if (msg.rx.ret < 0) { |
286 | err = -EINVAL; |
287 | goto close; |
288 | } |
289 | |
290 | data += req.fwr.datalen; |
291 | remaining -= req.fwr.datalen; |
292 | } |
293 | |
294 | close: |
295 | err = mrq_debug_close(bpmp, fd); |
296 | out: |
297 | mutex_unlock(lock: &bpmp_debug_lock); |
298 | return err; |
299 | } |
300 | |
301 | static int bpmp_debug_show(struct seq_file *m, void *p) |
302 | { |
303 | struct file *file = m->private; |
304 | struct inode *inode = file_inode(f: file); |
305 | struct tegra_bpmp *bpmp = inode->i_private; |
306 | char fnamebuf[256]; |
307 | const char *filename; |
308 | struct mrq_debug_request req = { |
309 | .cmd = CMD_DEBUG_READ, |
310 | }; |
311 | struct mrq_debug_response resp; |
312 | struct tegra_bpmp_message msg = { |
313 | .mrq = MRQ_DEBUG, |
314 | .tx = { |
315 | .data = &req, |
316 | .size = sizeof(req), |
317 | }, |
318 | .rx = { |
319 | .data = &resp, |
320 | .size = sizeof(resp), |
321 | }, |
322 | }; |
323 | u32 fd = 0, len = 0; |
324 | int remaining, err, close_err; |
325 | |
326 | filename = get_filename(bpmp, file, buf: fnamebuf, size: sizeof(fnamebuf)); |
327 | if (!filename) |
328 | return -ENOENT; |
329 | |
330 | mutex_lock(&bpmp_debug_lock); |
331 | err = mrq_debug_open(bpmp, name: filename, fd: &fd, len: &len, write: 0); |
332 | if (err) |
333 | goto out; |
334 | |
335 | req.frd.fd = fd; |
336 | remaining = len; |
337 | |
338 | while (remaining > 0) { |
339 | err = tegra_bpmp_transfer(bpmp, msg: &msg); |
340 | if (err < 0) { |
341 | goto close; |
342 | } else if (msg.rx.ret < 0) { |
343 | err = -EINVAL; |
344 | goto close; |
345 | } |
346 | |
347 | if (resp.frd.readlen > remaining) { |
348 | pr_err("%s: read data length invalid\n" , __func__); |
349 | err = -EINVAL; |
350 | goto close; |
351 | } |
352 | |
353 | seq_write(seq: m, data: resp.frd.data, len: resp.frd.readlen); |
354 | remaining -= resp.frd.readlen; |
355 | } |
356 | |
357 | close: |
358 | close_err = mrq_debug_close(bpmp, fd); |
359 | if (!err) |
360 | err = close_err; |
361 | out: |
362 | mutex_unlock(lock: &bpmp_debug_lock); |
363 | return err; |
364 | } |
365 | |
366 | static ssize_t bpmp_debug_store(struct file *file, const char __user *buf, |
367 | size_t count, loff_t *f_pos) |
368 | { |
369 | struct inode *inode = file_inode(f: file); |
370 | struct tegra_bpmp *bpmp = inode->i_private; |
371 | char *databuf = NULL; |
372 | char fnamebuf[256]; |
373 | const char *filename; |
374 | ssize_t err; |
375 | |
376 | filename = get_filename(bpmp, file, buf: fnamebuf, size: sizeof(fnamebuf)); |
377 | if (!filename) |
378 | return -ENOENT; |
379 | |
380 | databuf = memdup_user(buf, count); |
381 | if (IS_ERR(ptr: databuf)) |
382 | return PTR_ERR(ptr: databuf); |
383 | |
384 | err = mrq_debug_write(bpmp, name: filename, data: databuf, sz_data: count); |
385 | kfree(objp: databuf); |
386 | |
387 | return err ?: count; |
388 | } |
389 | |
390 | static int bpmp_debug_open(struct inode *inode, struct file *file) |
391 | { |
392 | return single_open_size(file, bpmp_debug_show, file, SZ_256K); |
393 | } |
394 | |
395 | static const struct file_operations bpmp_debug_fops = { |
396 | .open = bpmp_debug_open, |
397 | .read = seq_read, |
398 | .llseek = seq_lseek, |
399 | .write = bpmp_debug_store, |
400 | .release = single_release, |
401 | }; |
402 | |
403 | static int bpmp_populate_debugfs_inband(struct tegra_bpmp *bpmp, |
404 | struct dentry *parent, |
405 | char *ppath) |
406 | { |
407 | const size_t pathlen = SZ_256; |
408 | const size_t bufsize = SZ_16K; |
409 | struct dentry *dentry; |
410 | u32 dsize, attrs = 0; |
411 | struct seqbuf seqbuf; |
412 | char *buf, *pathbuf; |
413 | const char *name; |
414 | int err = 0; |
415 | |
416 | if (!bpmp || !parent || !ppath) |
417 | return -EINVAL; |
418 | |
419 | buf = kmalloc(size: bufsize, GFP_KERNEL); |
420 | if (!buf) |
421 | return -ENOMEM; |
422 | |
423 | pathbuf = kzalloc(size: pathlen, GFP_KERNEL); |
424 | if (!pathbuf) { |
425 | kfree(objp: buf); |
426 | return -ENOMEM; |
427 | } |
428 | |
429 | err = mrq_debug_read(bpmp, name: ppath, data: buf, sz_data: bufsize, nbytes: &dsize); |
430 | if (err) |
431 | goto out; |
432 | |
433 | seqbuf_init(seqbuf: &seqbuf, buf, size: dsize); |
434 | |
435 | while (!seqbuf_eof(seqbuf: &seqbuf)) { |
436 | err = seqbuf_read_u32(seqbuf: &seqbuf, v: &attrs); |
437 | if (err) |
438 | goto out; |
439 | |
440 | err = seqbuf_read_str(seqbuf: &seqbuf, str: &name); |
441 | if (err < 0) |
442 | goto out; |
443 | |
444 | if (attrs & DEBUGFS_S_ISDIR) { |
445 | size_t len; |
446 | |
447 | dentry = debugfs_create_dir(name, parent); |
448 | if (IS_ERR(ptr: dentry)) { |
449 | err = PTR_ERR(ptr: dentry); |
450 | goto out; |
451 | } |
452 | |
453 | len = snprintf(buf: pathbuf, size: pathlen, fmt: "%s%s/" , ppath, name); |
454 | if (len >= pathlen) { |
455 | err = -EINVAL; |
456 | goto out; |
457 | } |
458 | |
459 | err = bpmp_populate_debugfs_inband(bpmp, parent: dentry, |
460 | ppath: pathbuf); |
461 | if (err < 0) |
462 | goto out; |
463 | } else { |
464 | umode_t mode; |
465 | |
466 | mode = attrs & DEBUGFS_S_IRUSR ? 0400 : 0; |
467 | mode |= attrs & DEBUGFS_S_IWUSR ? 0200 : 0; |
468 | dentry = debugfs_create_file(name, mode, parent, data: bpmp, |
469 | fops: &bpmp_debug_fops); |
470 | if (IS_ERR(ptr: dentry)) { |
471 | err = -ENOMEM; |
472 | goto out; |
473 | } |
474 | } |
475 | } |
476 | |
477 | out: |
478 | kfree(objp: pathbuf); |
479 | kfree(objp: buf); |
480 | |
481 | return err; |
482 | } |
483 | |
484 | static int mrq_debugfs_read(struct tegra_bpmp *bpmp, |
485 | dma_addr_t name, size_t sz_name, |
486 | dma_addr_t data, size_t sz_data, |
487 | size_t *nbytes) |
488 | { |
489 | struct mrq_debugfs_request req = { |
490 | .cmd = CMD_DEBUGFS_READ, |
491 | .fop = { |
492 | .fnameaddr = (u32)name, |
493 | .fnamelen = (u32)sz_name, |
494 | .dataaddr = (u32)data, |
495 | .datalen = (u32)sz_data, |
496 | }, |
497 | }; |
498 | struct mrq_debugfs_response resp; |
499 | struct tegra_bpmp_message msg = { |
500 | .mrq = MRQ_DEBUGFS, |
501 | .tx = { |
502 | .data = &req, |
503 | .size = sizeof(req), |
504 | }, |
505 | .rx = { |
506 | .data = &resp, |
507 | .size = sizeof(resp), |
508 | }, |
509 | }; |
510 | int err; |
511 | |
512 | err = tegra_bpmp_transfer(bpmp, msg: &msg); |
513 | if (err < 0) |
514 | return err; |
515 | else if (msg.rx.ret < 0) |
516 | return -EINVAL; |
517 | |
518 | *nbytes = (size_t)resp.fop.nbytes; |
519 | |
520 | return 0; |
521 | } |
522 | |
523 | static int mrq_debugfs_write(struct tegra_bpmp *bpmp, |
524 | dma_addr_t name, size_t sz_name, |
525 | dma_addr_t data, size_t sz_data) |
526 | { |
527 | const struct mrq_debugfs_request req = { |
528 | .cmd = CMD_DEBUGFS_WRITE, |
529 | .fop = { |
530 | .fnameaddr = (u32)name, |
531 | .fnamelen = (u32)sz_name, |
532 | .dataaddr = (u32)data, |
533 | .datalen = (u32)sz_data, |
534 | }, |
535 | }; |
536 | struct tegra_bpmp_message msg = { |
537 | .mrq = MRQ_DEBUGFS, |
538 | .tx = { |
539 | .data = &req, |
540 | .size = sizeof(req), |
541 | }, |
542 | }; |
543 | |
544 | return tegra_bpmp_transfer(bpmp, msg: &msg); |
545 | } |
546 | |
547 | static int mrq_debugfs_dumpdir(struct tegra_bpmp *bpmp, dma_addr_t addr, |
548 | size_t size, size_t *nbytes) |
549 | { |
550 | const struct mrq_debugfs_request req = { |
551 | .cmd = CMD_DEBUGFS_DUMPDIR, |
552 | .dumpdir = { |
553 | .dataaddr = (u32)addr, |
554 | .datalen = (u32)size, |
555 | }, |
556 | }; |
557 | struct mrq_debugfs_response resp; |
558 | struct tegra_bpmp_message msg = { |
559 | .mrq = MRQ_DEBUGFS, |
560 | .tx = { |
561 | .data = &req, |
562 | .size = sizeof(req), |
563 | }, |
564 | .rx = { |
565 | .data = &resp, |
566 | .size = sizeof(resp), |
567 | }, |
568 | }; |
569 | int err; |
570 | |
571 | err = tegra_bpmp_transfer(bpmp, msg: &msg); |
572 | if (err < 0) |
573 | return err; |
574 | else if (msg.rx.ret < 0) |
575 | return -EINVAL; |
576 | |
577 | *nbytes = (size_t)resp.dumpdir.nbytes; |
578 | |
579 | return 0; |
580 | } |
581 | |
582 | static int debugfs_show(struct seq_file *m, void *p) |
583 | { |
584 | struct file *file = m->private; |
585 | struct inode *inode = file_inode(f: file); |
586 | struct tegra_bpmp *bpmp = inode->i_private; |
587 | const size_t datasize = m->size; |
588 | const size_t namesize = SZ_256; |
589 | void *datavirt, *namevirt; |
590 | dma_addr_t dataphys, namephys; |
591 | char buf[256]; |
592 | const char *filename; |
593 | size_t len, nbytes; |
594 | int err; |
595 | |
596 | filename = get_filename(bpmp, file, buf, size: sizeof(buf)); |
597 | if (!filename) |
598 | return -ENOENT; |
599 | |
600 | namevirt = dma_alloc_coherent(dev: bpmp->dev, size: namesize, dma_handle: &namephys, |
601 | GFP_KERNEL | GFP_DMA32); |
602 | if (!namevirt) |
603 | return -ENOMEM; |
604 | |
605 | datavirt = dma_alloc_coherent(dev: bpmp->dev, size: datasize, dma_handle: &dataphys, |
606 | GFP_KERNEL | GFP_DMA32); |
607 | if (!datavirt) { |
608 | err = -ENOMEM; |
609 | goto free_namebuf; |
610 | } |
611 | |
612 | len = strlen(filename); |
613 | strscpy_pad(namevirt, filename, namesize); |
614 | |
615 | err = mrq_debugfs_read(bpmp, name: namephys, sz_name: len, data: dataphys, sz_data: datasize, |
616 | nbytes: &nbytes); |
617 | |
618 | if (!err) |
619 | seq_write(seq: m, data: datavirt, len: nbytes); |
620 | |
621 | dma_free_coherent(dev: bpmp->dev, size: datasize, cpu_addr: datavirt, dma_handle: dataphys); |
622 | free_namebuf: |
623 | dma_free_coherent(dev: bpmp->dev, size: namesize, cpu_addr: namevirt, dma_handle: namephys); |
624 | |
625 | return err; |
626 | } |
627 | |
628 | static int debugfs_open(struct inode *inode, struct file *file) |
629 | { |
630 | return single_open_size(file, debugfs_show, file, SZ_128K); |
631 | } |
632 | |
633 | static ssize_t debugfs_store(struct file *file, const char __user *buf, |
634 | size_t count, loff_t *f_pos) |
635 | { |
636 | struct inode *inode = file_inode(f: file); |
637 | struct tegra_bpmp *bpmp = inode->i_private; |
638 | const size_t datasize = count; |
639 | const size_t namesize = SZ_256; |
640 | void *datavirt, *namevirt; |
641 | dma_addr_t dataphys, namephys; |
642 | char fnamebuf[256]; |
643 | const char *filename; |
644 | size_t len; |
645 | int err; |
646 | |
647 | filename = get_filename(bpmp, file, buf: fnamebuf, size: sizeof(fnamebuf)); |
648 | if (!filename) |
649 | return -ENOENT; |
650 | |
651 | namevirt = dma_alloc_coherent(dev: bpmp->dev, size: namesize, dma_handle: &namephys, |
652 | GFP_KERNEL | GFP_DMA32); |
653 | if (!namevirt) |
654 | return -ENOMEM; |
655 | |
656 | datavirt = dma_alloc_coherent(dev: bpmp->dev, size: datasize, dma_handle: &dataphys, |
657 | GFP_KERNEL | GFP_DMA32); |
658 | if (!datavirt) { |
659 | err = -ENOMEM; |
660 | goto free_namebuf; |
661 | } |
662 | |
663 | len = strlen(filename); |
664 | strscpy_pad(namevirt, filename, namesize); |
665 | |
666 | if (copy_from_user(to: datavirt, from: buf, n: count)) { |
667 | err = -EFAULT; |
668 | goto free_databuf; |
669 | } |
670 | |
671 | err = mrq_debugfs_write(bpmp, name: namephys, sz_name: len, data: dataphys, |
672 | sz_data: count); |
673 | |
674 | free_databuf: |
675 | dma_free_coherent(dev: bpmp->dev, size: datasize, cpu_addr: datavirt, dma_handle: dataphys); |
676 | free_namebuf: |
677 | dma_free_coherent(dev: bpmp->dev, size: namesize, cpu_addr: namevirt, dma_handle: namephys); |
678 | |
679 | return err ?: count; |
680 | } |
681 | |
682 | static const struct file_operations debugfs_fops = { |
683 | .open = debugfs_open, |
684 | .read = seq_read, |
685 | .llseek = seq_lseek, |
686 | .write = debugfs_store, |
687 | .release = single_release, |
688 | }; |
689 | |
690 | static int bpmp_populate_dir(struct tegra_bpmp *bpmp, struct seqbuf *seqbuf, |
691 | struct dentry *parent, u32 depth) |
692 | { |
693 | int err; |
694 | u32 d, t; |
695 | const char *name; |
696 | struct dentry *dentry; |
697 | |
698 | while (!seqbuf_eof(seqbuf)) { |
699 | err = seqbuf_read_u32(seqbuf, v: &d); |
700 | if (err < 0) |
701 | return err; |
702 | |
703 | if (d < depth) { |
704 | seqbuf_seek(seqbuf, offset: -4); |
705 | /* go up a level */ |
706 | return 0; |
707 | } else if (d != depth) { |
708 | /* malformed data received from BPMP */ |
709 | return -EIO; |
710 | } |
711 | |
712 | err = seqbuf_read_u32(seqbuf, v: &t); |
713 | if (err < 0) |
714 | return err; |
715 | err = seqbuf_read_str(seqbuf, str: &name); |
716 | if (err < 0) |
717 | return err; |
718 | |
719 | if (t & DEBUGFS_S_ISDIR) { |
720 | dentry = debugfs_create_dir(name, parent); |
721 | if (IS_ERR(ptr: dentry)) |
722 | return -ENOMEM; |
723 | err = bpmp_populate_dir(bpmp, seqbuf, parent: dentry, depth: depth+1); |
724 | if (err < 0) |
725 | return err; |
726 | } else { |
727 | umode_t mode; |
728 | |
729 | mode = t & DEBUGFS_S_IRUSR ? S_IRUSR : 0; |
730 | mode |= t & DEBUGFS_S_IWUSR ? S_IWUSR : 0; |
731 | dentry = debugfs_create_file(name, mode, |
732 | parent, data: bpmp, |
733 | fops: &debugfs_fops); |
734 | if (IS_ERR(ptr: dentry)) |
735 | return -ENOMEM; |
736 | } |
737 | } |
738 | |
739 | return 0; |
740 | } |
741 | |
742 | static int bpmp_populate_debugfs_shmem(struct tegra_bpmp *bpmp) |
743 | { |
744 | struct seqbuf seqbuf; |
745 | const size_t sz = SZ_512K; |
746 | dma_addr_t phys; |
747 | size_t nbytes; |
748 | void *virt; |
749 | int err; |
750 | |
751 | virt = dma_alloc_coherent(dev: bpmp->dev, size: sz, dma_handle: &phys, |
752 | GFP_KERNEL | GFP_DMA32); |
753 | if (!virt) |
754 | return -ENOMEM; |
755 | |
756 | err = mrq_debugfs_dumpdir(bpmp, addr: phys, size: sz, nbytes: &nbytes); |
757 | if (err < 0) { |
758 | goto free; |
759 | } else if (nbytes > sz) { |
760 | err = -EINVAL; |
761 | goto free; |
762 | } |
763 | |
764 | seqbuf_init(seqbuf: &seqbuf, buf: virt, size: nbytes); |
765 | err = bpmp_populate_dir(bpmp, seqbuf: &seqbuf, parent: bpmp->debugfs_mirror, depth: 0); |
766 | free: |
767 | dma_free_coherent(dev: bpmp->dev, size: sz, cpu_addr: virt, dma_handle: phys); |
768 | |
769 | return err; |
770 | } |
771 | |
772 | int tegra_bpmp_init_debugfs(struct tegra_bpmp *bpmp) |
773 | { |
774 | struct dentry *root; |
775 | bool inband; |
776 | int err; |
777 | |
778 | inband = tegra_bpmp_mrq_is_supported(bpmp, MRQ_DEBUG); |
779 | |
780 | if (!inband && !tegra_bpmp_mrq_is_supported(bpmp, MRQ_DEBUGFS)) |
781 | return 0; |
782 | |
783 | root = debugfs_create_dir(name: "bpmp" , NULL); |
784 | if (IS_ERR(ptr: root)) |
785 | return -ENOMEM; |
786 | |
787 | bpmp->debugfs_mirror = debugfs_create_dir(name: "debug" , parent: root); |
788 | if (IS_ERR(ptr: bpmp->debugfs_mirror)) { |
789 | err = -ENOMEM; |
790 | goto out; |
791 | } |
792 | |
793 | if (inband) |
794 | err = bpmp_populate_debugfs_inband(bpmp, parent: bpmp->debugfs_mirror, |
795 | ppath: "/" ); |
796 | else |
797 | err = bpmp_populate_debugfs_shmem(bpmp); |
798 | |
799 | out: |
800 | if (err < 0) |
801 | debugfs_remove_recursive(dentry: root); |
802 | |
803 | return err; |
804 | } |
805 | |