From 8d6c0a81d500d4a18c7846da553d01ededdd66a3 Mon Sep 17 00:00:00 2001 From: Justin Hammond Date: Thu, 6 Aug 2026 20:31:43 +0800 Subject: [PATCH 1/3] drivers/cpufreq: Add a CPU frequency scaling framework. 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 --- drivers/Kconfig | 1 + drivers/Makefile | 1 + drivers/cpufreq/CMakeLists.txt | 25 ++ drivers/cpufreq/Kconfig | 27 ++ drivers/cpufreq/Make.defs | 30 ++ drivers/cpufreq/cpufreq.c | 533 +++++++++++++++++++++++++++++++++ include/nuttx/cpufreq.h | 244 +++++++++++++++ include/nuttx/fs/ioctl.h | 6 + 8 files changed, 867 insertions(+) create mode 100644 drivers/cpufreq/CMakeLists.txt create mode 100644 drivers/cpufreq/Kconfig create mode 100644 drivers/cpufreq/Make.defs create mode 100644 drivers/cpufreq/cpufreq.c create mode 100644 include/nuttx/cpufreq.h diff --git a/drivers/Kconfig b/drivers/Kconfig index 20e77b4a97165..5671b17d2dc20 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -41,6 +41,7 @@ source "drivers/power/Kconfig" source "drivers/regmap/Kconfig" source "drivers/rpmsg/Kconfig" source "drivers/rptun/Kconfig" +source "drivers/cpufreq/Kconfig" source "drivers/sensors/Kconfig" source "drivers/serial/Kconfig" source "drivers/thermal/Kconfig" diff --git a/drivers/Makefile b/drivers/Makefile index dd39ff4d7f169..b79b235210e85 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -63,6 +63,7 @@ include rpmsg/Make.defs include rptun/Make.defs include sensors/Make.defs include serial/Make.defs +include cpufreq/Make.defs include spi/Make.defs include syslog/Make.defs include thermal/Make.defs diff --git a/drivers/cpufreq/CMakeLists.txt b/drivers/cpufreq/CMakeLists.txt new file mode 100644 index 0000000000000..20b38e4c7ee9a --- /dev/null +++ b/drivers/cpufreq/CMakeLists.txt @@ -0,0 +1,25 @@ +# ############################################################################## +# drivers/cpufreq/CMakeLists.txt +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed to the Apache Software Foundation (ASF) under one or more contributor +# license agreements. See the NOTICE file distributed with this work for +# additional information regarding copyright ownership. The ASF licenses this +# file to you under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. +# +# ############################################################################## + +if(CONFIG_CPUFREQ) + target_sources(drivers PRIVATE cpufreq.c) +endif() diff --git a/drivers/cpufreq/Kconfig b/drivers/cpufreq/Kconfig new file mode 100644 index 0000000000000..85a7b5a01f7a7 --- /dev/null +++ b/drivers/cpufreq/Kconfig @@ -0,0 +1,27 @@ +# +# For a description of the syntax of this configuration file, +# see the file kconfig-language.txt in the NuttX tools repository. +# + +menuconfig CPUFREQ + bool "CPU frequency scaling" + default n + ---help--- + A single-policy CPU frequency framework. A platform provides a + lower half (a frequency table and a way to move between its + entries). Each requester (a thermal cooling device, an + application, a power manager) installs a [min, max] request, + and the resolved frequency is the highest table entry under + the lowest ceiling. + +if CPUFREQ + +config CPUFREQ_CHARDEV + bool "/dev/cpufreq character device" + default y + ---help--- + Expose the policy as /dev/cpufreq. Each open descriptor owns + at most one frequency request, installed by ioctl and released + on close, including on task exit. + +endif # CPUFREQ diff --git a/drivers/cpufreq/Make.defs b/drivers/cpufreq/Make.defs new file mode 100644 index 0000000000000..8e2af3cf95c47 --- /dev/null +++ b/drivers/cpufreq/Make.defs @@ -0,0 +1,30 @@ +############################################################################ +# drivers/cpufreq/Make.defs +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +############################################################################ + +ifeq ($(CONFIG_CPUFREQ),y) + +CSRCS += cpufreq.c + +DEPPATH += --dep-path cpufreq +VPATH += cpufreq + +endif diff --git a/drivers/cpufreq/cpufreq.c b/drivers/cpufreq/cpufreq.c new file mode 100644 index 0000000000000..b27af2a289120 --- /dev/null +++ b/drivers/cpufreq/cpufreq.c @@ -0,0 +1,533 @@ +/**************************************************************************** + * drivers/cpufreq/cpufreq.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/* CPU frequency scaling: one policy, arbitrated by windows. + * + * Each requester (a thermal cooling device, a holder of /dev/cpufreq, a + * power manager) installs a [min, max] window. The resolved frequency is + * the highest table entry in the intersection of every window, so speed is + * the default and any one requester can cap it. When the windows do not + * intersect the lowest maximum wins. + * + * The lower half only ever hears "go to table entry N". + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +#ifdef CONFIG_CPUFREQ_CHARDEV +static int cpufreq_open(FAR struct file *filep); +static int cpufreq_close(FAR struct file *filep); +static int cpufreq_ioctl(FAR struct file *filep, int cmd, + unsigned long arg); +#endif + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static FAR struct cpufreq_policy *g_cpufreq_policy; + +#ifdef CONFIG_CPUFREQ_CHARDEV +static const struct file_operations g_cpufreq_fops = +{ + cpufreq_open, /* open */ + cpufreq_close, /* close */ + NULL, /* read */ + NULL, /* write */ + NULL, /* seek */ + cpufreq_ioctl, /* ioctl */ +}; +#endif + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: cpufreq_resolve + * + * Description: + * Recompute the target from every installed request and apply it if it + * moved. Called with the policy lock held. + * + ****************************************************************************/ + +static int cpufreq_resolve(FAR struct cpufreq_policy *policy) +{ + FAR struct cpufreq_qos *qos; + FAR dq_entry_t *entry; + unsigned int hi = ~0u; + unsigned int best; + unsigned int i; + int ret = OK; + + /* Only the ceilings matter to the pick: the table ascends, so the + * highest entry under the lowest ceiling honours any satisfiable floor + * automatically, and a floor that collides with a ceiling loses. + */ + + for (entry = dq_peek(&policy->requests); entry != NULL; + entry = dq_next(entry)) + { + qos = container_of(entry, struct cpufreq_qos, node); + + if (qos->max != CPUFREQ_NO_LIMIT && qos->max < hi) + { + hi = qos->max; + } + } + + best = 0; + for (i = 0; i < policy->nentries; i++) + { + if (policy->table[i].frequency <= hi) + { + best = i; + } + } + + if (best != policy->current && !policy->suspended) + { + ret = policy->driver->target_index(policy, best); + if (ret >= 0) + { + policy->current = best; + } + } + + return ret; +} + +/**************************************************************************** + * Name: File operations for /dev/cpufreq + * + * Description: + * Each open descriptor owns at most one request, installed by ioctl + * and withdrawn on clear or close, so a request is released when its + * descriptor closes, including on task exit. + * + ****************************************************************************/ + +#ifdef CONFIG_CPUFREQ_CHARDEV + +static int cpufreq_open(FAR struct file *filep) +{ + filep->f_priv = NULL; + return OK; +} + +static int cpufreq_close(FAR struct file *filep) +{ + FAR struct cpufreq_qos *qos = filep->f_priv; + + if (qos != NULL) + { + cpufreq_qos_remove_request(qos); + filep->f_priv = NULL; + } + + return OK; +} + +static int cpufreq_ioctl(FAR struct file *filep, int cmd, unsigned long arg) +{ + FAR struct cpufreq_policy *policy = g_cpufreq_policy; + FAR struct cpufreq_qos *qos = filep->f_priv; + int ret = OK; + + if (policy == NULL) + { + return -ENODEV; + } + + switch (cmd) + { + case CPUFREQIOC_GET_FREQUENCY: + { + FAR unsigned int *freq = (FAR unsigned int *)(uintptr_t)arg; + + if (freq == NULL) + { + return -EINVAL; + } + + if (policy->driver->get_frequency != NULL) + { + ret = policy->driver->get_frequency(policy); + if (ret < 0) + { + return ret; + } + + *freq = (unsigned int)ret; + ret = OK; + } + else + { + *freq = policy->table[policy->current].frequency; + } + } + break; + + case CPUFREQIOC_SET_REQUEST: + { + FAR const struct cpufreq_request_s *req = + (FAR const struct cpufreq_request_s *)(uintptr_t)arg; + + if (req == NULL) + { + return -EINVAL; + } + + if (qos != NULL) + { + ret = cpufreq_qos_update_request(qos, req->min, req->max); + } + else + { + qos = cpufreq_qos_add_request(policy, req->min, req->max); + if (qos == NULL) + { + return -ENOMEM; + } + + filep->f_priv = qos; + } + } + break; + + case CPUFREQIOC_CLEAR_REQUEST: + { + if (qos != NULL) + { + ret = cpufreq_qos_remove_request(qos); + filep->f_priv = NULL; + } + } + break; + + case CPUFREQIOC_GET_TABLE: + { + FAR struct cpufreq_table_query_s *query = + (FAR struct cpufreq_table_query_s *)(uintptr_t)arg; + unsigned int i; + + if (query == NULL) + { + return -EINVAL; + } + + query->nentries = policy->nentries; + if (query->frequencies != NULL) + { + for (i = 0; i < policy->nentries && i < query->maxlen; i++) + { + query->frequencies[i] = policy->table[i].frequency; + } + } + } + break; + + default: + ret = -ENOTTY; + break; + } + + return ret; +} + +#endif /* CONFIG_CPUFREQ_CHARDEV */ + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: cpufreq_init + ****************************************************************************/ + +int cpufreq_init(FAR struct cpufreq_driver *driver) +{ + FAR struct cpufreq_policy *policy; + FAR const struct cpufreq_frequency_table *table; + unsigned int count; + int ret; + + if (driver == NULL || driver->get_table == NULL || + driver->target_index == NULL) + { + return -EINVAL; + } + + if (g_cpufreq_policy != NULL) + { + return -EBUSY; + } + + policy = kmm_zalloc(sizeof(*policy)); + if (policy == NULL) + { + return -ENOMEM; + } + + policy->driver = driver; + + table = driver->get_table(policy); + if (table == NULL) + { + kmm_free(policy); + return -EINVAL; + } + + for (count = 0; table[count].frequency != CPUFREQ_TABLE_END; count++) + { + if (count > 0 && + table[count].frequency <= table[count - 1].frequency) + { + syslog(LOG_ERR, "cpufreq: table must ascend\n"); + kmm_free(policy); + return -EINVAL; + } + } + + if (count < 2) + { + syslog(LOG_ERR, "cpufreq: table too short to be worth scaling\n"); + kmm_free(policy); + return -EINVAL; + } + + policy->table = table; + policy->nentries = count; + policy->current = count - 1; /* Assume fastest until told otherwise */ + nxmutex_init(&policy->lock); + dq_init(&policy->requests); + + /* Prefer the lower half's reported frequency to the assumption above */ + + if (driver->get_frequency != NULL) + { + ret = driver->get_frequency(policy); + if (ret > 0) + { + unsigned int i; + + for (i = 0; i < count; i++) + { + if (table[i].frequency == (unsigned int)ret) + { + policy->current = i; + break; + } + } + } + } + + g_cpufreq_policy = policy; + +#ifdef CONFIG_CPUFREQ_CHARDEV + ret = register_driver("/dev/cpufreq", &g_cpufreq_fops, 0666, NULL); + if (ret < 0) + { + syslog(LOG_ERR, "cpufreq: cannot register /dev/cpufreq: %d\n", ret); + + /* The framework itself is still useful; carry on without it */ + } +#endif + + return OK; +} + +/**************************************************************************** + * Name: cpufreq_policy_get + ****************************************************************************/ + +FAR struct cpufreq_policy *cpufreq_policy_get(void) +{ + return g_cpufreq_policy; +} + +/**************************************************************************** + * Name: cpufreq_qos_add_request + ****************************************************************************/ + +FAR struct cpufreq_qos *cpufreq_qos_add_request( + FAR struct cpufreq_policy *policy, + unsigned int min, unsigned int max) +{ + FAR struct cpufreq_qos *qos; + + if (policy == NULL) + { + return NULL; + } + + qos = kmm_zalloc(sizeof(*qos)); + if (qos == NULL) + { + return NULL; + } + + qos->min = min; + qos->max = max; + + nxmutex_lock(&policy->lock); + dq_addlast(&qos->node, &policy->requests); + cpufreq_resolve(policy); + nxmutex_unlock(&policy->lock); + + return qos; +} + +/**************************************************************************** + * Name: cpufreq_qos_update_request + ****************************************************************************/ + +int cpufreq_qos_update_request(FAR struct cpufreq_qos *qos, + unsigned int min, unsigned int max) +{ + FAR struct cpufreq_policy *policy = g_cpufreq_policy; + int ret; + + if (qos == NULL || policy == NULL) + { + return -EINVAL; + } + + nxmutex_lock(&policy->lock); + qos->min = min; + qos->max = max; + ret = cpufreq_resolve(policy); + nxmutex_unlock(&policy->lock); + + return ret; +} + +/**************************************************************************** + * Name: cpufreq_qos_remove_request + ****************************************************************************/ + +int cpufreq_qos_remove_request(FAR struct cpufreq_qos *qos) +{ + FAR struct cpufreq_policy *policy = g_cpufreq_policy; + int ret; + + if (qos == NULL || policy == NULL) + { + return -EINVAL; + } + + nxmutex_lock(&policy->lock); + dq_rem(&qos->node, &policy->requests); + ret = cpufreq_resolve(policy); + nxmutex_unlock(&policy->lock); + + kmm_free(qos); + return ret; +} + +/**************************************************************************** + * Name: cpufreq_suspend + ****************************************************************************/ + +int cpufreq_suspend(void) +{ + FAR struct cpufreq_policy *policy = g_cpufreq_policy; + int ret = OK; + + if (policy == NULL) + { + return -ENODEV; + } + + nxmutex_lock(&policy->lock); + if (!policy->suspended) + { + if (policy->driver->suspend != NULL) + { + ret = policy->driver->suspend(policy); + } + + if (ret >= 0) + { + policy->suspended = true; + } + } + + nxmutex_unlock(&policy->lock); + return ret; +} + +/**************************************************************************** + * Name: cpufreq_resume + ****************************************************************************/ + +int cpufreq_resume(void) +{ + FAR struct cpufreq_policy *policy = g_cpufreq_policy; + int ret = OK; + + if (policy == NULL) + { + return -ENODEV; + } + + nxmutex_lock(&policy->lock); + if (policy->suspended) + { + if (policy->driver->resume != NULL) + { + ret = policy->driver->resume(policy); + } + + if (ret >= 0) + { + policy->suspended = false; + + /* Anything that changed while asleep applies now */ + + cpufreq_resolve(policy); + } + } + + nxmutex_unlock(&policy->lock); + return ret; +} diff --git a/include/nuttx/cpufreq.h b/include/nuttx/cpufreq.h new file mode 100644 index 0000000000000..88dc048f9ab83 --- /dev/null +++ b/include/nuttx/cpufreq.h @@ -0,0 +1,244 @@ +/**************************************************************************** + * include/nuttx/cpufreq.h + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __INCLUDE_NUTTX_CPUFREQ_H +#define __INCLUDE_NUTTX_CPUFREQ_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include + +#ifdef CONFIG_CPUFREQ + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Terminates a frequency table */ + +#define CPUFREQ_TABLE_END (~0u) + +/* Passed as a QoS bound to leave that side unconstrained */ + +#define CPUFREQ_NO_LIMIT (0) + +/* ioctl commands for the optional /dev/cpufreq character device. + * + * CPUFREQIOC_GET_FREQUENCY Arg: FAR unsigned int *. Receives the + * current frequency. + * CPUFREQIOC_SET_REQUEST Arg: FAR const struct cpufreq_request_s *. + * Installs or updates this file descriptor's + * QoS request. Released on close. + * CPUFREQIOC_CLEAR_REQUEST Arg: none. Removes this descriptor's + * request. + * CPUFREQIOC_GET_TABLE Arg: FAR struct cpufreq_table_query_s *. + * Copies out the frequency table. + */ + +#define CPUFREQIOC_GET_FREQUENCY _CPUFREQIOC(0x0001) +#define CPUFREQIOC_SET_REQUEST _CPUFREQIOC(0x0002) +#define CPUFREQIOC_CLEAR_REQUEST _CPUFREQIOC(0x0003) +#define CPUFREQIOC_GET_TABLE _CPUFREQIOC(0x0004) + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +struct cpufreq_policy; + +/* One entry of a driver's frequency table. The table is ascending and + * ends with an entry whose frequency is CPUFREQ_TABLE_END. The unit is + * the lower half's choice (kHz by Linux convention), as long as every + * consumer of the same policy agrees. + */ + +struct cpufreq_frequency_table +{ + unsigned int frequency; +}; + +/* The lower half: what a platform must provide. get_table and + * target_index are mandatory; the rest may be NULL. + */ + +struct cpufreq_driver +{ + CODE FAR const struct cpufreq_frequency_table * + (*get_table)(FAR struct cpufreq_policy *policy); + CODE int (*target_index)(FAR struct cpufreq_policy *policy, + unsigned int index); + CODE int (*get_frequency)(FAR struct cpufreq_policy *policy); + CODE int (*suspend)(FAR struct cpufreq_policy *policy); + CODE int (*resume)(FAR struct cpufreq_policy *policy); +}; + +/* The policy: one per system. The driver pointer must stay the first + * member; existing consumers reach the driver by casting the policy. + */ + +struct cpufreq_policy +{ + FAR struct cpufreq_driver *driver; + + /* Internal to the upper half */ + + FAR const struct cpufreq_frequency_table *table; + unsigned int nentries; + unsigned int current; /* Index of the entry last applied */ + bool suspended; + mutex_t lock; + dq_queue_t requests; +}; + +/* One QoS request: a [min, max] window the resolved frequency must + * respect. See cpufreq.c for how competing windows resolve. + */ + +struct cpufreq_qos +{ + dq_entry_t node; + unsigned int min; + unsigned int max; +}; + +/* Argument of CPUFREQIOC_SET_REQUEST */ + +struct cpufreq_request_s +{ + unsigned int min; /* Lowest acceptable, or CPUFREQ_NO_LIMIT */ + unsigned int max; /* Highest acceptable, or CPUFREQ_NO_LIMIT */ +}; + +/* Argument of CPUFREQIOC_GET_TABLE */ + +struct cpufreq_table_query_s +{ + FAR unsigned int *frequencies; /* Where to put them */ + unsigned int maxlen; /* Room, in entries */ + unsigned int nentries; /* How many the table really has */ +}; + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +#undef EXTERN +#if defined(__cplusplus) +#define EXTERN extern "C" +extern "C" +{ +#else +#define EXTERN extern +#endif + +/**************************************************************************** + * Name: cpufreq_init + * + * Description: + * Bring up the framework over a lower half. Called once, by the + * platform, after the hardware it drives is ready. Registers + * /dev/cpufreq when CONFIG_CPUFREQ_CHARDEV is enabled. + * + * Input Parameters: + * driver - The lower half. Must outlive the framework. + * + * Returned Value: + * Zero on success; a negated errno otherwise. -EBUSY if called twice. + * + ****************************************************************************/ + +int cpufreq_init(FAR struct cpufreq_driver *driver); + +/**************************************************************************** + * Name: cpufreq_policy_get + * + * Description: + * The system's policy, or NULL before cpufreq_init. + * + ****************************************************************************/ + +FAR struct cpufreq_policy *cpufreq_policy_get(void); + +/**************************************************************************** + * Name: cpufreq_qos_add_request + * + * Description: + * Constrain the frequency to [min, max] and re-resolve. Either bound + * may be CPUFREQ_NO_LIMIT. + * + * Returned Value: + * The request, to update or remove later; NULL on failure. + * + ****************************************************************************/ + +FAR struct cpufreq_qos *cpufreq_qos_add_request( + FAR struct cpufreq_policy *policy, + unsigned int min, unsigned int max); + +/**************************************************************************** + * Name: cpufreq_qos_update_request + * + * Description: + * Change an installed request's window and re-resolve. + * + ****************************************************************************/ + +int cpufreq_qos_update_request(FAR struct cpufreq_qos *qos, + unsigned int min, unsigned int max); + +/**************************************************************************** + * Name: cpufreq_qos_remove_request + * + * Description: + * Withdraw a request and re-resolve without it. The request is freed. + * + ****************************************************************************/ + +int cpufreq_qos_remove_request(FAR struct cpufreq_qos *qos); + +/**************************************************************************** + * Name: cpufreq_suspend / cpufreq_resume + * + * Description: + * Hand the hardware to the lower half's suspend path and back. While + * suspended the resolver leaves the hardware alone; requests are still + * accepted and take effect on resume. + * + ****************************************************************************/ + +int cpufreq_suspend(void); +int cpufreq_resume(void); + +#undef EXTERN +#if defined(__cplusplus) +} +#endif + +#endif /* CONFIG_CPUFREQ */ +#endif /* __INCLUDE_NUTTX_CPUFREQ_H */ diff --git a/include/nuttx/fs/ioctl.h b/include/nuttx/fs/ioctl.h index 4533ec6922d05..3869bb55b4970 100644 --- a/include/nuttx/fs/ioctl.h +++ b/include/nuttx/fs/ioctl.h @@ -115,6 +115,7 @@ #define _PTPBASE (0x4700) /* PTP ioctl commands */ #define _DSHOTIOCBASE (0x4800) /* Dshot device ioctl commands */ #define _PULSECOUNTBASE (0x4900) /* Pulse count driver ioctl commands */ +#define _CPUFREQIOCBASE (0x4a00) /* CPU frequency ioctl commands */ #define _WLIOCBASE (0x8b00) /* Wireless modules ioctl network commands */ /* boardctl() commands share the same number space */ @@ -852,6 +853,11 @@ #define _PULSECOUNTIOCVALID(c) (_IOC_TYPE(c)==_PULSECOUNTBASE) #define _PULSECOUNTIOC(nr) _IOC(_PULSECOUNTBASE,nr) +/* CPU frequency driver ioctl definitions ***********************************/ + +#define _CPUFREQIOCVALID(c) (_IOC_TYPE(c)==_CPUFREQIOCBASE) +#define _CPUFREQIOC(nr) _IOC(_CPUFREQIOCBASE,nr) + /**************************************************************************** * Public Type Definitions ****************************************************************************/ From 6139dfa14f60775e033bf579df90cfb26dfb9f79 Mon Sep 17 00:00:00 2001 From: Justin Hammond Date: Thu, 6 Aug 2026 20:31:43 +0800 Subject: [PATCH 2/3] thermal: Include what thermal_cpufreq_cooling.c uses. 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 --- drivers/thermal/thermal_cpufreq_cooling.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/thermal/thermal_cpufreq_cooling.c b/drivers/thermal/thermal_cpufreq_cooling.c index 7758decd6bd24..2718c3666b2b6 100644 --- a/drivers/thermal/thermal_cpufreq_cooling.c +++ b/drivers/thermal/thermal_cpufreq_cooling.c @@ -24,6 +24,8 @@ * Included Files ****************************************************************************/ +#include + #include #include From e30cb5e7dbc6b0eaed89d3856411b896e652a321 Mon Sep 17 00:00:00 2001 From: Justin Hammond Date: Fri, 7 Aug 2026 20:21:07 +0800 Subject: [PATCH 3/3] Documentation: describe the CPU frequency scaling framework. 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 --- .../components/drivers/special/cpufreq.rst | 200 ++++++++++++++++++ .../components/drivers/special/index.rst | 1 + 2 files changed, 201 insertions(+) create mode 100644 Documentation/components/drivers/special/cpufreq.rst diff --git a/Documentation/components/drivers/special/cpufreq.rst b/Documentation/components/drivers/special/cpufreq.rst new file mode 100644 index 0000000000000..ae29d915595a3 --- /dev/null +++ b/Documentation/components/drivers/special/cpufreq.rst @@ -0,0 +1,200 @@ +===================== +CPU Frequency Scaling +===================== + +The CPU frequency framework lets several unrelated parts of the system have +an opinion about how fast the CPU should run, and resolves those opinions +into one frequency. A platform supplies a lower half: a table of the +frequencies its hardware supports and a way to move between them. Everything +above that is arbitration. + +It is enabled with ``CONFIG_CPUFREQ``. There is one policy per system. + +Design +====== + +Each requester installs a ``[min, max]`` window that it can live with. A +thermal cooling device installs one as a zone heats up, an application +holding ``/dev/cpufreq`` installs one, a power manager installs one. The +framework keeps every window and, whenever the set changes, recomputes a +single answer: + +1. Take the lowest ``max`` across all installed requests. A request that + passes ``CPUFREQ_NO_LIMIT`` for a bound leaves that side unconstrained + and does not participate. +#. Choose the highest table entry at or below that ceiling. +#. Apply it through the lower half, if it differs from the entry currently + applied. + +Speed is therefore the default: with no requests installed, the top table +entry is selected, and any single requester can pull the system down. + +Two consequences of this are worth stating plainly, because they are design +decisions rather than oversights: + +- **A floor never raises the frequency.** Because the resolver already picks + the highest permitted entry, any ``min`` that can be satisfied already is. + The ``min`` field is accepted and stored so that a request expresses a + complete window, but it does not participate in the calculation. +- **When windows do not intersect, the lowest ceiling wins.** A requester + asking for less speed is presumed to be protecting something, so its + ceiling is honoured and a conflicting floor is not. + +The lower half is never told who asked for what. It only ever hears "go to +table entry N". + +The Lower Half +============== + +A platform provides a ``struct cpufreq_driver``. ``get_table`` and +``target_index`` are mandatory; the rest may be NULL: + +.. code-block:: c + + struct cpufreq_driver + { + CODE FAR const struct cpufreq_frequency_table * + (*get_table)(FAR struct cpufreq_policy *policy); + CODE int (*target_index)(FAR struct cpufreq_policy *policy, + unsigned int index); + CODE int (*get_frequency)(FAR struct cpufreq_policy *policy); + CODE int (*suspend)(FAR struct cpufreq_policy *policy); + CODE int (*resume)(FAR struct cpufreq_policy *policy); + }; + +``get_table`` + Returns the frequency table. It must ascend, and it must end with an entry + whose ``frequency`` is ``CPUFREQ_TABLE_END``. + +``target_index`` + Moves the hardware to the table entry at ``index``. This is the only call + that changes the frequency. + +``get_frequency`` + Reports where the hardware actually is, in table units. Called once during + initialisation so the framework can start from the truth rather than an + assumption. If NULL, the framework assumes the lowest entry. + +``suspend`` and ``resume`` + Called from ``cpufreq_suspend()`` and ``cpufreq_resume()``. + +The frequency unit is the lower half's choice. kHz is the Linux convention +and a reasonable default, but the framework does not care as long as every +consumer of the same policy agrees. + +Bring the framework up once, after the hardware it drives is ready: + +.. code-block:: c + + static struct cpufreq_driver g_mychip_cpufreq = + { + .get_table = mychip_get_table, + .target_index = mychip_target_index, + .get_frequency = mychip_get_frequency, + }; + + cpufreq_init(&g_mychip_cpufreq); + +``cpufreq_init()`` returns ``-EBUSY`` if called twice, and registers +``/dev/cpufreq`` when ``CONFIG_CPUFREQ_CHARDEV`` is set. + +.. note:: + ``driver`` must remain the first member of ``struct cpufreq_policy``. + Existing consumers reach the lower half by casting a policy pointer. + +In-kernel Requests +================== + +Kernel code constrains the frequency through three calls: + +.. code-block:: c + + FAR struct cpufreq_qos *qos; + + qos = cpufreq_qos_add_request(cpufreq_policy_get(), + CPUFREQ_NO_LIMIT, /* min */ + 800000); /* max */ + + cpufreq_qos_update_request(qos, CPUFREQ_NO_LIMIT, 1200000); + + cpufreq_qos_remove_request(qos); + +Each call re-resolves the frequency before returning. +``cpufreq_qos_remove_request()`` frees the request. + +``cpufreq_policy_get()`` returns NULL before ``cpufreq_init()`` has run. + +/dev/cpufreq +============ + +With ``CONFIG_CPUFREQ_CHARDEV`` the policy is also a character device, so an +application can read the frequency and install a request of its own. Each +open descriptor owns at most one request, which is released when the +descriptor closes, including on task exit. + +``CPUFREQIOC_GET_FREQUENCY`` + Arg: ``FAR unsigned int *``. Receives the current frequency. + +``CPUFREQIOC_SET_REQUEST`` + Arg: ``FAR const struct cpufreq_request_s *``. Installs or updates this + descriptor's request. Either bound may be ``CPUFREQ_NO_LIMIT``. + +``CPUFREQIOC_CLEAR_REQUEST`` + No argument. Removes this descriptor's request. + +``CPUFREQIOC_GET_TABLE`` + Arg: ``FAR struct cpufreq_table_query_s *``. Copies out the frequency + table. Set ``frequencies`` and ``maxlen``; ``nentries`` is returned as the + number the table really has, which may exceed ``maxlen``. + +.. code-block:: c + + struct cpufreq_request_s req = + { + .min = CPUFREQ_NO_LIMIT, + .max = 800000, + }; + + int fd = open("/dev/cpufreq", O_RDONLY); + ioctl(fd, CPUFREQIOC_SET_REQUEST, (unsigned long)&req); + + /* The cap holds for as long as this descriptor is open */ + + close(fd); + +Thermal Integration +=================== + +``CONFIG_THERMAL_CDEV_CPUFREQ`` registers CPU frequency as a thermal cooling +device, so a thermal zone can throttle the CPU with no board code in +between. The cooling device counts states downward from the top of the +frequency table: state 0 is unthrottled, and each step installs a tighter +window through the same QoS interface described above. + +See :doc:`../thermal/index` for how zones, trip points and cooling devices +fit together. + +Suspend and Resume +================== + +.. code-block:: c + + cpufreq_suspend(); + cpufreq_resume(); + +These pass through to the lower half's ``suspend`` and ``resume``. While +suspended, the resolver leaves the hardware alone: requests are still +accepted and still recorded, and whatever they resolve to is applied on +resume. + +Configuration +============= + +``CONFIG_CPUFREQ`` + Enables the framework. + +``CONFIG_CPUFREQ_CHARDEV`` + Registers ``/dev/cpufreq``. Default on. + +``CONFIG_THERMAL_CDEV_CPUFREQ`` + Registers the thermal cooling device. Requires ``CONFIG_THERMAL``. diff --git a/Documentation/components/drivers/special/index.rst b/Documentation/components/drivers/special/index.rst index 1444d74eb9b04..4241a4d4f0273 100644 --- a/Documentation/components/drivers/special/index.rst +++ b/Documentation/components/drivers/special/index.rst @@ -24,6 +24,7 @@ following section. audio.rst clk.rst + cpufreq.rst devicetree.rst devmem.rst dma.rst