mirror of
https://github.com/xen-project/xen.git
synced 2026-10-28 02:01:24 +01:00
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:
1 parent
1924f91d45
commit
7e020a4dd6
6 files changed
+219
No files matched your search
@@ -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
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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:
|
||||||
|
*/
|
||||||
@@ -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,
|
||||||
|
|||||||