/*      $NetBSD: fdt_ddb.c,v 1.2 2021/03/06 13:21:26 skrll Exp $        */

/*-
* Copyright (c) 2020 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Nick Hudson
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
*    notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
*    notice, this list of conditions and the following disclaimer in the
*    documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/


#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: fdt_ddb.c,v 1.2 2021/03/06 13:21:26 skrll Exp $");

#include <sys/param.h>

#include <libfdt.h>
#include <dev/fdt/fdt_ddb.h>
#include <dev/fdt/fdtvar.h>

#define FDT_MAX_DEPTH   16

static bool
fdt_isprint(const void *data, int len)
{
       const uint8_t *c = (const uint8_t *)data;

       if (len == 0)
               return false;

       /* Count consecutive zeroes */
       int cz = 0;
       for (size_t j = 0; j < len; j++) {
               if (c[j] == '\0')
                       cz++;
               else if (isprint(c[j]))
                       cz = 0;
               else
                       return false;
               if (cz > 1)
                       return false;
       }
       return true;
}

static void
fdt_print_properties(const void *fdt, int node,
   void (*pr)(const char *, ...) __printflike(1, 2))
{
       int property;

       fdt_for_each_property_offset(property, fdt, node) {
               int len;
               const struct fdt_property *prop =
                   fdt_get_property_by_offset(fdt, property, &len);
               const char *name = fdt_string(fdt, fdt32_to_cpu(prop->nameoff));

               pr("    %s", name);
               if (len == 0) {
                       pr("\n");
                       continue;
               }
               if (fdt_isprint(prop->data, len)) {
                       const uint8_t *c = (const uint8_t *)prop->data;

                       pr(" = \"");
                       for (size_t j = 0; j < len; j++) {
                               if (c[j] == '\0') {
                                       if (j + 1 != len)
                                               pr("\", \"");
                               } else
                                       pr("%c", c[j]);
                       }
                       pr("\"\n");
                       continue;
               }
               if ((len % 4) == 0) {
                       const uint32_t *cell = (const uint32_t *)prop->data;
                       size_t count = len / sizeof(uint32_t);

                       pr(" = <");
                       for (size_t j = 0; j < count; j++) {
                               pr("%#" PRIx32 "%s", fdt32_to_cpu(cell[j]),
                                   (j != count - 1) ? " " : "");
                       }
                       pr(">\n");
               } else {
                       const uint8_t *byte = (const uint8_t *)prop->data;

                       pr(" = [");
                       for (size_t j = 0; j < len; j++) {
                               pr("%02x%s", byte[j],
                                  (j != len - 1) ? " " : "");
                       }
                       pr("]\n");
               }
       }
}


void
fdt_print(const void *addr, bool full,
   void (*pr)(const char *, ...) __printflike(1, 2))
{
       const void *fdt = addr;
       const char *pname[FDT_MAX_DEPTH] = { NULL };

       int error = fdt_check_header(fdt);
       if (error) {
               pr("Invalid FDT at %p\n", fdt);
               return;
       }

       int depth = 0;
       for (int node = fdt_path_offset(fdt, "/");
            node >= 0 && depth >= 0;
            node = fdt_next_node(fdt, node, &depth)) {
               const char *name = fdt_get_name(fdt, node, NULL);

               if (depth > FDT_MAX_DEPTH) {
                       pr("max depth exceeded: %d\n", depth);
                       continue;
               }
               pname[depth] = name;
               /*
                * change conditional for when alternative root nodes
                * can be specified
                */
               if (depth == 0)
                       pr("/");
               for (size_t i = 1; i <= depth; i++) {
                       if (pname[i] == NULL)
                               break;
                       pr("/%s", pname[i]);
               }
               pr("\n");
               if (!full)
                       continue;
               fdt_print_properties(fdt, node, pr);
       }
}