Skip to content

[RFC] drivers/cpufreq: Add a CPU frequency scaling framework. - #19737

Closed
Fishwaldo wants to merge 3 commits into
apache:masterfrom
Fishwaldo:upstream-cpufreq
Closed

[RFC] drivers/cpufreq: Add a CPU frequency scaling framework.#19737
Fishwaldo wants to merge 3 commits into
apache:masterfrom
Fishwaldo:upstream-cpufreq

Conversation

@Fishwaldo

Copy link
Copy Markdown
Contributor

This is an RFC. The code is complete and tested on hardware, but it adds a
new subsystem with a public header, so I would rather have agreement on the
design than merge it and discover the API is wrong. Specific questions are at
the end. Happy to rework any of it.

Summary

  • The tree has carried consumers of a cpufreq framework for two years
    without the framework. drivers/thermal/thermal_cpufreq_cooling.c
    includes nuttx/cpufreq.h and speaks of policies and QoS requests;
    thermal_dummy.c defines a whole lower half and calls cpufreq_init().
    Both sit behind THERMAL_CDEV_CPUFREQ and THERMAL_DUMMY_CPUFREQ, whose
    dependency on CPUFREQ no configuration could ever satisfy.
  • So the API was not designed here. It is already fixed by that code, down
    to the order of the driver's operations and the detail that a policy can
    be cast to reach its driver. This supplies the missing half to that exact
    contract rather than inventing a new one.
  • The shape. One policy per system. A platform provides a lower half: an
    ascending frequency table and a way to move between its entries. Each
    requester (a thermal cooling device, an application holding
    /dev/cpufreq, a power manager) installs a [min, max] window. The
    resolved frequency is the highest table entry at or below the lowest
    ceiling. Speed is the default; any one requester can cap it; when windows
    do not intersect the lowest ceiling wins. The lower half only ever hears
    "go to table entry N".
  • With CPUFREQ_CHARDEV the policy is also /dev/cpufreq, with ioctls to
    read the current frequency, list the table, and install a request. Each
    open descriptor owns at most one request, released on close, including on
    task exit.
  • Suspend and resume pass through to the lower half. While suspended the
    resolver leaves the hardware alone and applies whatever changed on the way
    back.
  • Documentation is included, as
    Documentation/components/drivers/special/cpufreq.rst.

Questions for reviewers

  1. One policy per system. Linux has one per CPU or per cluster. This has
    one, globally, because that is what the existing consumers assume
    (cpufreq_policy_get() takes no argument). Fine for now, or should the
    API carry a policy handle from the start? Adding one later is a break.
  2. min is structurally inert. The resolver picks the highest entry under
    the lowest ceiling, which is already the maximum permitted, so a floor is
    either already satisfied or can only be met by violating a ceiling. It is
    accepted and stored, never read. I left it because the existing thermal
    consumer passes it and because a future governor that selects below maximum
    would make it meaningful. Keep it, keep it but reject inverted windows with
    -EINVAL, or drop it from the API?
  3. No governors. The policy is "run at the highest frequency nobody has
    capped". No ondemand, no schedutil, no idle-driven scaling. Is that the
    right default for NuttX, or should there be a governor hook now?
  4. Userspace surface. /dev/cpufreq with four ioctls. thermal exposes
    /proc/thermal for inspection; cpufreq has no procfs equivalent, so there
    is no way to see the installed requests. Worth adding?
  5. Frequency unit. The lower half chooses it. kHz is the Linux convention
    and what this port uses, but nothing enforces it, and mixing units across
    consumers of one policy would be silently wrong. Fix it as kHz in the API?
  6. driver must be the first member of struct cpufreq_policy, because
    thermal_cpufreq_cooling.c casts a policy pointer to reach the lower half.
    That is an unenforced ABI constraint I have documented rather than fixed.
    Leave it, or add an accessor and change that caller?

Impact

  • Is new feature added? Is existing feature changed? NEW, and it makes
    two existing but unbuildable features reachable.
  • Impact on user? NO unless enabled. CONFIG_CPUFREQ defaults off.
  • Impact on build? NO beyond the new directory when enabled.
  • Impact on hardware? NO directly; a platform opts in by providing a
    lower half.
  • Impact on documentation? YES, provided. New page under specialized
    drivers, plus Kconfig help.
  • Impact on security? NO. /dev/cpufreq grants no more than the ability
    to cap the CPU's speed, and a request dies with its descriptor.
  • Impact on compatibility? NO. Nothing that builds today changes.
  • Build-mode dependence? NO.

Testing

I confirm that changes are verified on local setup and works as intended:

  • Build Host: macOS 26.5.1, arm64 (Apple Silicon), xPack riscv-none-elf-gcc
    15.2.0
  • Target: real hardware. RISC-V, ESWIN EIC7700X EVB at 1.4 GHz
    (downstream board port, not yet upstream), kernel build, with a real
    lower half over the SoC's CPU PLL and a real die temperature sensor
  • Docs: sphinx-build clean, no warnings from the new page

Both existing consumers were driven, which is the point of the exercise.

First, THERMAL_DUMMY with its cpufreq half enabled. The dummy zone's
simulated temperature drives the previously dead cooling device, which
installs and updates QoS requests through this framework:

##### CMD 1: ls /dev/cpufreq
 /dev/cpufreq
##### CMD 2: cat /proc/thermal/cpu-thermal
z:cpu-thermal t:73 t:1 h:16 l:0 c:fan0 s:3|3
z:cpu-thermal t:73 t:1 h:3 l:3 c:cpufreq s:3|3
z:cpu-thermal t:73 t:2 h:2 l:0 c:cpufreq s:3|2

Then the real thing: the board's own die zone, throttling actual silicon as
it heats and releasing as it cools. c:cpufreq s:N is the cooling state.

[CPU0] cpu: measured 1400 MHz, clock tree says 1400 MHz

z:die t:39 t:2 h:8 l:0 c:cpufreq s:0|(invalid)     <- 39C, unthrottled
z:die t:40 t:2 h:8 l:0 c:cpufreq s:1|1             <- 40C, first step
z:die t:40 t:2 h:8 l:0 c:cpufreq s:8|8             <- stepped to the cap
z:die t:34 t:2 h:8 l:0 c:cpufreq s:0|0             <- cooled, released

A userspace floor held through /dev/cpufreq, which also demonstrates
question 2 above: the request is accepted and the frequency stays at the
top entry, because that is where it already was.

holding 1400 floor: 1400000 kHz
holding 1400 floor: 1400000 kHz
holding 1400 floor: 1400000 kHz

/proc/thermal showing the cpufreq cooling device stepping its state is
upstream's own two-year-old code running for the first time.

PR verification Self-Check

  • This PR introduces only one functional change.
  • I have updated all required description fields above.
  • My PR adheres to Contributing Guidelines and Documentation.
  • My PR is still work in progress (not ready for review).
  • My PR is ready for review and can be safely merged into a codebase.

Claude (claude-opus-5) assisted with authoring this framework, its code
comments, the documentation and this PR description. The commits carry
Assisted-by: tags per CONTRIBUTING.md §1.5.

The tree has carried consumers of a cpufreq framework for two years
without the framework.  thermal_cpufreq_cooling.c includes nuttx/cpufreq.h
and speaks of policies and QoS requests; thermal_dummy.c defines a whole
lower half and calls cpufreq_init; both sit behind THERMAL_CDEV_CPUFREQ
and THERMAL_DUMMY_CPUFREQ, whose dependency on CPUFREQ no configuration
could ever satisfy.  The API they were written against is therefore
already fixed, down to the order of the driver's operations and the
detail that a policy can be cast to reach its driver, and this supplies
the missing half to that exact contract rather than inventing a new one.

The shape is one policy arbitrated by windows.  A platform provides a
lower half: an ascending frequency table and a way to move between its
entries.  Each requester (a thermal cooling device, an application, a
power manager) installs a request naming the window it can live with,
and the resolved frequency is the highest table entry under the lowest
ceiling.  Speed is the default; any one requester can cap it; when
requests collide the lowest ceiling wins.  The lower half only ever
hears "go to entry N" and never learns who wanted what.

With CPUFREQ_CHARDEV the policy is also /dev/cpufreq, with ioctls to
read the current frequency, list the table, and install a request.  Each
open descriptor owns at most one request, released on close, including
on task exit.

Suspend and resume pass through to the lower half; while suspended the
resolver leaves the hardware alone and applies whatever changed on the
way back.

Verified on hardware against both existing consumers: with THERMAL_DUMMY
and its cpufreq half enabled, the dummy zone's simulated temperature
drives the previously dead cooling device, which installs and updates
QoS requests through this framework onto the dummy lower half.
/proc/thermal showing the cpufreq cooling device stepping its state is
upstream's own two-year-old code running for the first time.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Justin Hammond <justin@dynam.ac>
The file calls therr and thinfo, which live in nuttx/debug.h via debug.h,
and included neither.  It has never been compilable, since its Kconfig
option depends on a CPUFREQ symbol that did not exist, so nobody ever saw
the error.  With the cpufreq framework now present the option is
satisfiable and the missing include is the only thing between this file
and working, two years after it was merged.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Justin Hammond <justin@dynam.ac>
The cpufreq framework had no documentation.  This covers what a platform
must provide, how competing requests resolve into one frequency, the
in-kernel QoS calls, the /dev/cpufreq ioctls, and how the thermal cooling
device sits on top of it.

Two properties of the resolver are stated explicitly rather than left to
be discovered, since both are deliberate: a floor never raises the
frequency, because the resolver already picks the highest permitted table
entry; and where windows do not intersect the lowest ceiling wins.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Justin Hammond <justin@dynam.ac>
@github-actions github-actions Bot added Area: Drivers Drivers issues Size: XL The size of the change in this PR is very large. Consider breaking down the PR into smaller pieces. labels Aug 7, 2026
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown

MemBrowse Memory Report

No memory changes detected for:

# ##############################################################################

if(CONFIG_CPUFREQ)
target_sources(drivers PRIVATE cpufreq.c)

@xiaoxiang781216 xiaoxiang781216 Aug 7, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's better to integrate the well test and more functionality from:
https://github.com/open-vela/nuttx/tree/dev/drivers/devfreq
Instead rewrite from scratch by AI.
BTW, it the origin cpufreq framework work with thermal framework directly and extend to support any device frequency scaling.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Fishwaldo here is the pr: #19741

@linguini1

Copy link
Copy Markdown
Contributor

You should start this discussion on the mailing list please @Fishwaldo

@acassis

acassis commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

You should start this discussion on the mailing list please @Fishwaldo

Agree, I think both implementations could be merged in a single subsystem

@Fishwaldo

Copy link
Copy Markdown
Contributor Author

Closing this RFC.

The devfreq framework has since landed on master (#19741), and that is the
better place to build on. Rather than carry a second frequency scaling
framework, the thermal side will be adjusted to work with devfreq later.

Thanks to everyone who looked at it.

@Fishwaldo Fishwaldo closed this Aug 9, 2026
@acassis

acassis commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Closing this RFC.

The devfreq framework has since landed on master (#19741), and that is the better place to build on. Rather than carry a second frequency scaling framework, the thermal side will be adjusted to work with devfreq later.

Thanks to everyone who looked at it.

Hi @Fishwaldo thank you very much for your initial cpufreq proposal. This is sad when things like that happen, but Xiaomi has many drivers and features implemented in their open-vela/NuttX fork and they don't have time or enough developers to submit it upstream. So, if we have added your solution they will spend a lot of time and money re-validating everything and they have million of devices on street that will need to be updated as well. I'm glad that you understand it and didn't get upset.

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

Labels

Area: Drivers Drivers issues Size: XL The size of the change in this PR is very large. Consider breaking down the PR into smaller pieces.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants