Skip to content

drivers/devfreq: introduce device frequency scaling framework - #19741

Merged
acassis merged 15 commits into
apache:masterfrom
zzby0:devfreq
Aug 8, 2026
Merged

drivers/devfreq: introduce device frequency scaling framework#19741
acassis merged 15 commits into
apache:masterfrom
zzby0:devfreq

Conversation

@zzby0

@zzby0 zzby0 commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR introduces a device frequency scaling (devfreq) framework for
NuttX, providing generic dynamic voltage and frequency scaling (DVFS)
infrastructure for any clock-scalable device (CPU, GPU, memory bus, DSP, etc.).

Unlike a single system-wide CPU policy, devfreq manages any number of
independent devices, each registered by name with its own frequency table and
governor.

Architecture — the framework separates policy from mechanism:

  • A lower half (struct devfreq_driver_s) is provided by the platform: an
    ascending frequency table plus target_index/get_frequency callbacks. The
    lower half is only ever told "go to table entry N".
  • Two independent forces resolve the frequency:
    • QoS requests — each requester installs a [min, max] window; the
      framework aggregates all windows (highest min, lowest max) into a single
      clamp. When windows do not intersect, the driver's conflict_policy
      (PREFER_HIGH / PREFER_LOW) decides the winner.
    • Governorsperformance (top of window), powersave (bottom of
      window), and ondemand (load-driven scaling).
  • The resolved frequency is snapped to a real table entry and applied through
    the lower half.

Components:

  • Core framework (drivers/devfreq/devfreq.c) — registration, table
    validation, QoS resolution, governor dispatch, suspend/resume, change
    notifier chain.
  • Governors: performance, powersave, ondemand.
  • QoS constraint engine (devfreq_qos.c) built on a priority-sorted list
    (include/nuttx/plist.h).
  • procfs interface (/proc/devfreq/<name>) — read frequency table / current
    frequency / governor / QoS list; write to install a frequency constraint
    from user space.
  • Public API (include/nuttx/devfreq.h) and documentation
    (Documentation/.../special/devfreq.rst).

Impact

  • New feature: adds the devfreq framework. Entirely opt-in — gated behind
    CONFIG_DEVFREQ (default n); no effect on existing configurations.
  • Build: new drivers/devfreq/ subsystem wired into drivers/Kconfig,
    drivers/Makefile, and CMakeLists.txt.
  • User space: when CONFIG_DEVFREQ_PROCFS is set, exposes
    /proc/devfreq/<name> (read status/table, write <min> <max> in kHz to
    constrain, 0 0 to clear). Requires CONFIG_FS_PROCFS; auto-selects
    CONFIG_FS_PROCFS_REGISTER.
  • New config options: CONFIG_DEVFREQ, CONFIG_DEVFREQ_PROCFS,
    CONFIG_DEVFREQ_PROCFS_QOS, CONFIG_DEVFREQ_GOV_ONDEMAND,
    CONFIG_DEVFREQ_SAMPLE_RATE, CONFIG_DEVFREQ_LOAD_THRESHOLD.
  • New header: include/nuttx/plist.h (priority-sorted list, header-only),
    a dependency of the QoS engine.
  • Hardware: no impact unless a platform registers a lower-half driver.
  • Documentation: new page under
    Documentation/components/drivers/special/.
  • Compatibility: no changes to existing APIs; no regressions expected.

Testing

Host & Targets

Item Detail
Host OS Linux x86_64 (Ubuntu)
Toolchains host gcc 13.4.0 (sim), arm-none-eabi-gcc 10.3.1 (arm)
NuttX / apps apache/master
Real hardware BES2800bp, ARMv8-M Cortex-M55

tools/checkpatch.sh passes on all commits.

Build Verification

Arch Config devfreq options Result
sim (x86_64) sim:nsh DEVFREQ + PROCFS + PROCFS_QOS LD nuttx OK, 0 warnings
arm (Cortex-A7) qemu-armv7a:nsh DEVFREQ + PROCFS + PROCFS_QOS LD nuttx OK, 0 warnings

Runtime Functional Test — Simulator

A temporary dummy lower-half driver (table 200000 400000 600000 800000 kHz)
was registered as /proc/devfreq/test to exercise the framework end to end
(scaffolding removed after testing).

performance governor (selects the top of the resolved window):

nsh> cat /proc/devfreq/test
 governor:    performance
 cur_freq:    800000
 freq_table:  200000 400000 600000 800000

nsh> echo '0 400000'      > /proc/devfreq/test   # cap: max=400000
test_devfreq: target_index=1 freq=400000         # -> cur_freq 400000

nsh> echo '600000 800000' > /proc/devfreq/test   # floor honoured
test_devfreq: target_index=3 freq=800000         # -> cur_freq 800000

nsh> echo '200000 600000' > /proc/devfreq/test   # window [200000,600000]
test_devfreq: target_index=2 freq=600000         # -> top-in-window 600000

nsh> echo '0 0'           > /proc/devfreq/test   # clear QoS
test_devfreq: target_index=3 freq=800000         # -> restores 800000

nsh> echo '800000 200000' > /proc/devfreq/test   # invalid min>max
                                                 # -> write returns -EINVAL, no change

powersave governor (selects the bottom of the resolved window):

nsh> cat /proc/devfreq/test
test_devfreq: target_index=0 freq=200000         # default -> lowest 200000
 governor:    powersave
 cur_freq:    200000

nsh> echo '400000 800000' > /proc/devfreq/test   # window [400000,800000]
test_devfreq: target_index=1 freq=400000         # -> bottom-in-window 400000

Runtime Functional Test — Real Hardware (BES2800bp, Cortex-M55)

The framework was also validated on real silicon driving the CPU clock through
a platform CPU devfreq lower half (/proc/devfreq/cpu, table
32 26000 52000 104000 208000 320000 kHz, performance governor). The lower-half
CPU driver is board/downstream code and is not part of this PR.

Scenario 1 — Concurrent QoS requests with conflicting windows.
Two requests are installed: one caps at 208000, another pins
[320000, 320000]. The windows do not intersect (aggregate min 320000 >
aggregate max 208000), so the driver's conflict_policy resolves it — here
the higher frequency wins and the CPU runs at 320 MHz:

ap> cat /proc/devfreq/cpu
 devfreq:     cpu
 governor:    performance
 cur_freq:    320000
 suspended:   False
 freq_table:  32 26000 52000 104000 208000 320000
 qos_list(min, max, backtrace):
 0, 208000,
 320000, 320000,

Scenario 2 — Updating a QoS request re-resolves and changes the real clock.
The [320000, 320000] request is updated to [208000, 208000] via procfs. The
windows now intersect at 208000, and the CPU frequency drops to 208 MHz on
hardware:

ap> echo "208000 208000" > /proc/devfreq/cpu

ap> cat /proc/devfreq/cpu
 devfreq:     cpu
 governor:    performance
 cur_freq:    208000
 suspended:   False
 freq_table:  32 26000 52000 104000 208000 320000
 qos_list(min, max, backtrace):
 0, 208000,
 208000, 208000,

Coverage Summary

Verified across simulator and hardware: device registration, procfs read/write,
QoS [min, max] aggregation (upper cap and lower floor), multi-requester
conflict resolution via conflict_policy, governor selection (performance =
top-of-window, powersave = bottom-of-window), QoS clear, invalid-input
rejection, and lower-half target_index / get_frequency invocation with a
real frequency change on Cortex-M55.

Documentation

Documentation/components/drivers/special/devfreq.rst renders correctly via
make html.

Notes

All temporary test scaffolding (the sim dummy driver) was reverted after
testing and is not part of this PR.

@github-actions github-actions Bot added Area: Documentation Improvements or additions to documentation Size: XL The size of the change in this PR is very large. Consider breaking down the PR into smaller pieces. labels Aug 8, 2026
@zzby0 zzby0 changed the title Devfreq drivers/devfreq: introduce device frequency scaling framework Aug 8, 2026
zzby0 and others added 15 commits August 8, 2026 15:26
This commit introduces a devfreq framework to manage device frequency
scaling. The framework includes the following features:
1.devfreq governor
  - provide governor ops, including init, start, stop, exit
  - default governor, performance & powersave
  - customized governor, device can provide governor when register
2.runtime register and unregister
  - device can runtime register & unregister, search by name
3.suspend and resume
  - suspend and resume frequency scaling
4.notify
  - register & unregister notifier callback, notify frequency changes
5.qos support
  - simplified QoS, manage multiple freq range request
  - including init, add/remove/update request, get value

Signed-off-by: guanyi <guanyi@xiaomi.com>
> ls /proc/devfreq
 /proc/devfreq:
 test_devfreq
> cat /proc/devfreq/test_devfreq
 devfreq:     test_devfreq
 governor:    test_devfreq_governor
 cur_freq:    500
 suspended:   False
 freq_table:  100 300 500 700 900
 qos_list(min, max, backtrace):
 195, 829, 0x4007c26 0x40a0e0e 0x405c706 0x4011186 0x4010dca 0x42777cc 0x4062f7e 0x409da6a

Signed-off-by: guanyi <guanyi@xiaomi.com>
It's better not to use global governor, as modifying one device will cause all devices' governor to be modified.

Signed-off-by: guanyi3 <guanyi3@xiaomi.com>
we do not hope the governor and driver in devfreq to be modified.

Signed-off-by: guanyi3 <guanyi3@xiaomi.com>
Add devfreq ondemand governor that scales device frequency based on CPU load. When CPU load exceeds the configured threshold, frequency is set to maximum; otherwise it is scaled proportionally.

Signed-off-by: guanyi3 <guanyi3@xiaomi.com>
devfreq_qos_add_request -> devfreq_refresh_limit -> devfreq_limit_governor -> devfreq_gov_ondemand_limit, here use governor_data but it's 0x0

Signed-off-by: guanyi3 <guanyi3@xiaomi.com>
we may call devfreq_find_by_name() in pm_callback, and shouldn't call nxmutex_lock() in idle_loop, so replace mutex to spinlock.

Signed-off-by: guanyi3 <guanyi3@xiaomi.com>
Add the ability to set frequency constraints via procfs write.
Supported formats:
  echo <min>,<max> > /proc/devfreq/<name>  - set frequency range
  echo 0,0 > /proc/devfreq/<name>          - remove constraint

The QoS request is bound to the devfreq device lifetime so that
shell commands like echo (which open, write, close immediately)
work correctly. Leading whitespace in the write buffer is skipped
to handle extra writes from nsh echo (e.g. trailing newline).

Also add write permissions in devfreq_stat() and a procfs_qos
field in devfreq_s guarded by CONFIG_DEVFREQ_PROCFS.

Signed-off-by: guanyi3 <guanyi3@xiaomi.com>
(cherry picked from commit 70ae195c84f35a4d0b85fcc14187989b60fc0280)
When multiple QoS requests have no overlapping frequency range (min > max), the previous behavior always clamped to the lower frequency. Add a conflict_policy field to devfreq_driver_s so callers can choose between DEVFREQ_CONFLICT_PREFER_HIGH (default, choose higher freq) and DEVFREQ_CONFLICT_PREFER_LOW (choose lower freq) at registration.

Signed-off-by: guanyi3 <guanyi3@xiaomi.com>
When devfreq_gov_ondemand_stop() is called from idle task context,
work_cancel() is used instead of work_cancel_sync(), which does not
wait for the currently running worker to complete. If
devfreq_gov_ondemand_exit() then frees governor_data, the worker
may still be accessing it, causing a use-after-free crash.

Fix this by:
- Nullifying dev->governor_data under dev->lock in exit before freeing.
- Moving the governor_data read inside dev->lock in the worker and
  adding a NULL check to bail out early if data has been freed.

Signed-off-by: guanyi3 <guanyi3@xiaomi.com>
…iver_target

The cached devfreq->cur may become stale when the hardware frequency is
changed externally (e.g. by another core or governor). This causes
driver_target to incorrectly skip frequency transitions when the target
matches the cached value but differs from the actual hardware frequency.

Use driver->get_frequency() to read the real hardware frequency for the
unchanged check, and sync devfreq->cur on match to keep the cache correct.

Signed-off-by: guanyi3 <guanyi3@xiaomi.com>
QOS_REQ_MIN should return the highest value among all min requests
(most restrictive lower bound), but plist_first returns the lowest.
QOS_REQ_MAX should return the lowest value among all max requests
(most restrictive upper bound), but plist_last returns the highest.

This caused qos constraints to be ineffective. For example, two
requests (32, 208000) and (104000, 104000) would merge to (32, 208000)
instead of the correct (104000, 104000).

Fix by using plist_last for QOS_REQ_MIN and plist_first for QOS_REQ_MAX.

Signed-off-by: guanyi3 <guanyi3@xiaomi.com>
When CONFIG_LIBC_BACKTRACE_DEPTH is not set or <= 0, backtrace_get()
is a macro that always sets depth to 0, making the for-loop body
unreachable (Coverity CID 8405332 DEADCODE).

Wrap backtrace_get() call, the loop, and related variable declarations
with #if CONFIG_LIBC_BACKTRACE_DEPTH > 0 to eliminate the dead code
and avoid unused variable warnings.

Signed-off-by: guanyi3 <guanyi3@xiaomi.com>
Add Kconfig, Make.defs, and CMakeLists.txt entries for the ondemand governor so it can be enabled via CONFIG_DEVFREQ_GOV_ONDEMAND.

Signed-off-by: guanyi3 <guanyi3@xiaomi.com>
Document the device frequency scaling framework: the QoS/governor arbitration model, the lower-half driver interface, built-in governors, in-kernel QoS requests, change notifications, procfs, and suspend/resume.

Signed-off-by: guanyi3 <guanyi3@xiaomi.com>
@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown

MemBrowse Memory Report

hifive1-revb

@acassis acassis left a comment

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.

@zzby0 please analyze merging with: #19737 I suggest using cpufreq name because it is used on Linux and other OSes.

@zzby0

zzby0 commented Aug 8, 2026

Copy link
Copy Markdown
Contributor Author

@zzby0 please analyze merging with: #19737 I suggest using cpufreq name because it is used on Linux and other OSes.

Thanks for the comment !
Linux has both cpufreq and devfreq, but due to legacy issues, devfreq is incompatible with cpufreq. However, Nuttx doesn't have these legacy issues; the CPU can be used as a device and is compatible with devfreq, meaning we only need to maintain one framework. In this case, devfreq may be more suitable. I'd appreciate your suggestions.

@acassis

acassis commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

@zzby0 thank you for explanation. Please include a board config example for some popular board like esp32-devkit or stm32f4discovery

@acassis
acassis merged commit e7ef45d into apache:master Aug 8, 2026
55 checks passed

cpuload = devfreq_gov_ondemand_cpuload();
nxmutex_lock(&dev->lock);
data = dev->governor_data;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@zzby0 @xiaoxiang781216 hi, this file doesn't compile. Is there any commit missing in this PR?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Okay, I'll check it right away.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks so much for pointing out the problem 👍
I will fix in 19788 😄

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

Labels

Area: Documentation Improvements or additions to documentation 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