Files
rajamesandClaude Opus 5.5 c95191736e fix(console): the serial line is found, not assumed -- from the devicetree on a board, COM1 by probe on a PC
Each board has one serial line that is its console, the lowest common
denominator beside the screen and keyboard.  Its address was QEMU's,
fixed: on a Raspberry Pi 5 the aarch64 one is ordinary RAM, written to
with every character printed; on a Milk-V Mars the riscv64 one is the
board's UART with registers four bytes apart, and the UEFI loader would
have waited for ever on its first character; on a PC with no COM1 the
prompt read 0xFF for ever.

console_attach_serial(), called before anything prints: COM1 if a UART
answers there; elsewhere the UART that /chosen/stdout-path names
(fdt_uart.c: alias, path, the address carried up through each bus's
ranges, reg-shift and reg-io-width); with no devicetree, QEMU's.  A
devicetree that names no usable console gives no line, not a guess.  A
line that does not come ready is given up, not waited on.  The riscv64
loader's early serial does the same.

tools/fdt_uart_test.c, run by hand, 19 checks plain and under the
sanitizers: devicetrees shaped like the Mars's, the Pi 5's and QEMU's,
what must find nothing, and a block cut short at every length; and the
Pi 5's real devicetree gives a PL011 at 0x107d001000.

Acceptance, one QEMU at a time: v3 on amd64 (logs/20261013-204436),
aarch64 (-204803) and riscv64 (-205001) to the zuse prompt, no unknown
word, hash 0x08873e0f44b7cb2a on all three as before.  -204600 is an
aarch64 run during which the disk images were restored under it by a
script that had been refused a second QEMU; it passed and is kept, and
was run again.  v4 on the three ISAs (-211149, -211444, -211857): POST
538 of 538, dict_hash 0xe5a57f3eb72ff94a, the typed session unchanged.
The Mars image through OpenSBI and U-Boot in QEMU took its line and its
interrupt controller from the devicetree and passed POST.

Found on the way and not mended (boards/BRINGUP.md 1.6): built with
EMERGENCY_CONSOLE_ENABLED=1 the prompt floods with empty lines.  -210442
shows it; the commit before this does the same.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 21:21:32 -04:00

204 lines
9.0 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James. All rights reserved.
Licensed under the StarForth License, Version 1.0.
*/
/**
* fdt.h - Minimal flattened-devicetree reader
*
* Just enough of the Devicetree Specification v0.4 §5 to pull values out of
* the blob the UEFI firmware publishes under EFI_DTB_TABLE_GUID, or that a
* native (non-UEFI) boot entry passes directly. Read-only, no allocation, no
* tree construction — it walks the structure block each call, which is fine
* for the handful of boot-time lookups the kernel needs.
*
* Deliberately not a general devicetree library. Added for punch-list item
* 0.3 (riscv64 timebase-frequency); extended (FABRIC-3.md §IV.3/§V.3,
* 2026-09-04) with node-scoped lookup, for exactly the case this header
* originally flagged as a future need (item 0.6's aarch64 GIC) plus its
* real, concrete consumers as of this pass: the Raspberry Pi 5's UART/
* mailbox register addresses (native boot, no ACPI) and the Milk-V Mars's
* real PLIC base address (currently hardcoded to QEMU-virt's own value,
* `arch/riscv64/plic.c`'s own doc comment already warned this isn't
* assumed stable across configurations).
*/
#ifndef STARKERNEL_FDT_H
#define STARKERNEL_FDT_H
#include <stdint.h>
/**
* @brief Test whether @p fdt points at a valid flattened devicetree.
*
* Checks the 0xd00dfeed magic and that the structure and strings blocks lie
* inside totalsize. Does not validate the token stream.
*
* @param fdt Candidate blob; NULL is safe and returns 0.
* @return 1 if the header is usable, 0 otherwise.
*/
int fdt_valid(const void* fdt);
/**
* @brief Find the first property with @p name anywhere in the tree.
*
* Scans the structure block in document order and returns the first match
* regardless of which node it belongs to. That is sufficient for properties
* which are uniform across a machine (timebase-frequency being the case this
* was written for) and is *not* sufficient for anything node-scoped.
*
* @param fdt Blob, already checked with @c fdt_valid().
* @param name Property name, NUL-terminated.
* @param len_out Receives the property length in bytes; may be NULL.
* @return Pointer to the property value inside @p fdt, or NULL if not found.
* The value is big-endian as stored in the blob.
*/
const void* fdt_find_prop(const void* fdt, const char* name, uint32_t* len_out);
/**
* @brief Read a single-cell (32-bit) property by name.
*
* Convenience over @c fdt_find_prop() that also handles the big-endian
* conversion. Fails if the property is absent or not exactly 4 bytes.
*
* @param fdt Blob, already checked with @c fdt_valid().
* @param name Property name, NUL-terminated.
* @param out Receives the host-order value on success; untouched on failure.
* @return 1 on success, 0 on failure.
*/
int fdt_prop_u32(const void* fdt, const char* name, uint32_t* out);
/**
* @brief Find the first node whose "compatible" property matches @p compatible.
*
* "compatible" is a NUL-separated list of strings (DT spec §2.3.1) — matches
* if @p compatible equals any one entry in the list, not just the whole
* property verbatim. Scans the whole tree in document order; the first
* matching node wins if more than one exists.
*
* @param fdt Blob, already checked with @c fdt_valid().
* @param compatible Compatible string to match, NUL-terminated.
* @return An opaque handle to the matched node, for use with
* @c fdt_find_prop_in_node() only (not a raw offset or a pointer
* to anything else meaningful) — or NULL if no node matches.
*/
const void* fdt_find_node_by_compatible(const void* fdt, const char* compatible);
/**
* @brief Find the first node whose "device_type" property equals @p type.
*
* Some standard nodes (`/memory` per DT spec §3.4) are identified by
* `device_type`, not `compatible` — unlike `compatible`, `device_type` is a
* single NUL-terminated string, not a list, so this matches the whole
* property value rather than scanning entries within it. Scans the whole
* tree in document order; the first matching node wins if more than one
* exists.
*
* @param fdt Blob, already checked with @c fdt_valid().
* @param type device_type value to match, NUL-terminated.
* @return An opaque handle to the matched node, for use with
* @c fdt_find_prop_in_node() only — or NULL if no node matches.
*/
const void* fdt_find_node_by_device_type(const void* fdt, const char* type);
/**
* @brief Find the first node whose own name matches @p name.
*
* Node names follow the DT spec §2.2.1 `name[@unit-address]` convention —
* matches if @p name equals the node's name up to (not including) an `@`
* suffix, or the whole name if there is none. For a singleton node with
* no unit address (`/reserved-memory` being the concrete case this was
* added for), this is an exact match. Scans the whole tree in document
* order; the first matching node wins if more than one exists.
*
* @param fdt Blob, already checked with @c fdt_valid().
* @param name Node name to match, NUL-terminated, no `@` suffix.
* @return An opaque handle to the matched node, for use with
* @c fdt_find_prop_in_node() / @c fdt_next_child_node() only —
* or NULL if no node matches.
*/
const void* fdt_find_node_by_name(const void* fdt, const char* name);
/**
* @brief Iterate the direct children of one node.
*
* Pass @p prev_child as NULL to get the first child; pass a previous
* result back in to get the next one. Stops (returns NULL) once there are
* no more children. Skips over each child's own descendants correctly
* (so a child with grandchildren doesn't confuse the scan), but does not
* itself descend into them — only direct children of @p parent are ever
* returned.
*
* @param fdt Blob, already checked with @c fdt_valid().
* @param parent Handle from one of the `fdt_find_node_by_*()`
* functions.
* @param prev_child NULL for the first child, or a handle previously
* returned by this function for @p parent to
* continue from.
* @return Handle to the next direct child, for use with
* @c fdt_find_prop_in_node() / @c fdt_next_child_node() only —
* or NULL once @p parent's children are exhausted.
*/
const void* fdt_next_child_node(const void* fdt, const void* parent,
const void* prev_child);
/**
* @brief Find a property by name, scoped to one node.
*
* Like @c fdt_find_prop(), but scans only @p node's own direct properties
* (as returned by @c fdt_find_node_by_compatible()) — stops at the first
* child node or the end of @p node's property list, never descends into
* children, never continues into a sibling. This is the difference that
* matters for a property name like "reg", which is not unique across the
* tree the way "timebase-frequency" (the whole reason @c fdt_find_prop()
* was originally sufficient) happens to be.
*
* @param fdt Blob, already checked with @c fdt_valid().
* @param node Handle from @c fdt_find_node_by_compatible(); NULL is
* safe and returns NULL (propagates a failed node lookup
* without a separate caller-side check).
* @param name Property name, NUL-terminated.
* @param len_out Receives the property length in bytes; may be NULL.
* @return Pointer to the property value inside @p fdt, or NULL if not
* found (or if @p node is NULL). The value is big-endian as
* stored in the blob.
*/
const void* fdt_find_prop_in_node(const void* fdt, const void* node,
const char* name, uint32_t* len_out);
/**
* @brief Is the structure block whole?
* @c fdt_valid() checks the header; this checks also that the structure
* block ends with its end token, as a blob that has not been cut short
* does. A reader that takes a property's absence to mean its default
* (a UART's "reg-shift") must not be handed half a tree.
* @return 1 if @p fdt is valid and its structure block ends with FDT_END.
*/
int fdt_complete(const void* fdt);
/**
* @brief The root node.
* @param fdt Blob; checked with @c fdt_valid().
* @return Handle to the root node, for @c fdt_find_child() and
* @c fdt_find_prop_in_node(), or NULL.
*/
const void* fdt_root_node(const void* fdt);
/**
* @brief Find a direct child of a node by its whole name.
* The name is matched exactly, unit address included ("serial@10000000"
* is not "serial"), and is given with its length so that one component of
* a path can be passed without copying it. Only @p parent's own children
* are looked at, never their descendants.
* @param fdt Blob; checked with @c fdt_valid().
* @param parent Node handle; NULL is safe and returns NULL.
* @param name The child's name; need not be NUL-terminated.
* @param name_len Its length in bytes.
* @return Handle to the child, or NULL if @p parent has none of that name.
*/
const void* fdt_find_child(const void* fdt, const void* parent,
const char* name, uint32_t name_len);
#endif /* STARKERNEL_FDT_H */