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