xen/device-tree: add a helper to dump an FDT in DTS-like format

The device tree Xen builds for dom0/hwdom or for a dom0less domain is
only ever consumed by the guest, so a mistake in it surfaces as an
obscure failure inside the domain, far away from the code which
produced the tree.

Add device_tree_dump(). The output follows dtc's format so that a dump
can be diffed against the source DTS or fed back to dtc; the value
formatting heuristics are ported from dtc's utilfdt_print_data() for
that reason. The walk itself goes through libfdt rather than decoding
the tag stream by hand as dtc's fdtdump does.

Dumping a whole tree is only useful while debugging and costs both code
size and a lot of console output, so it is put under its own option,
CONFIG_DEVICE_TREE_DUMP. It is separate from CONFIG_DEVICE_TREE_DEBUG
because the generated tree is often all that is needed, without the rest
of the device tree debug messages.

Signed-off-by: Oleksii Kurochko <oleksii.kurochko@gmail.com>
Reviewed-by: Michal Orzel <michal.orzel@amd.com>
This commit is contained in:
Oleksii Kurochko authored and Michal Orzel committed 2026-10-15 14:37:19 +02:00
1 parent 1924f91d45
commit 7e020a4dd6
6 files changed
+219

No files matched your search

+9
View File
@@ -98,6 +98,15 @@ config DEVICE_TREE_DEBUG
logged in the Xen ring buffer. logged in the Xen ring buffer.
If unsure, say N here. If unsure, say N here.
config DEVICE_TREE_DUMP
bool "Dump generated device trees"
depends on DEVICE_TREE_PARSE
help
The device tree Xen generates for dom0/hwdom and for each dom0less
domain is printed on the Xen console in a DTS-like format.
The dump is logged at debug level.
If unsure, say N here.
config SCRUB_DEBUG config SCRUB_DEBUG
bool "Page scrubbing test" bool "Page scrubbing test"
default DEBUG default DEBUG
+2
View File
@@ -1706,6 +1706,8 @@ static int __init prepare_dtb_hwdom(struct domain *d, struct kernel_info *kinfo)
if ( ret < 0 ) if ( ret < 0 )
goto err; goto err;
device_tree_dump(d, kinfo->fdt);
return 0; return 0;
err: err:
+1
View File
@@ -6,6 +6,7 @@ obj-$(CONFIG_DOMAIN_BUILD_HELPERS) += domain-build.init.o
obj-$(filter $(CONFIG_DOM0LESS_BOOT),$(CONFIG_HAS_DEVICE_TREE_DISCOVERY)) += dom0less-build.init.o obj-$(filter $(CONFIG_DOM0LESS_BOOT),$(CONFIG_HAS_DEVICE_TREE_DISCOVERY)) += dom0less-build.init.o
obj-$(CONFIG_DOM0LESS_BOOT) += dom0less-bindings.init.o obj-$(CONFIG_DOM0LESS_BOOT) += dom0less-bindings.init.o
obj-$(CONFIG_OVERLAY_DTB) += dt-overlay.o obj-$(CONFIG_OVERLAY_DTB) += dt-overlay.o
obj-$(CONFIG_DEVICE_TREE_DUMP) += fdt-dump.init.o
obj-$(CONFIG_HAS_DEVICE_TREE_DISCOVERY) += intc.o obj-$(CONFIG_HAS_DEVICE_TREE_DISCOVERY) += intc.o
obj-$(CONFIG_DOMAIN_BUILD_HELPERS) += kernel.o obj-$(CONFIG_DOMAIN_BUILD_HELPERS) += kernel.o
obj-$(CONFIG_STATIC_EVTCHN) += static-evtchn.init.o obj-$(CONFIG_STATIC_EVTCHN) += static-evtchn.init.o
+3
View File
@@ -1,5 +1,6 @@
/* SPDX-License-Identifier: GPL-2.0-only */ /* SPDX-License-Identifier: GPL-2.0-only */
#include <xen/bootfdt.h>
#include <xen/bootinfo.h> #include <xen/bootinfo.h>
#include <xen/device_tree.h> #include <xen/device_tree.h>
#include <xen/dom0less-build.h> #include <xen/dom0less-build.h>
@@ -587,6 +588,8 @@ static int __init prepare_dtb_domU(struct domain *d, struct kernel_info *kinfo)
if ( ret < 0 ) if ( ret < 0 )
goto err; goto err;
device_tree_dump(d, kinfo->fdt);
return 0; return 0;
err: err:
+188
View File
@@ -0,0 +1,188 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Dump a flattened device tree blob in a DTS-like format.
*
* is_printable_string() and print_value() are derived from
* util_is_printable_string() and utilfdt_print_data() in the device tree
* compiler (dtc).
*
* Copyright 2011 The Chromium Authors, All Rights Reserved.
* Copyright 2008 Jon Loeliger, Freescale Semiconductor, Inc.
* util_is_printable_string contributed by
* Pantelis Antoniou <pantelis.antoniou AT gmail.com>
*/
#include <xen/bootfdt.h>
#include <xen/ctype.h>
#include <xen/init.h>
#include <xen/lib.h>
#include <xen/libfdt/libfdt.h>
#include <xen/types.h>
/* Deeper nodes are still printed, but not indented any further. */
#define DUMP_MAX_INDENT_DEPTH 32
/* Spaces per nesting level. */
#define DUMP_INDENT_WIDTH 4
/* Indentation, in characters, of a node at depth @depth. */
static int __init indent_of(int depth)
{
return DUMP_INDENT_WIDTH * min(depth, DUMP_MAX_INDENT_DEPTH);
}
static bool __init is_printable_string(const void *data, unsigned int len)
{
const char *s = data;
const char *se = s + len;
/* A zero length property, or one which isn't NUL terminated, isn't one. */
if ( (len == 0) || (s[len - 1] != '\0') )
return false;
while ( s < se )
{
const char *ss = s;
/*
* Xen's isprint() also accepts the characters 0xa0-0xff, while the
* one dtc uses only accepts ASCII ones.
*/
while ( (s < se) && *s && isascii(*s) && isprint(*s) )
s++;
/* Stopped on a non printable character, or the string is empty. */
if ( (*s != '\0') || (s == ss) )
return false;
s++;
}
return true;
}
/*
* Print the value of a property, using the same heuristics as dtc: a list of
* NUL terminated printable strings is printed as such, a value which is a
* multiple of 4 bytes as a list of cells, anything else as a byte string.
*
* The caller has already started the line (and with it selected the log
* level), so no XENLOG_* prefix may be used here.
*/
static void __init print_value(const void *data, unsigned int len)
{
unsigned int i;
/* Empty property, nothing to print. */
if ( len == 0 )
return;
if ( is_printable_string(data, len) )
{
const char *s = data;
const char *end = data + len;
printk(" = ");
while ( s < end )
{
printk("\"%s\"", s);
s += strlen(s) + 1;
if ( s < end )
printk(", ");
}
}
else if ( IS_ALIGNED(len, sizeof(fdt32_t)) )
{
const fdt32_t *cell = data;
printk(" = <");
for ( i = 0; i < len / sizeof(*cell); i++ )
printk("%s0x%08x", i ? " " : "", fdt32_ld(&cell[i]));
printk(">");
}
else
{
const uint8_t *byte = data;
printk(" = [");
for ( i = 0; i < len; i++ )
printk("%s%02x", i ? " " : "", byte[i]);
printk("]");
}
}
static void __init print_properties(const void *fdt, int node, int indent)
{
int prop;
fdt_for_each_property_offset ( prop, fdt, node )
{
const char *name;
const void *data;
int len;
data = fdt_getprop_by_offset(fdt, prop, &name, &len);
if ( !data )
{
printk(XENLOG_DEBUG "%*scouldn't read property at offset %d: %s\n",
indent, "", prop, fdt_strerror(len));
return;
}
printk(XENLOG_DEBUG "%*s%s", indent, "", name);
print_value(data, len);
printk(";\n");
}
if ( (prop < 0) && (prop != -FDT_ERR_NOTFOUND) )
printk(XENLOG_DEBUG "%*scouldn't walk the properties: %s\n",
indent, "", fdt_strerror(prop));
}
void __init device_tree_dump(const struct domain *d, const void *fdt)
{
int node, depth = 0;
/* Depth up to which nodes are open and still have to be closed. */
int open_depth = 0;
printk(XENLOG_DEBUG "Device tree for %pd:\n", d);
printk(XENLOG_DEBUG "/dts-v1/;\n");
/*
* fdt_next_node() signals the end of the subtree it was started on by
* making the depth negative, and only returns an error when the blob
* itself is malformed.
*/
for ( node = 0; (node >= 0) && (depth >= 0);
node = fdt_next_node(fdt, node, &depth) )
{
const char *name = fdt_get_name(fdt, node, NULL);
/* Close the nodes which aren't a parent of the current one. */
while ( open_depth > depth )
printk(XENLOG_DEBUG "%*s};\n", indent_of(--open_depth), "");
printk(XENLOG_DEBUG "%*s%s {\n", indent_of(depth), "",
(depth == 0) ? "/" : (name ?: "<unknown>"));
print_properties(fdt, node, indent_of(depth) + DUMP_INDENT_WIDTH);
open_depth = depth + 1;
}
while ( open_depth > 0 )
printk(XENLOG_DEBUG "%*s};\n", indent_of(--open_depth), "");
if ( node < 0 )
printk(XENLOG_DEBUG "The device tree dump is incomplete: %s\n",
fdt_strerror(node));
}
/*
* Local variables:
* mode: C
* c-file-style: "BSD"
* c-basic-offset: 4
* tab-width: 4
* indent-tabs-mode: nil
* End:
*/
+16
View File
@@ -99,6 +99,22 @@ int device_tree_for_each_node(const void *fdt, int node,
device_tree_node_func func, device_tree_node_func func,
void *data); void *data);
struct domain;
/*
* device_tree_dump - print a device tree blob in a DTS-like format
* @d: domain the device tree was generated for.
* @fdt: flat device tree.
*
* The tree is printed at debug log level and is only meant to be used for
* debugging, so it is a no-op unless CONFIG_DEVICE_TREE_DUMP is enabled.
*/
#ifdef CONFIG_DEVICE_TREE_DUMP
void device_tree_dump(const struct domain *d, const void *fdt);
#else
static inline void device_tree_dump(const struct domain *d, const void *fdt) {}
#endif
typedef enum { typedef enum {
BOOTMOD_XEN, BOOTMOD_XEN,
BOOTMOD_FDT, BOOTMOD_FDT,
Reference in new issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.