NetBSD Problem Report #60671
From www@netbsd.org Mon Aug 31 06:11:08 2026
Return-Path: <www@netbsd.org>
Received: from mail.netbsd.org (mail.netbsd.org [199.233.217.200])
(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)
key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256
client-signature RSA-PSS (2048 bits) client-digest SHA256)
(Client CN "mail.netbsd.org", Issuer "YR2" (not verified))
by mollari.NetBSD.org (Postfix) with ESMTPS id E9BAC1A923B
for <gnats-bugs@gnats.NetBSD.org>; Mon, 31 Aug 2026 06:11:07 +0000 (UTC)
Message-Id: <20260831061106.2AD811A923C@mollari.NetBSD.org>
Date: Mon, 31 Aug 2026 06:11:06 +0000 (UTC)
From: stefano@dragas.it
Reply-To: stefano@dragas.it
To: gnats-bugs@NetBSD.org
Subject: cpufreq_dt ignores min/max voltage ranges in opp-microvolt, breaking DVFS
X-Send-Pr-Version: www-1.0
X-From4GNATS: "stefano@dragas.it via gnats" <gnats-admin@NetBSD.org>
>Number: 60671
>Category: kern
>Synopsis: cpufreq_dt ignores min/max voltage ranges in opp-microvolt, breaking DVFS
>Confidential: no
>Severity: serious
>Priority: medium
>Responsible: kern-bug-people
>State: open
>Class: sw-bug
>Submitter-Id: net
>Arrival-Date: Mon Aug 31 06:15:00 +0000 2026
>Last-Modified: Mon Sep 14 19:40:01 +0000 2026
>Originator: Stefano Marinelli
>Release: NetBSD 11.0_STABLE / netbsd-11
>Organization:
>Environment:
NetBSD opionenb 11.0_STABLE NetBSD 11.0_STABLE (GENERIC) #0: Tue Aug 25 20:15:20 UTC 2026 mkrepro@mkrepro.NetBSD.org:/usr/src/sys/arch/evbarm/compile/GENERIC evbarm
>Description:
The FDT cpufreq driver only stores the first value of the
"opp-microvolt" property when parsing operating-points-v2.
An OPP may specify three values:
opp-microvolt = <target min max>;
However, cpufreq_dt currently only stores opp_uv[0], and later requests
that exact voltage from the regulator using:
fdtbus_regulator_set_voltage(sc->sc_supply, new_uv, new_uv);
This causes CPU frequency transitions to fail when the regulator does not
provide the exact target voltage, but does provide a valid voltage within
the range specified by the OPP.
This can be reproduced on the Allwinner H3 Orange Pi boards.
For example, the available CPU frequencies are:
machdep.cpufreq.cpu0.available = 1008 816 648
On these boards the OPP table contains voltage ranges, while the GPIO
regulator provides a limited set of discrete voltages.
As a consequence, with the unmodified kernel:
# sysctl -w machdep.cpufreq.cpu0.target=648
sysctl: machdep.cpufreq.cpu0.target: Invalid argument
Interestingly, the frequency may already have changed before the
regulator operation fails:
# sysctl machdep.cpufreq.cpu0.current
machdep.cpufreq.cpu0.current = 648
Going upwards can fail before the clock transition:
# sysctl -w machdep.cpufreq.cpu0.target=816
machdep.cpufreq.cpu0.target: 648 -> 816
# sysctl -w machdep.cpufreq.cpu0.target=1008
sysctl: machdep.cpufreq.cpu0.target: Invalid argument
# sysctl machdep.cpufreq.cpu0.current
machdep.cpufreq.cpu0.current = 816
This also breaks estd. estd correctly selects a different CPU frequency,
but when the sysctl operation returns an error it exits with:
estd: Cannot set CPU frequency (maybe you aren't root?)
The problem appears to be that cpufreq_dt loses the min/max voltage
information supplied by operating-points-v2.
>How-To-Repeat:
On an Orange Pi One running NetBSD 11_STABLE:
# sysctl machdep.cpufreq
machdep.cpufreq.cpu0.available = 1008 816 648
Stop estd if it is running (it will crash anyway), then try:
# sysctl -w machdep.cpufreq.cpu0.target=816
# sysctl -w machdep.cpufreq.cpu0.target=648
# sysctl -w machdep.cpufreq.cpu0.target=816
# sysctl -w machdep.cpufreq.cpu0.target=1008
Some transitions return EINVAL.
Running estd and generating CPU load also reproduces the problem, as
estd exits when one of these transitions returns an error.
>Fix:
Modify cpufreq_dt so that it preserves the target, minimum and maximum
values from:
opp-microvolt = <target min max>;
and pass the valid voltage range to fdtbus_regulator_set_voltage()
instead of requiring the exact target voltage.
For legacy OPP tables containing only one voltage, min and max remain
equal to the target voltage, preserving the existing behaviour.
With this change applied to netbsd-11, I rebuilt a GENERIC
evbarm/earmv7hf kernel and tested it on an Orange Pi One.
All transitions now complete successfully:
1008 -> 816
816 -> 648
648 -> 816
816 -> 1008
All sysctl operations return success, and estd can dynamically scale
between 648, 816 and 1008 MHz without terminating.
Patch follows:
diff --git a/sys/dev/fdt/cpufreq_dt.c b/sys/dev/fdt/cpufreq_dt.c
index d80eb90a5..741db01da 100644
--- a/sys/dev/fdt/cpufreq_dt.c
+++ b/sys/dev/fdt/cpufreq_dt.c
@@ -55,6 +55,8 @@ static kmutex_t cpufreq_dt_tables_lock;
struct cpufreq_dt_opp {
u_int freq_khz;
u_int voltage_uv;
+ u_int voltage_min_uv;
+ u_int voltage_max_uv;
u_int latency_ns;
};
@@ -94,6 +96,7 @@ cpufreq_dt_set_rate(struct cpufreq_dt_softc *sc, u_int freq_khz)
{
struct cpufreq_dt_opp *opp = NULL;
u_int old_rate, new_rate, old_uv, new_uv;
+ u_int new_min_uv, new_max_uv;
uint64_t xc;
int error;
ssize_t n;
@@ -109,6 +112,8 @@ cpufreq_dt_set_rate(struct cpufreq_dt_softc *sc, u_int freq_khz)
old_rate = clk_get_rate(sc->sc_clk);
new_rate = freq_khz * 1000;
new_uv = opp->voltage_uv;
+ new_min_uv = opp->voltage_min_uv;
+ new_max_uv = opp->voltage_max_uv;
if (old_rate == new_rate)
return 0;
@@ -120,7 +125,7 @@ cpufreq_dt_set_rate(struct cpufreq_dt_softc *sc, u_int freq_khz)
if (new_uv > old_uv) {
error = fdtbus_regulator_set_voltage(sc->sc_supply,
- new_uv, new_uv);
+ new_min_uv, new_max_uv);
if (error != 0)
return error;
}
@@ -137,7 +142,7 @@ cpufreq_dt_set_rate(struct cpufreq_dt_softc *sc, u_int freq_khz)
if (sc->sc_supply != NULL) {
if (new_uv < old_uv) {
error = fdtbus_regulator_set_voltage(sc->sc_supply,
- new_uv, new_uv);
+ new_min_uv, new_max_uv);
if (error != 0)
return error;
}
@@ -351,6 +356,10 @@ cpufreq_dt_parse_opp(struct cpufreq_dt_softc *sc)
for (i = 0; i < sc->sc_nopp; i++, opp += 2) {
sc->sc_opp[i].freq_khz = be32toh(opp[0]);
sc->sc_opp[i].voltage_uv = be32toh(opp[1]);
+ sc->sc_opp[i].voltage_min_uv =
+ sc->sc_opp[i].voltage_uv;
+ sc->sc_opp[i].voltage_max_uv =
+ sc->sc_opp[i].voltage_uv;
}
return 0;
@@ -444,11 +453,22 @@ cpufreq_dt_parse_opp_v2(struct cpufreq_dt_softc *sc)
if (of_getprop_uint64(opp_node, "opp-hz", &opp_hz) != 0)
return EINVAL;
opp_uv = fdtbus_get_prop(opp_node, "opp-microvolt", &len);
- if (opp_uv == NULL || len < 1)
+ if (opp_uv == NULL || len < sizeof(*opp_uv))
return EINVAL;
/* Table is in reverse order */
sc->sc_opp[index].freq_khz = (u_int)(opp_hz / 1000);
sc->sc_opp[index].voltage_uv = be32toh(opp_uv[0]);
+ if (len >= 3 * sizeof(*opp_uv)) {
+ sc->sc_opp[index].voltage_min_uv =
+ be32toh(opp_uv[1]);
+ sc->sc_opp[index].voltage_max_uv =
+ be32toh(opp_uv[2]);
+ } else {
+ sc->sc_opp[index].voltage_min_uv =
+ sc->sc_opp[index].voltage_uv;
+ sc->sc_opp[index].voltage_max_uv =
+ sc->sc_opp[index].voltage_uv;
+ }
of_getprop_uint32(opp_node, "clock-latency-ns", &sc->sc_opp[index].latency_ns);
--index;
}
Tested-by: Stefano Marinelli <stefano@dragas.it>
>Audit-Trail:
From: Yuri Honegger <yuri.honegger@icloud.com>
To: "gnats-bugs@netbsd.org" <gnats-bugs@NetBSD.org>
Cc:
Subject: Re: kern/60671
Date: Mon, 14 Sep 2026 21:36:04 +0200
About your patch: It looks correct, but it doesn=E2=80=99t modify the =
aprint calls. If we only communicate the range to the regulator, we =
should also update the printfs to report a range instead of a specific =
value to avoid confusion. And at that point, voltage_uv is basically =
unused and can be removed.
However, I think a better approach would be to change the fdt_regulator =
framework so that it accepts preferred_uv, min_uv and max_uv instead of =
only min_uv and max_uv.=20
Looking through some regulator implementations, most always use min_uv =
or the average as a substitute for a preferred_uv.
For example, tps65217 uses uv_min and does some rounding that can lead =
to values outside uv_min. Having a preferred_uv doesn=E2=80=99t solve =
the rounding issue, but it can add some margin.
In terms of scope, it looks doable (around 15 callsites and =
implementations).
I also thinks it is slightly less risky because it doesn=E2=80=99t =
change the value that reaches the regulator, so all regulators should =
continue to behave exactly as they do right now on boards unaffected by =
your issue.
(Contact us)
$NetBSD: query-full-pr,v 1.51 2026/08/10 02:28:17 riastradh Exp $
$NetBSD: gnats_config.sh,v 1.10 2026/05/13 22:00:09 riastradh Exp $
Copyright © 1994-2026
The NetBSD Foundation, Inc. ALL RIGHTS RESERVED.