diff --git a/xen/Kconfig.debug b/xen/Kconfig.debug index d900d926c5..8e90209b8b 100644 --- a/xen/Kconfig.debug +++ b/xen/Kconfig.debug @@ -98,6 +98,15 @@ config DEVICE_TREE_DEBUG logged in the Xen ring buffer. 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 bool "Page scrubbing test" default DEBUG diff --git a/xen/arch/arm/domain_build.c b/xen/arch/arm/domain_build.c index 8cc5ac91f8..72f6f7539a 100644 --- a/xen/arch/arm/domain_build.c +++ b/xen/arch/arm/domain_build.c @@ -1706,6 +1706,8 @@ static int __init prepare_dtb_hwdom(struct domain *d, struct kernel_info *kinfo) if ( ret < 0 ) goto err; + device_tree_dump(d, kinfo->fdt); + return 0; err: diff --git a/xen/common/device-tree/Makefile b/xen/common/device-tree/Makefile index 9036e455d6..730d202349 100644 --- a/xen/common/device-tree/Makefile +++ b/xen/common/device-tree/Makefile @@ -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-$(CONFIG_DOM0LESS_BOOT) += dom0less-bindings.init.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_DOMAIN_BUILD_HELPERS) += kernel.o obj-$(CONFIG_STATIC_EVTCHN) += static-evtchn.init.o diff --git a/xen/common/device-tree/dom0less-build.c b/xen/common/device-tree/dom0less-build.c index 2021838d38..fcb438c50a 100644 --- a/xen/common/device-tree/dom0less-build.c +++ b/xen/common/device-tree/dom0less-build.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0-only */ +#include #include #include #include @@ -587,6 +588,8 @@ static int __init prepare_dtb_domU(struct domain *d, struct kernel_info *kinfo) if ( ret < 0 ) goto err; + device_tree_dump(d, kinfo->fdt); + return 0; err: diff --git a/xen/common/device-tree/fdt-dump.c b/xen/common/device-tree/fdt-dump.c new file mode 100644 index 0000000000..0f5176de8b --- /dev/null +++ b/xen/common/device-tree/fdt-dump.c @@ -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 + */ + +#include +#include +#include +#include +#include +#include + +/* 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 ?: "")); + 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: + */ diff --git a/xen/include/xen/bootfdt.h b/xen/include/xen/bootfdt.h index 0e82ccea2f..b19e0fa41a 100644 --- a/xen/include/xen/bootfdt.h +++ b/xen/include/xen/bootfdt.h @@ -99,6 +99,22 @@ int device_tree_for_each_node(const void *fdt, int node, device_tree_node_func func, 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 { BOOTMOD_XEN, BOOTMOD_FDT,