Skip to content

VRTD clock related safety improvements - #216

Open
quetric wants to merge 5 commits into
devfrom
207-bug-in-service-shell-crashing-the-node
Open

VRTD clock related safety improvements#216
quetric wants to merge 5 commits into
devfrom
207-bug-in-service-shell-crashing-the-node

Conversation

@quetric

@quetric quetric commented Sep 5, 2026

Copy link
Copy Markdown
Collaborator

No description provided.

Three defects combined to let a design write leave the clock driver
addressing a device that no longer exists, and then to report the
resulting garbage as a successfully programmed frequency.

device_refresh_pf2_after_design_write() closed and reopened the control
device and every BAR file after the PF2 remove/rescan, but left the
clock driver untouched. The driver holds a non-owning pointer to the
control device and its own mapping of the BAR window carrying the clock
wizard registers, both established against the pre-PDI PF2. A clock
operation issued after a design write therefore drove MMIO through a
stale mapping. Tear the driver down before the handle it borrows and
recreate it once the fresh PF2 has been reopened, matching the order
device_destroy() already uses.

The register decode paths clamped a zero multiplier, a zero input
divider and a zero output divider to 1. A register window that reads
back as zero consequently produced f_out = f_VCO rather than an error,
which is how a request for 200 MHz was reported as 4 GHz. Return zero
from the decode helpers and reject it at every call site.

clock_driver_try_set_rate_hz() logged a warning and returned success
when the rate read back disagreed with the request. Compare the reading
against the configuration that was just written instead: a disagreement
there means the register window did not accept the programming, so the
value does not describe a frequency the hardware is producing. Fail with
EIO rather than returning it as an achieved rate.

Fixes #207
clock_driver_wait_for_lock() read the lock bit from offset 0x33C, which
is not the status register. 0x33C is the second word of the CLKOUT0 leaf
register pair, holding the high-time count in both of its low bytes, so
the predicate tested bit 0 of the output divider's high-time count
rather than PLL lock. The status register is at offset 0x04 and reports
lock in bit 0, as the upstream Linux clocking wizard driver does.

The consequence was that candidate selection rejected any (M, D, O)
tuple whose high-time count was even, after stalling for the full
200 ms lock timeout. For a 365 MHz request this discarded the
best-ranked candidate and settled on one producing a different VCO
frequency; for requests where every candidate has an even high-time
count it failed outright. Requests that happened to work, such as
200 MHz, did so only because the first candidate had an odd count.

Read the status register instead, both when waiting for lock and when
dumping wizard state for diagnostics. The state dump also carried its
own copy of the leaf offset computation that still had the off-by-three
error corrected in #210; call the shared helper.

Fixes #206
The clock driver wrote the M, D and O registers and then trusted
whatever it read back. When the register window is not reachable the
writes are discarded and the reads return either zeros or all ones,
neither of which is distinguishable from a configuration by inspection
alone. Confirm each of the six divider registers by reading it back
immediately after the write: they are plain read/write storage, so a
mismatch identifies a window that did not take the access. Report that
as a device fault that aborts the request rather than as a lock timeout
that causes the remaining candidates to be tried in turn.

Treat a register reading as all ones as a failed access in the query
path as well. None of the registers the driver reads has all 32 bits
defined, so the pattern is a completion that did not reach the device.

Log a failed refresh after a design write. The completion path reported
"Design write completed successfully" whenever the transfer itself had
succeeded, including when the subsequent PF2 remove, rescan and reopen
had failed and left the device without usable handles.

Remove the BAR2 fallback in clock_driver_init(). Its stated premise,
that the compute platform places the clock wizards on BAR2, does not
hold: both shells map clk_wizard_slash to BAR4 + 0x00000000 and
clk_wizard_service to BAR4 + 0x00010000, as documented in
docs/explanation/bar-address-map.rst. The fallback could therefore
never select the right window, and on the service shell BAR2 + 0 is the
eth_0 control interface, so it would have directed clock writes at an
unrelated peripheral. There is one BAR to open, and failing to open it
is a failure to construct the driver.

Fixes #207
vrtd tracks the loaded shell entirely in software. device->current_shell is
set from the boot partition after a reset and from the SET_SHELL_STATE
request, and is never confirmed against the device. Any divergence between
that belief and the design actually loaded goes undetected, and the daemon
continues to drive register windows on the assumption that the wrong shell
provides them.

The static shell already publishes its own identity: the build-ID AXI GPIO
at BAR4 + 0x20000 carries the source commit hash and, in bit 28 of the high
word, the shell variant. Each shell's create_project.tcl forces that bit, so
it is valid even for bitstreams built without SLASH_BUILD_ID_HI set. smi
already reads the register to report the build ID; this adds the equivalent
reader to vrtd together with a comparison against the expected shell.

The comparison is applied at the two points where the expectation is formed
or must still hold:

  - After a reset, before recording the shell derived from the boot
    partition. The partition states the intended shell; a device reporting a
    different one did not boot what was asked of it.

  - After the PF2 remove/rescan that follows a design write. Partial
    reconfiguration targets the user region and cannot change the shell, so
    a change in the reported variant means the reopened BAR window is not
    addressing the static shell vrtd assumes.

A high word of all ones, the value an MMIO read returns when it does not
reach the device, and any word with the reserved bits set are rejected as
unreadable rather than decoded. The check is skipped when no shell has been
claimed, since there is then nothing to contradict.
The clock wizard's reconfiguration register file resets to zero and only
reflects what software has written into it. The frequency a design comes
up with is fixed at synthesis and cannot be read back through it, so on a
board where nothing has called set-rate the whole M/D/O range reads zero.

Clamping a zero multiplier, divider or output divider to 1 turned that
state into an arithmetic identity: f_out = f_in * 1 / 1 / 1. Every read of
an unprogrammed wizard returned a confident 100 MHz that was really just
the reference clock leaking through the clamps, and `clockwiz --get` had
never once reported an actual measured rate.

Reading a configuration now yields three outcomes rather than two. OK
carries a rate. UNCONFIGURED means the registers are cleared and nothing
has been programmed; it is an ordinary state, not a failure, and surfaces
as a rate of 0 with a successful return. FAULT is reserved for a register
window answering 0xFFFFFFFF, which is a window that is not answering at
all, and still fails with EIO.

v80-smi prints "unconfigured" for a rate of 0 rather than a bare number,
since 0 Hz is not a realizable rate and printing one would be inventing it.

Verified on a V80: both wizard windows read zero across 0x330-0x39C with
their lock bits set, and both regions now report unconfigured where the
previous build reported 100000000.
@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown

Coverage

component covered total vs dev
linker 1540 3863 39.9% no change
vrt 10889 14340 75.9% no change
vrtd 1617 4611 35.1% +1.6 pts

Lines covered at 3404c2f, compared with the most recent successful dev run. Full HTML reports are attached to the test runs as artifacts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant