Summary
Penguin can inject/model pseudofiles under most kernel special filesystems
(procfs, sysfs, devfs), but it cannot model the OF device-tree exposed at
/proc/device-tree/* and /sys/firmware/devicetree/base/*. Firmware that reads
its hardware description from the device tree (model, platform, compatible,
per-peripheral nodes) therefore sees the donor machine's tree (e.g. the generic
QEMU malta compatible = "mti,malta") instead of anything representative of the
target, and there is currently no way to override it.
Root cause
The device-tree case is not "a missing backend" — it's that the existing
backends use the wrong primitive for this filesystem. Both pseudofile backends
model a path by creating a node in the target fs:
portal_procfs.c → proc_create_data(...)
portal_sysfs.c → sysfs_create_file(...)
The OF device-tree does not accept node creation:
- procfs:
/proc/device-tree already exists (it's a symlink to
/sys/firmware/devicetree/base), so creating under it collides —
portal_procfs: Collision: 'device-tree' exists but is a file.
- sysfs:
/sys/firmware/devicetree/base/ is a view of the kernel's
unflattened OF tree (of_root). Its kobjects reject arbitrary attribute
creation — sysfs_create_file returns 0/fails:
plugins.sysfs: Failed to register sysfs 'firmware/devicetree/base/model' (kernel returned 0).
Node-creation is simply not how the OF tree is populated. It is unflattened from
the FDT at boot into an in-kernel struct device_node tree; the procfs and
sysfs paths are both read-only projections of that tree.
Why this matters (device-agnostic)
This is a general rehosting gap, not tied to any one target. Any firmware whose
early boot / board-detection / identity logic consults the device tree is
affected. Common consumers:
- board-detection and
model/platform/compatible reads (identity, sysinfo,
board.json population);
- drivers and init scripts that enumerate peripherals from DT nodes (GPIO/LED
controllers, switches, i2c/spi devices, pinctrl) — when the node is absent the
driver never binds, cascading into missing /sys/class/* entries that then
have to be stubbed one by one downstream;
- anything that keys behavior off
/proc/device-tree/* values.
Because the model/platform/compatible come from the donor machine, the guest's
notion of "what hardware am I" is fixed to the emulator's board, which is often
the first thing target firmware branches on.
Proposed fix
Option A (preferred): patch the live OF tree
After boot the OF tree is a mutable in-kernel struct device_node tree
rooted at of_root. Both the procfs and sysfs views derive from it, so a single
override is reflected in both — and it matches exactly what a real DTB would have
provided. Add a portal op that manipulates the live tree via the kernel's own
GPL-exported OF APIs:
of_find_node_by_path() / of_get_child_by_name() to locate/attach;
of_update_property() / of_add_property() (or of_changeset_*) to set
property values;
of_attach_node() / node allocation to add missing nodes.
Surface it in penguin config as a device_tree: (or devicetree:) section, e.g.:
device_tree:
/:
model: "Example Vendor ExampleBoard"
compatible: ["vendor,board", "vendor,soc"]
/some-peripheral@10000000:
compatible: "vendor,widget"
status: "okay"
This is minimal, generic, and fixes procfs + sysfs simultaneously.
Option B (fallback, more general): read-path interception
Intercept the read / ->show of an already-existing immutable special file
and substitute contents without creating a node. Heavier, but covers the OF tree
plus any other read-only kernel-backed file where node creation is refused.
Acceptance criteria
- A rehosting config can set arbitrary
/proc/device-tree/* (equivalently
/sys/firmware/devicetree/base/*) property values and add nodes.
- Overrides are visible through both the procfs and sysfs projections.
- Works on the donor kernels Penguin ships (no dependency on a specific board);
no regression to existing procfs/sysfs/devfs pseudofile modeling.
Notes
Today the workaround is to override the consuming userland scripts via
static_files/lib_inject, which is per-consumer and brittle. A driver-level
OF override removes that whole class of workaround.
Summary
Penguin can inject/model pseudofiles under most kernel special filesystems
(procfs, sysfs, devfs), but it cannot model the OF device-tree exposed at
/proc/device-tree/*and/sys/firmware/devicetree/base/*. Firmware that readsits hardware description from the device tree (model, platform, compatible,
per-peripheral nodes) therefore sees the donor machine's tree (e.g. the generic
QEMU malta
compatible = "mti,malta") instead of anything representative of thetarget, and there is currently no way to override it.
Root cause
The device-tree case is not "a missing backend" — it's that the existing
backends use the wrong primitive for this filesystem. Both pseudofile backends
model a path by creating a node in the target fs:
portal_procfs.c→proc_create_data(...)portal_sysfs.c→sysfs_create_file(...)The OF device-tree does not accept node creation:
/proc/device-treealready exists (it's a symlink to/sys/firmware/devicetree/base), so creating under it collides —portal_procfs: Collision: 'device-tree' exists but is a file./sys/firmware/devicetree/base/is a view of the kernel'sunflattened OF tree (
of_root). Its kobjects reject arbitrary attributecreation —
sysfs_create_filereturns 0/fails:plugins.sysfs: Failed to register sysfs 'firmware/devicetree/base/model' (kernel returned 0).Node-creation is simply not how the OF tree is populated. It is unflattened from
the FDT at boot into an in-kernel
struct device_nodetree; the procfs andsysfs paths are both read-only projections of that tree.
Why this matters (device-agnostic)
This is a general rehosting gap, not tied to any one target. Any firmware whose
early boot / board-detection / identity logic consults the device tree is
affected. Common consumers:
model/platform/compatiblereads (identity, sysinfo,board.json population);
controllers, switches, i2c/spi devices, pinctrl) — when the node is absent the
driver never binds, cascading into missing
/sys/class/*entries that thenhave to be stubbed one by one downstream;
/proc/device-tree/*values.Because the model/platform/compatible come from the donor machine, the guest's
notion of "what hardware am I" is fixed to the emulator's board, which is often
the first thing target firmware branches on.
Proposed fix
Option A (preferred): patch the live OF tree
After boot the OF tree is a mutable in-kernel
struct device_nodetreerooted at
of_root. Both the procfs and sysfs views derive from it, so a singleoverride is reflected in both — and it matches exactly what a real DTB would have
provided. Add a portal op that manipulates the live tree via the kernel's own
GPL-exported OF APIs:
of_find_node_by_path()/of_get_child_by_name()to locate/attach;of_update_property()/of_add_property()(orof_changeset_*) to setproperty values;
of_attach_node()/ node allocation to add missing nodes.Surface it in penguin config as a
device_tree:(ordevicetree:) section, e.g.:This is minimal, generic, and fixes procfs + sysfs simultaneously.
Option B (fallback, more general): read-path interception
Intercept the read /
->showof an already-existing immutable special fileand substitute contents without creating a node. Heavier, but covers the OF tree
plus any other read-only kernel-backed file where node creation is refused.
Acceptance criteria
/proc/device-tree/*(equivalently/sys/firmware/devicetree/base/*) property values and add nodes.no regression to existing procfs/sysfs/devfs pseudofile modeling.
Notes
Today the workaround is to override the consuming userland scripts via
static_files/lib_inject, which is per-consumer and brittle. A driver-levelOF override removes that whole class of workaround.