U-Boot Bulk Clock Enable: A Failed Call Can Leave Earlier Clocks Enabled
U-Boot v2025.01’s clk_enable_bulk() is a forward loop,
not an all-or-nothing operation. If a later clock returns a negative
error other than -ENOSYS, the function stops and returns
that error without disabling clocks enabled earlier in the list.
A driver that retries the whole call or assumes failure restored the
initial state can therefore accumulate clock ownership or leave hardware
active. Its error path needs the actual partial-success contract, not
just the bulk API’s final return value.
Read the
implementation behind the convenience API
The clock
API header describes a bulk object previously obtained through
clk_get_bulk() and a zero-or-error result. In the active
clock implementation, the uclass
source visits entries in increasing index order.
For each entry it calls clk_enable(). A negative result
other than -ENOSYS immediately leaves the loop. The helper
has no unwind loop and returns no index identifying the completed
prefix. Earlier successful calls remain part of the caller’s state.
The special treatment of -ENOSYS also matters: that
result does not stop the bulk loop. A zero bulk result therefore means
no stopping error occurred under this policy; it is not independent
evidence that every clock’s provider performed a physical enable
operation.
This article concerns the active clock framework. A configuration
using the header’s disabled-framework stubs has another contract and
must not be mistaken for a hardware-enabling path.
Trace the state
after a middle-entry failure
Consider an illustrative list containing clocks A, B and C. A
succeeds, B returns a stopping error and C is not attempted. The bulk
result reports B’s failure, but A’s successful operation is not undone
by this helper.
Do not infer that B is physically unchanged merely because it
returned an error. Its provider may have performed partial work,
including parent-clock operations. That question belongs to the
provider’s implementation. The reliable shared-layer observation is
narrower: earlier successful entries are not automatically unwound and
later entries are skipped.

A whole-list
retry can change reference counts
With the common-clock-framework path enabled,
clk_enable() can resolve a clock’s private object and
increment an already nonzero enable_count without repeating
the provider operation. This makes a blind retry observable even if the
physical signal was already running.
Suppose A’s first successful enable acquired one reference and no
other owner changes it. Retrying the list from A can acquire another
reference before reaching B again. One later disable can then leave A’s
count nonzero. This is a source-derived example under the stated
resolved-clock conditions, not a measurement of any board.
The solution is not to force the counter or disable shared clocks
indiscriminately. Track which ownership the driver acquired and review
how its intended retry or cleanup balances that ownership. A provider’s
partial-failure recovery can require additional handling beyond the
successful-prefix ledger.
Bulk
disable is not an automatic inverse transaction
clk_disable_bulk() also walks forward and stops on a
negative error other than -ENOSYS. It is not a
reverse-order unwind of only those entries enabled by a failed call.
Calling it on the entire original list without understanding ownership
can reach entries that this attempt never enabled, while an early
disable failure can prevent later cleanup.
Derive cleanup order from the actual clock and peripheral
dependencies. Before retrying initialization, establish a known
ownership state or preserve an explicit partial state that the retry
understands.
A proposed fault fixture should fail the middle enable, record every
attempted provider call and relevant reference count, then exercise the
driver’s cleanup and retry path. Include a disable failure separately.
No clock operation or hardware test was executed here. Acceptance
requires balanced ownership through partial failure, rather than merely
a later successful bulk return.