From b4f8a4b6e023166d043118ed1243ffe77fd225c2 Mon Sep 17 00:00:00 2001 From: guanyi Date: Wed, 2 Apr 2025 14:19:24 +0800 Subject: [PATCH 01/15] driver/devfreq: DVFS framework for devices 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 --- drivers/Kconfig | 1 + drivers/Makefile | 1 + drivers/devfreq/CMakeLists.txt | 25 + drivers/devfreq/Kconfig | 36 ++ drivers/devfreq/Make.defs | 30 + drivers/devfreq/devfreq.c | 856 ++++++++++++++++++++++++++ drivers/devfreq/devfreq_performance.c | 63 ++ drivers/devfreq/devfreq_powersave.c | 63 ++ drivers/devfreq/devfreq_qos.c | 195 ++++++ include/nuttx/devfreq.h | 324 ++++++++++ include/nuttx/devfreq/devfreq_qos.h | 66 ++ include/nuttx/plist.h | 346 +++++++++++ 12 files changed, 2006 insertions(+) create mode 100644 drivers/devfreq/CMakeLists.txt create mode 100644 drivers/devfreq/Kconfig create mode 100644 drivers/devfreq/Make.defs create mode 100644 drivers/devfreq/devfreq.c create mode 100644 drivers/devfreq/devfreq_performance.c create mode 100644 drivers/devfreq/devfreq_powersave.c create mode 100644 drivers/devfreq/devfreq_qos.c create mode 100644 include/nuttx/devfreq.h create mode 100644 include/nuttx/devfreq/devfreq_qos.h create mode 100644 include/nuttx/plist.h diff --git a/drivers/Kconfig b/drivers/Kconfig index 20e77b4a97165..2dd9ff9cdcb3a 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -11,6 +11,7 @@ source "drivers/crypto/Kconfig" source "drivers/loop/Kconfig" source "drivers/can/Kconfig" source "drivers/clk/Kconfig" +source "drivers/devfreq/Kconfig" source "drivers/i2c/Kconfig" source "drivers/i3c/Kconfig" source "drivers/spi/Kconfig" diff --git a/drivers/Makefile b/drivers/Makefile index dd39ff4d7f169..fe6b440e53975 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -34,6 +34,7 @@ include bch/Make.defs include can/Make.defs include clk/Make.defs include crypto/Make.defs +include devfreq/Make.defs include devicetree/Make.defs include dma/Make.defs include math/Make.defs diff --git a/drivers/devfreq/CMakeLists.txt b/drivers/devfreq/CMakeLists.txt new file mode 100644 index 0000000000000..91ba9ef476066 --- /dev/null +++ b/drivers/devfreq/CMakeLists.txt @@ -0,0 +1,25 @@ +# ############################################################################## +# drivers/devfreq/CMakeLists.txt +# +# 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_DEVFREQ) + set(SRCS devfreq.c devfreq_performance.c devfreq_powersave.c devfreq_qos.c) + + target_sources(drivers PRIVATE ${SRCS}) +endif() diff --git a/drivers/devfreq/Kconfig b/drivers/devfreq/Kconfig new file mode 100644 index 0000000000000..7bb0a18ab51bf --- /dev/null +++ b/drivers/devfreq/Kconfig @@ -0,0 +1,36 @@ +# +# For a description of the syntax of this configuration file, +# see the file kconfig-language.txt in the NuttX tools repository. +# + +config DEVFREQ + bool "devfreq" + default n + ---help--- + devfreq framework, dynamic voltage frequency scaling(DVFS) + for device + +if DEVFREQ + +choice + prompt "DEVFREQ_DEFAULT_GOVERNOR" + default DEVFREQ_DEFAULT_GOV_PERFORMANCE + +config DEVFREQ_DEFAULT_GOV_PERFORMANCE + bool "devfreq_performance" + ---help--- + devfreq performance governor, always choose the highest frequency + +config DEVFREQ_DEFAULT_GOV_POWERSAVE + bool "devfreq_powersave" + ---help--- + devfreq powersave governor, always choose the lowest frequency + +config DEVFREQ_DEFAULT_GOV_PASSIVE + bool "devfreq_passive" + ---help--- + devfreq passive governor, a device-defined governor + +endchoice + +endif diff --git a/drivers/devfreq/Make.defs b/drivers/devfreq/Make.defs new file mode 100644 index 0000000000000..e50951e5b6b6c --- /dev/null +++ b/drivers/devfreq/Make.defs @@ -0,0 +1,30 @@ +############################################################################ +# drivers/devfreq/Make.defs +# +# 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. +# +############################################################################ + +# Include devfreq sources + +ifeq ($(CONFIG_DEVFREQ),y) + +CSRCS += devfreq.c devfreq_performance.c devfreq_powersave.c devfreq_qos.c + +DEPPATH += --dep-path devfreq +VPATH += devfreq + +endif diff --git a/drivers/devfreq/devfreq.c b/drivers/devfreq/devfreq.c new file mode 100644 index 0000000000000..2bb3a59929061 --- /dev/null +++ b/drivers/devfreq/devfreq.c @@ -0,0 +1,856 @@ +/**************************************************************************** + * drivers/devfreq/devfreq.c + * + * 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. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static struct list_node g_devfreq_list = LIST_INITIAL_VALUE(g_devfreq_list); +static mutex_t g_devfreq_list_lock = NXMUTEX_INITIALIZER; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static int devfreq_init_governor(FAR struct devfreq_s *devfreq); +static void devfreq_exit_governor(FAR struct devfreq_s *devfreq); +static int devfreq_start_governor(FAR struct devfreq_s *devfreq); +static void devfreq_stop_governor(FAR struct devfreq_s *devfreq); +static void devfreq_limit_governor(FAR struct devfreq_s *devfreq); +static ssize_t devfreq_table_find_freq(FAR struct devfreq_s *devfreq, + uint32_t target_freq, + int relation); +static int devfreq_table_validate(FAR struct devfreq_s *devfreq); +static void devfreq_refresh_limit(FAR struct devfreq_s *devfreq); +static int devfreq_driver_target(FAR struct devfreq_s *devfreq, + uint32_t target_freq, + int relation); + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: devfreq_init_governor + * + * Description: + * Initialize governor + * + * Input Parameters: + * devfreq - devfreq device + * + * Returned Value: + * Zero on success; a negated errno on failure + * + ****************************************************************************/ + +static int devfreq_init_governor(FAR struct devfreq_s *devfreq) +{ + if (!devfreq->governor) + { + return -EINVAL; + } + + if (devfreq->governor->init) + { + return devfreq->governor->init(devfreq); + } + + return 0; +} + +/**************************************************************************** + * Name: devfreq_exit_governor + * + * Description: + * Exit governor + * + * Input Parameters: + * devfreq - devfreq device + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void devfreq_exit_governor(FAR struct devfreq_s *devfreq) +{ + if (!devfreq->governor) + { + return; + } + + if (devfreq->governor->exit) + { + devfreq->governor->exit(devfreq); + } +} + +/**************************************************************************** + * Name: devfreq_start_governor + * + * Description: + * Start governor + * + * Input Parameters: + * devfreq - devfreq device + * + * Returned Value: + * Zero (OK) on success; a negated errno on failure + * + ****************************************************************************/ + +static int devfreq_start_governor(FAR struct devfreq_s *devfreq) +{ + if (devfreq->suspended) + { + return 0; + } + + if (!devfreq->governor) + { + return -EINVAL; + } + + if (devfreq->governor->start) + { + int ret = devfreq->governor->start(devfreq); + if (ret < 0) + { + return ret; + } + } + + devfreq_limit_governor(devfreq); + return 0; +} + +/**************************************************************************** + * Name: devfreq_stop_governor + * + * Description: + * Stop governor + * + * Input Parameters: + * devfreq - devfreq device + * + * Returned Value: + * Zero on success; a negated errno on failure + * + ****************************************************************************/ + +static void devfreq_stop_governor(FAR struct devfreq_s *devfreq) +{ + if (devfreq->suspended || !devfreq->governor) + { + return; + } + + if (devfreq->governor->stop) + { + devfreq->governor->stop(devfreq); + } +} + +/**************************************************************************** + * Name: devfreq_limit_governor + * + * Description: + * Limit governor + * + * Input Parameters: + * devfreq - devfreq device + * + * Returned Value: + * Zero on success; a negated errno on failure + * + ****************************************************************************/ + +static void devfreq_limit_governor(FAR struct devfreq_s *devfreq) +{ + if (devfreq->suspended || !devfreq->governor) + { + return; + } + + if (devfreq->governor->limit) + { + uint32_t freq = devfreq->governor->limit(devfreq); + devfreq_driver_target(devfreq, freq, DEVFREQ_RELATION_L); + } +} + +/**************************************************************************** + * Name: devfreq_table_find_freq + * + * Description: + * Find frequency in table + * + * Input Parameters: + * devfreq - devfreq device + * target_freq - target frequency + * relation - relation + * + * Returned Value: + * target index on success; negated errno on failure + * + ****************************************************************************/ + +static ssize_t devfreq_table_find_freq(FAR struct devfreq_s *devfreq, + uint32_t target_freq, + int relation) +{ + ssize_t best = -ENOENT; + size_t i; + + if (relation == DEVFREQ_RELATION_L) + { + for (i = 0; devfreq->freq_table[i] != DEVFREQ_ENTRY_END; i++) + { + if (devfreq->freq_table[i] == DEVFREQ_ENTRY_INVALID) + { + continue; + } + + if (devfreq->freq_table[i] >= target_freq) + { + best = i; + break; + } + } + } + else if (relation == DEVFREQ_RELATION_H) + { + for (i = 0; devfreq->freq_table[i] != DEVFREQ_ENTRY_END; i++) + { + if (devfreq->freq_table[i] == DEVFREQ_ENTRY_INVALID) + { + continue; + } + + if (devfreq->freq_table[i] > target_freq) + { + break; + } + + best = i; + } + } + + return best; +} + +/**************************************************************************** + * Name: devfreq_table_validate + * + * Description: + * Validate table + * + * Input Parameters: + * devfreq - devfreq device + * + * Returned Value: + * Zero on success; a negated errno on failure + * + ****************************************************************************/ + +static int devfreq_table_validate(FAR struct devfreq_s *devfreq) +{ + FAR const uint32_t *table = devfreq->freq_table; + uint32_t prv_freq = 0; + uint32_t min_freq = UINT32_MAX; + uint32_t max_freq = 0; + size_t i; + + if (!table) + { + return -EINVAL; + } + + for (i = 0; table[i] != DEVFREQ_ENTRY_END; i++) + { + if (table[i] == DEVFREQ_ENTRY_INVALID) + { + continue; + } + + if (i && table[i] <= prv_freq) + { + return -EINVAL; + } + + if (table[i] < min_freq) + { + min_freq = table[i]; + } + + if (table[i] > max_freq) + { + max_freq = table[i]; + } + + prv_freq = table[i]; + } + + devfreq->min = min_freq; + devfreq->max = max_freq; + + return 0; +} + +/**************************************************************************** + * Name: devfreq_refresh_limit + * + * Description: + * Refresh limit + * + * Input Parameters: + * devfreq - devfreq device + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void devfreq_refresh_limit(FAR struct devfreq_s *devfreq) +{ + uint32_t min; + uint32_t max; + ssize_t idx; + + min = qos_get_value(&devfreq->constraints, QOS_REQ_MIN); + max = qos_get_value(&devfreq->constraints, QOS_REQ_MAX); + + if (min > max) + { + min = max; + } + + idx = devfreq_table_find_freq(devfreq, min, DEVFREQ_RELATION_L); + if (idx >= 0) + { + devfreq->min = devfreq->freq_table[idx]; + } + + idx = devfreq_table_find_freq(devfreq, max, DEVFREQ_RELATION_H); + if (idx >= 0) + { + devfreq->max = devfreq->freq_table[idx]; + } + + devfreq_limit_governor(devfreq); +} + +/**************************************************************************** + * Name: devfreq_driver_target + * + * Description: + * Set target frequency + * + * Input Parameters: + * devfreq - devfreq device + * target_freq - target frequency + * relation - relation to target frequency + * + * Returned Value: + * Zero on success; a negated errno on failure + * + ****************************************************************************/ + +static int devfreq_driver_target(FAR struct devfreq_s *devfreq, + uint32_t target_freq, + int relation) +{ + struct devfreq_notifier_s freq; + ssize_t idx; + int ret; + + if (!devfreq) + { + return -EINVAL; + } + + idx = devfreq_table_find_freq(devfreq, target_freq, relation); + if (idx < 0) + { + return -ENOENT; + } + + target_freq = devfreq->freq_table[idx]; + if (target_freq == devfreq->cur) + { + return 0; + } + + freq.old = devfreq->cur; + freq.new = target_freq; + + blocking_notifier_call_chain(&devfreq->notifier_list, + DEVFREQ_PRECHANGE, &freq); + ret = devfreq->driver->target_index(devfreq, idx); + blocking_notifier_call_chain(&devfreq->notifier_list, + DEVFREQ_POSTCHANGE, &freq); + if (ret < 0) + { + freq.old = target_freq; + freq.new = devfreq->cur; + blocking_notifier_call_chain(&devfreq->notifier_list, + DEVFREQ_PRECHANGE, &freq); + blocking_notifier_call_chain(&devfreq->notifier_list, + DEVFREQ_POSTCHANGE, &freq); + return ret; + } + + devfreq->cur = target_freq; + return 0; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: devfreq_register + * + * Description: + * Register devfreq device + * + * Input Parameters: + * name - device name + * governor - governor + * driver - driver + * priv - private data + * + * Returned Value: + * devfreq device on success; NULL on failure + * + ****************************************************************************/ + +FAR struct devfreq_s *devfreq_register( + const char *name, + FAR struct devfreq_governor_s *governor, + FAR struct devfreq_driver_s *driver, + FAR void *priv) +{ + FAR struct devfreq_s *devfreq = devfreq_find_by_name(name); + + if (devfreq || !driver) + { + return NULL; + } + + devfreq = (FAR struct devfreq_s *)kmm_zalloc(sizeof(struct devfreq_s)); + if (!devfreq) + { + return NULL; + } + + qos_constraints_init(&devfreq->constraints); + BLOCKING_INIT_NOTIFIER_HEAD(&devfreq->notifier_list); + nxmutex_init(&devfreq->lock); + + devfreq->driver = driver; + devfreq->priv = priv; + devfreq->suspended = false; + devfreq->freq_table = driver->get_table(devfreq); + if (!devfreq->freq_table) + { + goto out; + } + + if (devfreq_table_validate(devfreq) < 0) + { + goto out; + } + + if (!governor) + { + devfreq->governor = devfreq_default_governor(); + } + else + { + devfreq->governor = governor; + } + + if (devfreq_init_governor(devfreq) < 0) + { + goto out; + } + + devfreq_start_governor(devfreq); + + nxmutex_lock(&g_devfreq_list_lock); + list_add_tail(&g_devfreq_list, &devfreq->node); + nxmutex_unlock(&g_devfreq_list_lock); + + return devfreq; + +out: + nxmutex_destroy(&devfreq->lock); + nxmutex_destroy(&devfreq->notifier_list.mutex); + kmm_free(devfreq); + return NULL; +} + +/**************************************************************************** + * Name: devfreq_unregister + * + * Description: + * Unregister devfreq device + * + * Input Parameters: + * devfreq - devfreq device + * + * Returned Value: + * Zero on success; a negated errno on failure + * + ****************************************************************************/ + +int devfreq_unregister(FAR struct devfreq_s *devfreq) +{ + if (!devfreq) + { + return -EINVAL; + } + + nxmutex_lock(&g_devfreq_list_lock); + list_delete(&devfreq->node); + nxmutex_unlock(&g_devfreq_list_lock); + + devfreq_stop_governor(devfreq); + devfreq_exit_governor(devfreq); + + nxmutex_destroy(&devfreq->lock); + nxmutex_destroy(&devfreq->notifier_list.mutex); + kmm_free(devfreq); + return 0; +} + +/**************************************************************************** + * Name: devfreq_suspend + * + * Description: + * Suspend devfreq device + * + * Input Parameters: + * devfreq - devfreq device + * + * Returned Value: + * Zero on success; a negated errno on failure + * + ****************************************************************************/ + +int devfreq_suspend(FAR struct devfreq_s *devfreq) +{ + nxmutex_lock(&devfreq->lock); + + devfreq_stop_governor(devfreq); + + if (devfreq->driver->suspend) + { + int ret = devfreq->driver->suspend(devfreq); + if (ret < 0) + { + nxmutex_unlock(&devfreq->lock); + return ret; + } + } + + devfreq->suspended = true; + nxmutex_unlock(&devfreq->lock); + return 0; +} + +/**************************************************************************** + * Name: devfreq_resume + * + * Description: + * Resume devfreq device + * + * Input Parameters: + * devfreq - devfreq device + * + * Returned Value: + * Zero on success; a negated errno on failure + * + ****************************************************************************/ + +int devfreq_resume(struct devfreq_s *devfreq) +{ + nxmutex_lock(&devfreq->lock); + + if (devfreq->driver->resume) + { + int ret = devfreq->driver->resume(devfreq); + if (ret < 0) + { + nxmutex_unlock(&devfreq->lock); + return ret; + } + } + + devfreq->suspended = false; + devfreq_start_governor(devfreq); + + nxmutex_unlock(&devfreq->lock); + return 0; +} + +/**************************************************************************** + * Name: devfreq_register_notifier + * + * Description: + * Register notifier + * + * Input Parameters: + * devfreq - devfreq device + * nb - notifier block + * + * Returned Value: + * Zero on success; a negated errno on failure + * + ****************************************************************************/ + +int devfreq_register_notifier(FAR struct devfreq_s *devfreq, + FAR struct notifier_block *nb) +{ + if (!devfreq || !nb) + { + return -EINVAL; + } + + blocking_notifier_chain_register(&devfreq->notifier_list, nb); + return 0; +} + +/**************************************************************************** + * Name: devfreq_unregister_notifier + * + * Description: + * Unregister notifier + * + * Input Parameters: + * devfreq - devfreq device + * nb - notifier block + * + * Returned Value: + * Zero on success; a negated errno on failure + * + ****************************************************************************/ + +int devfreq_unregister_notifier(FAR struct devfreq_s *devfreq, + FAR struct notifier_block *nb) +{ + if (!devfreq || !nb) + { + return -EINVAL; + } + + blocking_notifier_chain_unregister(&devfreq->notifier_list, nb); + return 0; +} + +/**************************************************************************** + * Name: devfreq_get_frequency + * + * Description: + * Get current frequency + * + * Input Parameters: + * devfreq - devfreq device + * + * Returned Value: + * Current frequency + * + ****************************************************************************/ + +FAR uint32_t devfreq_get_frequency(FAR struct devfreq_s *devfreq) +{ + return devfreq->driver->get_frequency(devfreq); +} + +/**************************************************************************** + * Name: devfreq_qos_add_request + * + * Description: + * Add a new request + * + * Input Parameters: + * devfreq - devfreq device + * min - minimum frequency + * max - maximum frequency + * + * Returned Value: + * Zero on success; a negated errno on failure + * + ****************************************************************************/ + +FAR struct qos_request_s *devfreq_qos_add_request( + FAR struct devfreq_s *devfreq, + uint32_t min, uint32_t max) +{ + FAR struct qos_request_s *req; + + if (!devfreq) + { + return NULL; + } + + nxmutex_lock(&devfreq->lock); + + req = qos_add_request(&devfreq->constraints, min, max); + devfreq_refresh_limit(devfreq); + + nxmutex_unlock(&devfreq->lock); + return req; +} + +/**************************************************************************** + * Name: devfreq_qos_update_request + * + * Description: + * Update a request + * + * Input Parameters: + * qos - devfreq qos + * min - minimum frequency + * max - maximum frequency + * + * Returned Value: + * Zero on success; a negated errno on failure + * + ****************************************************************************/ + +int devfreq_qos_update_request(FAR struct devfreq_s *devfreq, + FAR struct qos_request_s *req, + uint32_t min, uint32_t max) +{ + int ret; + + if (!devfreq || !req) + { + return -EINVAL; + } + + nxmutex_lock(&devfreq->lock); + + ret = qos_update_request(&devfreq->constraints, req, min, max); + if (ret < 0) + { + nxmutex_unlock(&devfreq->lock); + return ret; + } + + devfreq_refresh_limit(devfreq); + + nxmutex_unlock(&devfreq->lock); + return ret; +} + +/**************************************************************************** + * Name: devfreq_qos_remove_request + * + * Description: + * Remove a request + * + * Input Parameters: + * qos - devfreq qos + * + * Returned Value: + * Zero on success; a negated errno on failure + * + ****************************************************************************/ + +int devfreq_qos_remove_request(FAR struct devfreq_s *devfreq, + FAR struct qos_request_s *req) +{ + int ret; + + if (!devfreq || !req) + { + return -EINVAL; + } + + nxmutex_lock(&devfreq->lock); + + ret = qos_remove_request(&devfreq->constraints, req); + if (ret < 0) + { + nxmutex_unlock(&devfreq->lock); + return ret; + } + + devfreq_refresh_limit(devfreq); + + nxmutex_unlock(&devfreq->lock); + return ret; +} + +/**************************************************************************** + * Name: devfreq_find_by_name + * + * Description: + * find a devfreq entry from global list by name + * + * Input Parameters: + * name - devfreq name + * + * Returned Value: + * devfreq handle + * + ****************************************************************************/ + +FAR struct devfreq_s *devfreq_find_by_name(FAR const char *name) +{ + FAR struct devfreq_s *devfreq; + + if (!name) + { + return NULL; + } + + nxmutex_lock(&g_devfreq_list_lock); + + list_for_every_entry(&g_devfreq_list, devfreq, struct devfreq_s, node) + { + if (!strcmp(devfreq->name, name)) + { + nxmutex_unlock(&g_devfreq_list_lock); + return devfreq; + } + } + + nxmutex_unlock(&g_devfreq_list_lock); + return NULL; +} diff --git a/drivers/devfreq/devfreq_performance.c b/drivers/devfreq/devfreq_performance.c new file mode 100644 index 0000000000000..5e582607f9ea2 --- /dev/null +++ b/drivers/devfreq/devfreq_performance.c @@ -0,0 +1,63 @@ +/**************************************************************************** + * drivers/devfreq/devfreq_performance.c + * + * 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. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#ifdef CONFIG_DEVFREQ_DEFAULT_GOV_PERFORMANCE + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static uint32_t devfreq_performance_limit(FAR struct devfreq_s *devfreq); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static struct devfreq_governor_s g_devfreq_gov_performance = +{ + .name = "performance", + .limit = devfreq_performance_limit, +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static uint32_t devfreq_performance_limit(FAR struct devfreq_s *devfreq) +{ + return devfreq->max; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +FAR struct devfreq_governor_s *devfreq_default_governor(void) +{ + return &g_devfreq_gov_performance; +} + +#endif /* CONFIG_DEVFREQ_DEFAULT_GOV_PERFORMANCE */ diff --git a/drivers/devfreq/devfreq_powersave.c b/drivers/devfreq/devfreq_powersave.c new file mode 100644 index 0000000000000..bc4e991dd1243 --- /dev/null +++ b/drivers/devfreq/devfreq_powersave.c @@ -0,0 +1,63 @@ +/**************************************************************************** + * drivers/devfreq/devfreq_powersave.c + * + * 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. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#ifdef CONFIG_DEVFREQ_DEFAULT_GOV_POWERSAVE + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static uint32_t devfreq_powersave_limit(FAR struct devfreq_s *devfreq); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static struct devfreq_governor_s g_devfreq_gov_powersave = +{ + .name = "powersave", + .limit = devfreq_powersave_limit, +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static uint32_t devfreq_powersave_limit(FAR struct devfreq_s *devfreq) +{ + return devfreq->min; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +FAR struct devfreq_governor_s *devfreq_default_governor(void) +{ + return &g_devfreq_gov_powersave; +} + +#endif /* CONFIG_DEVFREQ_DEFAULT_GOV_POWERSAVE */ diff --git a/drivers/devfreq/devfreq_qos.c b/drivers/devfreq/devfreq_qos.c new file mode 100644 index 0000000000000..251b25f0880cb --- /dev/null +++ b/drivers/devfreq/devfreq_qos.c @@ -0,0 +1,195 @@ +/**************************************************************************** + * drivers/devfreq/devfreq_qos.c + * + * 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. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: qos_constraints_init + * + * Description: + * Initialize a qos_constraints_s struct. + * + * Input Parameters: + * qos - The qos constraints struct to initialize. + * + * Returned Value: + * None + * + ****************************************************************************/ + +void qos_constraints_init(FAR struct qos_constraints_s *constraints) +{ + plist_head_init(&constraints->min_requests); + plist_head_init(&constraints->max_requests); +} + +/**************************************************************************** + * Name: qos_add_request + * + * Description: + * Add a qos request to qos constraints. + * + * Input Parameters: + * qos - The qos constraints to add the request to. + * min - The minimum priority/value of the request. + * max - The maximum priority/value of the request. + * + * Returned Value: + * A pointer to the new qos_request_s. + * + ****************************************************************************/ + +FAR struct qos_request_s *qos_add_request( + FAR struct qos_constraints_s *constraints, + uint32_t min, uint32_t max) +{ + FAR struct qos_request_s *req; + + if (!constraints) + { + return NULL; + } + + req = kmm_zalloc(sizeof(struct qos_request_s)); + if (!req) + { + return NULL; + } + + plist_node_init(&req->min_req, min); + plist_node_init(&req->max_req, max); + + plist_add(&req->min_req, &constraints->min_requests); + plist_add(&req->max_req, &constraints->max_requests); + + return req; +} + +/**************************************************************************** + * Name: qos_remove_request + * + * Description: + * Remove qos request from qos constraints. + * + * Input Parameters: + * qos - The qos_constraints_s to remove the request from. + * req - The qos_request_s to remove. + * + * Returned Value: + * Zero on success; a negated errno on failure. + * + ****************************************************************************/ + +int qos_remove_request(FAR struct qos_constraints_s *constraints, + FAR struct qos_request_s *req) +{ + if (!req || !constraints) + { + return -EINVAL; + } + + plist_del(&req->min_req, &constraints->min_requests); + plist_del(&req->max_req, &constraints->max_requests); + + kmm_free(req); + + return 0; +} + +/**************************************************************************** + * Name: qos_update_request + * + * Description: + * Update qos request. + * + * Input Parameters: + * qos - The qos constraints. + * req - The qos request to update. + * min - The new minimum priority/value of the request. + * max - The new maximum priority/value of the request. + * + * Returned Value: + * Zero on success; a negated errno on failure. + * + ****************************************************************************/ + +int qos_update_request(FAR struct qos_constraints_s *constraints, + FAR struct qos_request_s *req, + uint32_t min, uint32_t max) +{ + if (!req || !constraints) + { + return -EINVAL; + } + + req->min_req.prio = min; + req->max_req.prio = max; + + plist_del(&req->min_req, &constraints->min_requests); + plist_del(&req->max_req, &constraints->max_requests); + plist_add(&req->min_req, &constraints->min_requests); + plist_add(&req->max_req, &constraints->max_requests); + + return 0; +} + +/**************************************************************************** + * Name: qos_get_value + * + * Description: + * Get min or max value of qos constraints. + * + * Input Parameters: + * qos - The qos constraints. + * type - The type of request to get the value of. + * + * Returned Value: + * The value of the qos request. + * + ****************************************************************************/ + +uint32_t qos_get_value(FAR struct qos_constraints_s *constraints, + enum qos_req_type_e type) +{ + switch (type) + { + case QOS_REQ_MIN: + { + return plist_first(&constraints->min_requests)->prio; + } + + case QOS_REQ_MAX: + { + return plist_last(&constraints->max_requests)->prio; + } + } + + return 0; +} diff --git a/include/nuttx/devfreq.h b/include/nuttx/devfreq.h new file mode 100644 index 0000000000000..367986e5b5076 --- /dev/null +++ b/include/nuttx/devfreq.h @@ -0,0 +1,324 @@ +/**************************************************************************** + * include/nuttx/devfreq.h + * + * 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_DEVFREQ_H +#define __INCLUDE_NUTTX_DEVFREQ_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define DEVFREQ_PRECHANGE 0 +#define DEVFREQ_POSTCHANGE 1 + +/* Special Values of .frequency field */ + +#define DEVFREQ_ENTRY_INVALID ~0u +#define DEVFREQ_ENTRY_END ~1u + +#define DEVFREQ_RELATION_L 0 /* lowest frequency at or above target */ +#define DEVFREQ_RELATION_H 1 /* highest frequency below or at target */ + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +struct devfreq_s +{ + char name[NAME_MAX]; + struct list_node node; + + FAR struct devfreq_governor_s *governor; + FAR struct devfreq_driver_s *driver; + + FAR const uint32_t *freq_table; + + struct qos_constraints_s constraints; + + struct blocking_notifier_head notifier_list; + + uint32_t min; /* in kHz */ + uint32_t max; /* in kHz */ + uint32_t cur; /* in kHz */ + + bool suspended; + + mutex_t lock; + + FAR void *priv; +}; + +struct devfreq_governor_s +{ + char name[NAME_MAX]; + CODE int (*init)(FAR struct devfreq_s *devfreq); + CODE int (*exit)(FAR struct devfreq_s *devfreq); + CODE int (*start)(FAR struct devfreq_s *devfreq); + CODE void (*stop)(FAR struct devfreq_s *devfreq); + CODE uint32_t (*limit)(FAR struct devfreq_s *devfreq); +}; + +struct devfreq_driver_s +{ + CODE FAR const uint32_t * + (*get_table)(FAR struct devfreq_s *devfreq); + CODE int (*target_index)(FAR struct devfreq_s *devfreq, + size_t index); + CODE uint32_t (*get_frequency)(FAR struct devfreq_s *devfreq); + CODE int (*suspend)(FAR struct devfreq_s *devfreq); + CODE int (*resume)(FAR struct devfreq_s *devfreq); +}; + +struct devfreq_notifier_s +{ + uint32_t old; + uint32_t new; +}; + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +#undef EXTERN +#if defined(__cplusplus) +#define EXTERN extern "C" +extern "C" +{ +#else +#define EXTERN extern +#endif + +/**************************************************************************** + * Name: devfreq_register + * + * Description: + * Register devfreq device + * + * Input Parameters: + * name - device name + * governor - governor + * driver - driver + * priv - private data + * + * Returned Value: + * devfreq device on success; NULL on failure + * + ****************************************************************************/ + +FAR struct devfreq_s *devfreq_register( + FAR const char *name, + FAR struct devfreq_governor_s *governor, + FAR struct devfreq_driver_s *driver, + FAR void *priv); + +/**************************************************************************** + * Name: devfreq_unregister + * + * Description: + * unregister devfreq + * + * Input Parameters: + * devfreq - devfreq_s handle + * + * Returned Value: + * Zero on success; a negated errno value on failure. + * + ****************************************************************************/ + +int devfreq_unregister(FAR struct devfreq_s *devfreq); + +/**************************************************************************** + * Name: devfreq_suspend + * + * Description: + * suspend devfreq governors + * + * Input Parameters: + * devfreq - devfreq_s handle + * + * Returned Value: + * Zero on success; a negated errno value on failure. + * + ****************************************************************************/ + +int devfreq_suspend(FAR struct devfreq_s *devfreq); + +/**************************************************************************** + * Name: devfreq_resume + * + * Description: + * resume devfreq governors + * + * Input Parameters: + * devfreq - devfreq_s handle + * + * Returned Value: + * Zero on success; a negated errno value on failure. + * + ****************************************************************************/ + +int devfreq_resume(FAR struct devfreq_s *devfreq); + +/**************************************************************************** + * Name: devfreq_set_governor + * + * Description: + * set devfreq governor + * + * Input Parameters: + * devfreq - devfreq_s handle + * governor - governor handle + * + * Returned Value: + * Zero on success; a negated errno value on failure. + * + ****************************************************************************/ + +int devfreq_register_notifier(FAR struct devfreq_s *devfreq, + FAR struct notifier_block *nb); + +/**************************************************************************** + * Name: devfreq_unregister_notifier + * + * Description: + * unregister devfreq notifier + * + * Input Parameters: + * devfreq - devfreq_s handle + * nb - notifier block + * + * Returned Value: + * Zero on success; a negated errno value on failure. + * + ****************************************************************************/ + +int devfreq_unregister_notifier(FAR struct devfreq_s *devfreq, + FAR struct notifier_block *nb); + +/**************************************************************************** + * Name: devfreq_get + * + * Description: + * get the current device frequency (in kHz) + * + * Input Parameters: + * devfreq - devfreq_s handle + * + * Returned Value: + * a non-negative value + * + ****************************************************************************/ + +uint32_t devfreq_get_frequency(FAR struct devfreq_s *devfreq); + +/**************************************************************************** + * Name: devfreq_qos_add_request + * + * Description: + * Insert new frequency QoS request + * + * Input Parameters: + * policy - devfreq_policy handle + * min - min freq + * max - max freq + * + * Returned Value: + * qos handle for update and remove, or NULL if fail + * + ****************************************************************************/ + +FAR struct qos_request_s *devfreq_qos_add_request( + FAR struct devfreq_s *devfreq, + uint32_t min, uint32_t max); + +/**************************************************************************** + * Name: devfreq_qos_update_request + * + * Description: + * Update frequency QoS request from its list. + * + * Input Parameters: + * qos - Request to remove. + * min - min freq + * max - max freq + * + * Returned Value: + * Zero on success; a negated errno value on failure. + * + ****************************************************************************/ + +int devfreq_qos_update_request(FAR struct devfreq_s *devfreq, + FAR struct qos_request_s *qos, + uint32_t min, uint32_t max); + +/**************************************************************************** + * Name: devfreq_qos_remove_request + * + * Description: + * Remove frequency QoS request from its list. + * + * Input Parameters: + * qos - Request to remove. + * + * Returned Value: + * Zero on success; a negated errno value on failure. + * + ****************************************************************************/ + +int devfreq_qos_remove_request(FAR struct devfreq_s *devfreq, + FAR struct qos_request_s *req); + +/**************************************************************************** + * Name: devfreq_find_by_name + * + * Description: + * find a devfreq entry from global list by name + * + * Input Parameters: + * name - devfreq name + * + * Returned Value: + * devfreq handle + * + ****************************************************************************/ + +FAR struct devfreq_s *devfreq_find_by_name(FAR const char *name); + +#ifdef CONFIG_DEVFREQ_DEFAULT_GOV_PASSIVE +#define devfreq_default_governor() NULL +#else +FAR struct devfreq_governor_s *devfreq_default_governor(void); +#endif + +#undef EXTERN +#if defined(__cplusplus) +} +#endif + +#endif /* __INCLUDE_NUTTX_DEVFREQ_H */ diff --git a/include/nuttx/devfreq/devfreq_qos.h b/include/nuttx/devfreq/devfreq_qos.h new file mode 100644 index 0000000000000..47837b4c6e055 --- /dev/null +++ b/include/nuttx/devfreq/devfreq_qos.h @@ -0,0 +1,66 @@ +/**************************************************************************** + * include/nuttx/devfreq/devfreq_qos.h + * + * 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_DEVFREQ_DEVFREQ_QOS_H +#define __INCLUDE_NUTTX_DEVFREQ_DEVFREQ_OQS_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +enum qos_req_type_e +{ + QOS_REQ_MIN, + QOS_REQ_MAX +}; + +struct qos_request_s +{ + struct plist_node min_req; + struct plist_node max_req; +}; + +struct qos_constraints_s +{ + struct plist_head min_requests; + struct plist_head max_requests; +}; + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +void qos_constraints_init(FAR struct qos_constraints_s *constraints); +FAR struct qos_request_s *qos_add_request(FAR struct qos_constraints_s *qos, + uint32_t min, uint32_t max); +int qos_remove_request(FAR struct qos_constraints_s *constraints, + FAR struct qos_request_s *req); +int qos_update_request(FAR struct qos_constraints_s *constraints, + FAR struct qos_request_s *req, + uint32_t min, uint32_t max); +uint32_t qos_get_value(FAR struct qos_constraints_s *constraints, + enum qos_req_type_e type); +#endif diff --git a/include/nuttx/plist.h b/include/nuttx/plist.h new file mode 100644 index 0000000000000..79177159e00ab --- /dev/null +++ b/include/nuttx/plist.h @@ -0,0 +1,346 @@ +/**************************************************************************** + * include/nuttx/plist.h + * + * 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_PLIST_H +#define __INCLUDE_NUTTX_PLIST_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include + +/**************************************************************************** + * Public Type Definitions + ****************************************************************************/ + +struct plist_head +{ + struct list_node node_list; +}; + +struct plist_node +{ + uint32_t prio; + struct list_node prio_list; + struct list_node node_list; +}; + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* PLIST_HEAD_INIT - static struct plist_head initializer + * head: struct plist_head variable name + */ + +#define PLIST_HEAD_INIT(head) \ + { \ + .node_list = LIST_HEAD_INIT((head).node_list) \ + } + +/* PLIST_HEAD - declare and init plist_head + * head: name for struct plist_head variable + */ + +#define PLIST_HEAD(head) \ + struct plist_head head = PLIST_HEAD_INIT(head) + +/* PLIST_NODE_INIT - static struct plist_node initializer + * node: struct plist_node variable name + * prio: initial node priority + */ + +#define PLIST_NODE_INIT(node, val) \ + { \ + .prio = (val), \ + .prio_list = LIST_HEAD_INIT((node).prio_list), \ + .node_list = LIST_HEAD_INIT((node).node_list), \ + } + +/* plist_head_init - dynamic struct plist_head initializer + * head: struct plist_head pointer + */ + +#define plist_head_init(head) \ + list_initialize(&(head)->node_list) + +/* plist_node_init - Dynamic struct plist_node initializer + * node: struct plist_node pointer + * prio: initial node priority + */ + +#define plist_node_init(node, val) \ + do \ + { \ + (node)->prio = (val); \ + list_initialize(&(node)->prio_list); \ + list_initialize(&(node)->node_list); \ + } \ + while(0) + +/* plist_for_each - iterate over the plist + * pos: the type * to use as a loop counter + * head: the head for your list + */ + +#define plist_for_each(pos, head) \ + list_for_every_entry(&(head)->node_list, pos, typeof(*pos), node_list) + +/* plist_for_each_continue - continue iteration over the plist + * pos: the type * to use as a loop cursor + * head: the head for your list + * + * Continue to iterate over plist, continuing after the current position. + */ + +#define plist_for_each_continue(pos, head) \ + list_for_every_entry_continue(pos, &(head)->node_list, typeof(*pos), node_list) + +/* plist_for_each_safe - iterate safely over a plist of given type + * pos: the type * to use as a loop counter + * n: another type * to use as temporary storage + * head: the head for your list + * + * Iterate over a plist of given type, safe against removal of list entry. + */ + +#define plist_for_each_safe(pos, n, head) \ + list_for_every_entry_safe(&(head)->node_list, pos, n, typeof(*pos), node_list) + +/* plist_for_each_entry - iterate over list of given type + * pos: the type * to use as a loop counter + * head: the head for your list + * mem: the name of the list_node within the struct + */ + +#define plist_for_each_entry(pos, head, mem) \ + list_for_every_entry(&(head)->node_list, pos, typeof(*pos), mem.node_list) + +/* plist_for_each_entry_continue - continue iteration over list of given type + * pos: the type * to use as a loop cursor + * head: the head for your list + * m: the name of the list_node within the struct + * + * Continue to iterate over list of given type, continuing after + * the current position. + */ +#define plist_for_each_entry_continue(pos, head, m) \ + list_for_every_entry_continue(pos, &(head)->node_list, typeof(*pos), m.node_list) + +/* plist_for_each_entry_safe - iterate safely over list of given type + * pos: the type * to use as a loop counter + * n: another type * to use as temporary storage + * head: the head for your list + * m: the name of the list_node within the struct + * + * Iterate over list of given type, safe against removal of list entry. + */ +#define plist_for_each_entry_safe(pos, n, head, m) \ + list_for_every_entry_safe(&(head)->node_list, pos, n, typeof(*pos), m.node_list) + +/* All functions below assume the plist_head is not empty. */ + +/* plist_first_entry - get the struct for the first entry + * head: the struct plist_head pointer + * type: the type of the struct this is embedded in + * member: the name of the list_node within the struct + */ + +#define plist_first_entry(head, type, member) \ + container_of(plist_first(head), type, member) + +/* plist_last_entry - get the struct for the last entry + * head: the struct plist_head pointer + * type: the type of the struct this is embedded in + * member: the name of the list_node within the struct + */ + +#define plist_last_entry(head, type, member) \ + container_of(plist_last(head), type, member) + +/* plist_next - get the next entry in list + * pos: the type * to cursor + */ + +#define plist_next(pos) list_next_entry(pos, typeof(*(pos)), node_list) + +/* plist_prev - get the prev entry in list + * pos: the type * to cursor + */ + +#define plist_prev(pos) list_prev_entry(pos, typeof(*(pos)), node_list) + +/* plist_head_empty - return !0 if a plist_head is empty + * head: struct plist_head pointer + */ + +#define plist_head_empty(head) list_is_empty(&(head)->node_list) + +/* plist_node_empty - return !0 if plist_node is not on a list + * node: struct plist_node pointer + */ + +#define plist_node_empty(node) list_is_empty(&(node)->node_list) + +/* plist_first - return the first node (and thus, highest priority) + * head: the struct plist_head pointer + * + * Assumes the plist is _not_ empty. + */ + +#define plist_first(head) \ + list_entry((head)->node_list.next, struct plist_node, node_list) + +/* plist_last - return the last node (and thus, lowest priority) + * head: the struct plist_head pointer + * + * Assumes the plist is _not_ empty. + */ + +#define plist_last(head) \ + list_entry((head)->node_list.prev, struct plist_node, node_list) + +/* plist_add - add node to head + * node: struct plist_node pointer + * head: struct plist_head pointer + */ + +#define plist_add(node, head) \ + do \ + { \ + FAR struct plist_head *head_ = (head); \ + FAR struct plist_node *node_ = (node); \ + FAR struct list_node *node_next_ = &head_->node_list; \ + \ + DEBUGASSERT(plist_node_empty(node_)); \ + DEBUGASSERT(list_is_empty(&node_->prio_list)); \ + \ + if (!plist_head_empty(head_)) \ + { \ + FAR struct plist_node *first_ = plist_first(head_); \ + FAR struct plist_node *iter_ = first_; \ + FAR struct plist_node *prev_ = NULL; \ + \ + do \ + { \ + if (node_->prio < iter_->prio) \ + { \ + node_next_ = &iter_->node_list; \ + break; \ + } \ + \ + prev_ = iter_; \ + iter_ = list_entry(iter_->prio_list.next, \ + struct plist_node, prio_list); \ + } \ + while (iter_ != first_); \ + \ + if (!prev_ || prev_->prio != node_->prio) \ + { \ + list_add_tail(&iter_->prio_list, &node_->prio_list); \ + } \ + } \ + \ + list_add_tail(node_next_, &node_->node_list); \ + } \ + while (0) + +/* plist_del - Remove a node from plist. + * node: struct plist_node pointer - entry to be removed + * head: struct plist_head pointer - list head + */ + +#define plist_del(node, head) \ + do \ + { \ + FAR struct plist_head *head_ = (head); \ + FAR struct plist_node *node_ = (node); \ + \ + if (!list_is_empty(&node_->prio_list)) \ + { \ + if (node_->node_list.next != &head_->node_list) \ + { \ + FAR struct plist_node *next_ = \ + list_entry(node_->node_list.next, \ + struct plist_node, node_list); \ + \ + /* Add the next plist_node into prio_list */ \ + \ + if (list_is_empty(&next_->prio_list)) \ + { \ + list_add_head(&node_->prio_list, &next_->prio_list); \ + } \ + } \ + \ + list_delete_init(&node_->prio_list); \ + } \ + \ + list_delete_init(&node_->node_list); \ + } \ + while (0) + +/* plist_requeue - Requeue node at end of same-prio entries. + * + * This is essentially an optimized plist_del() followed by + * plist_add(). It moves an entry already in the plist to + * after any other same-priority entries. + * node: struct plist_node pointer - entry to be moved + * head: struct plist_head pointer - list head + */ + +#define plist_requeue(node, head) \ + do \ + { \ + FAR struct plist_head *head_ = (head); \ + FAR struct plist_node *node_ = (node); \ + \ + DEBUGASSERT(!plist_head_empty(head_)); \ + DEBUGASSERT(!plist_node_empty(node_)); \ + \ + if (node_ != plist_last(head_)) \ + { \ + FAR struct plist_node *iter_ = plist_next(node_); \ + \ + if (node_->prio == iter_->prio) \ + { \ + FAR struct list_node *node_next_ = &head_->node_list; \ + \ + plist_del(node_, head_); \ + \ + plist_for_each_continue(iter_, head_) \ + { \ + if (node_->prio != iter_->prio) \ + { \ + node_next_ = &iter_->node_list; \ + break; \ + } \ + } \ + \ + list_add_tail(node_next_, &node_->node_list); \ + } \ + } \ + } \ + while (0) + +#endif /* __INCLUDE_NUTTX_PLIST_H */ From 014e00199edf579a287ad956bfe0d3e45a5372b0 Mon Sep 17 00:00:00 2001 From: guanyi Date: Fri, 16 May 2025 16:07:54 +0800 Subject: [PATCH 02/15] driver/devfreq: add procfs for devfreq > 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 --- drivers/devfreq/CMakeLists.txt | 4 + drivers/devfreq/Kconfig | 15 + drivers/devfreq/Make.defs | 6 + drivers/devfreq/devfreq.c | 35 +++ drivers/devfreq/devfreq_procfs.c | 414 ++++++++++++++++++++++++++++ drivers/devfreq/devfreq_qos.c | 9 + drivers/drivers_initialize.c | 5 + include/nuttx/devfreq.h | 32 +++ include/nuttx/devfreq/devfreq_qos.h | 3 + 9 files changed, 523 insertions(+) create mode 100644 drivers/devfreq/devfreq_procfs.c diff --git a/drivers/devfreq/CMakeLists.txt b/drivers/devfreq/CMakeLists.txt index 91ba9ef476066..9fb34381e49b6 100644 --- a/drivers/devfreq/CMakeLists.txt +++ b/drivers/devfreq/CMakeLists.txt @@ -21,5 +21,9 @@ if(CONFIG_DEVFREQ) set(SRCS devfreq.c devfreq_performance.c devfreq_powersave.c devfreq_qos.c) + if(CONFIG_DEVFREQ_PROCFS) + list(APPEND SRCS devfreq_procfs.c) + endif() + target_sources(drivers PRIVATE ${SRCS}) endif() diff --git a/drivers/devfreq/Kconfig b/drivers/devfreq/Kconfig index 7bb0a18ab51bf..cd0b562913f9c 100644 --- a/drivers/devfreq/Kconfig +++ b/drivers/devfreq/Kconfig @@ -33,4 +33,19 @@ config DEVFREQ_DEFAULT_GOV_PASSIVE endchoice +config DEVFREQ_PROCFS + bool "devfreq_procfs" + default n + depends on FS_PROCFS + select FS_PROCFS_REGISTER + ---help--- + devfreq procfs support + +config DEVFREQ_PROCFS_QOS + bool "devfreq_procfs_qos" + default n + depends on DEVFREQ_PROCFS + ---help--- + devfreq procfs show qos requests and their callers + endif diff --git a/drivers/devfreq/Make.defs b/drivers/devfreq/Make.defs index e50951e5b6b6c..59baad2d02382 100644 --- a/drivers/devfreq/Make.defs +++ b/drivers/devfreq/Make.defs @@ -24,6 +24,12 @@ ifeq ($(CONFIG_DEVFREQ),y) CSRCS += devfreq.c devfreq_performance.c devfreq_powersave.c devfreq_qos.c +ifeq ($(CONFIG_DEVFREQ_PROCFS),y) + +CSRCS += devfreq_procfs.c + +endif + DEPPATH += --dep-path devfreq VPATH += devfreq diff --git a/drivers/devfreq/devfreq.c b/drivers/devfreq/devfreq.c index 2bb3a59929061..c2116315b5ce6 100644 --- a/drivers/devfreq/devfreq.c +++ b/drivers/devfreq/devfreq.c @@ -480,6 +480,7 @@ FAR struct devfreq_s *devfreq_register( BLOCKING_INIT_NOTIFIER_HEAD(&devfreq->notifier_list); nxmutex_init(&devfreq->lock); + strlcpy(devfreq->name, name, NAME_MAX); devfreq->driver = driver; devfreq->priv = priv; devfreq->suspended = false; @@ -854,3 +855,37 @@ FAR struct devfreq_s *devfreq_find_by_name(FAR const char *name) nxmutex_unlock(&g_devfreq_list_lock); return NULL; } + +/**************************************************************************** + * Name: devfreq_find_by_index + * + * Description: + * find a devfreq entry from global list by index + * + * Input Parameters: + * index - devfreq index + * + * Returned Value: + * devfreq handle + * + ****************************************************************************/ + +FAR struct devfreq_s *devfreq_find_by_index(size_t index) +{ + FAR struct devfreq_s *devfreq; + size_t i = 0; + + nxmutex_lock(&g_devfreq_list_lock); + + list_for_every_entry(&g_devfreq_list, devfreq, struct devfreq_s, node) + { + if (index == i++) + { + nxmutex_unlock(&g_devfreq_list_lock); + return devfreq; + } + } + + nxmutex_unlock(&g_devfreq_list_lock); + return NULL; +} diff --git a/drivers/devfreq/devfreq_procfs.c b/drivers/devfreq/devfreq_procfs.c new file mode 100644 index 0000000000000..700e1a960aa22 --- /dev/null +++ b/drivers/devfreq/devfreq_procfs.c @@ -0,0 +1,414 @@ +/**************************************************************************** + * drivers/devfreq/devfreq_procfs.c + * + * 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. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Determines the size of an intermediate buffer that must be large enough + * to handle the longest line generated by this logic. + */ + +#define DEVFREQ_LINELEN 256 + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct devfreq_procfs_s +{ + struct procfs_file_s base; + FAR struct devfreq_s *devfreq; +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/* File system methods */ + +static int devfreq_open(FAR struct file *filep, + FAR const char *relpath, + int oflags, mode_t mode); +static int devfreq_close(FAR struct file *filep); +static ssize_t devfreq_read(FAR struct file *filep, + FAR char *buffer, + size_t buflen); +static ssize_t devfreq_write(FAR struct file *filep, + FAR const char *buffer, + size_t buflen); +static int devfreq_dup(FAR const struct file *oldp, + FAR struct file *newp); +static int devfreq_opendir(FAR const char *relpath, + FAR struct fs_dirent_s **dir); +static int devfreq_readdir(FAR struct fs_dirent_s *dir, + FAR struct dirent *entry); +static int devfreq_closedir(FAR struct fs_dirent_s *dir); +static int devfreq_rewinddir(FAR struct fs_dirent_s *dir); +static int devfreq_stat(FAR const char *relpath, FAR struct stat *buf); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct procfs_operations g_devfreq_operations = +{ + .open = devfreq_open, /* open */ + .close = devfreq_close, /* close */ + .read = devfreq_read, /* read */ + .write = devfreq_write, /* write */ + .poll = NULL, /* poll */ + .dup = devfreq_dup, /* dup */ + + .opendir = devfreq_opendir, /* opendir */ + .closedir = devfreq_closedir, /* closedir */ + .readdir = devfreq_readdir, /* readdir */ + .rewinddir = devfreq_rewinddir, /* rewinddir */ + .stat = devfreq_stat, /* stat */ +}; + +static const struct procfs_entry_s g_devfreq_procfs_root = +{ + "devfreq", &g_devfreq_operations, PROCFS_DIR_TYPE +}; + +static const struct procfs_entry_s g_devfreq_procfs_entry = +{ + "devfreq/**", &g_devfreq_operations, PROCFS_UNKOWN_TYPE +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: devfreq_open + ****************************************************************************/ + +static int devfreq_open(FAR struct file *filep, FAR const char *relpath, + int oflags, mode_t mode) +{ + FAR struct devfreq_s *devfreq; + FAR struct devfreq_procfs_s *devfreq_procfs; + + relpath += strlen("devfreq/"); + devfreq = devfreq_find_by_name(relpath); + if (!devfreq) + { + return -ENOENT; + } + + devfreq_procfs = kmm_zalloc(sizeof(struct devfreq_procfs_s)); + if (!devfreq_procfs) + { + return -ENOMEM; + } + + devfreq_procfs->devfreq = devfreq; + filep->f_priv = devfreq_procfs; + return 0; +} + +/**************************************************************************** + * Name: devfreq_close + ****************************************************************************/ + +static int devfreq_close(FAR struct file *filep) +{ + DEBUGASSERT(filep->f_priv); + + kmm_free(filep->f_priv); + filep->f_priv = NULL; + return 0; +} + +/**************************************************************************** + * Name: devfreq_read + ****************************************************************************/ + +static ssize_t devfreq_read(FAR struct file *filep, + FAR char *buffer, size_t buflen) +{ + FAR struct devfreq_procfs_s *devfreq_procfs = filep->f_priv; + FAR struct devfreq_s *devfreq = devfreq_procfs->devfreq; +#ifdef CONFIG_DEVFREQ_PROCFS_QOS + FAR struct qos_request_s *qos; + void **stack; + int depth; +#endif + off_t offset = filep->f_pos; + size_t i; + + nxmutex_lock(&devfreq->lock); + + procfs_sprintf(buffer, buflen, &offset, + " devfreq: %s\n" + " governor: %s\n" + " cur_freq: %"PRIu32"\n" + " suspended: %s\n", + devfreq->name, + devfreq->governor->name, + devfreq->cur, + devfreq->suspended ? "True" : "False"); + + if (devfreq->freq_table) + { + procfs_sprintf(buffer, buflen, &offset, " freq_table: "); + for (i = 0; devfreq->freq_table[i] != DEVFREQ_ENTRY_END; i++) + { + if (devfreq->freq_table[i] == DEVFREQ_ENTRY_INVALID) + { + continue; + } + + procfs_sprintf(buffer, buflen, &offset, + " %"PRIu32"", devfreq->freq_table[i]); + } + + procfs_sprintf(buffer, buflen, &offset, "\n"); + } + +#ifdef CONFIG_DEVFREQ_PROCFS_QOS + procfs_sprintf(buffer, buflen, &offset, + " qos_list(min, max, backtrace):\n"); + plist_for_each_entry(qos, &devfreq->constraints.min_requests, min_req) + { + stack = backtrace_get(qos->backtrace, &depth); + procfs_sprintf(buffer, buflen, &offset, + " %"PRIu32", %"PRIu32",", + qos->min_req.prio, qos->max_req.prio); + for (i = 0; i < depth; i++) + { + procfs_sprintf(buffer, buflen, &offset, " %p", stack[i]); + } + + procfs_sprintf(buffer, buflen, &offset, "\n"); + } +#endif + + nxmutex_unlock(&devfreq->lock); + + if (offset < 0) + { + offset = -offset; + } + else + { + offset = 0; + } + + filep->f_pos += offset; + return offset; +} + +/**************************************************************************** + * Name: devfreq_write + ****************************************************************************/ + +static ssize_t devfreq_write(FAR struct file *filep, + FAR const char *buffer, size_t buflen) +{ + return buflen; +} + +/**************************************************************************** + * Name: devfreq_dup + * + * Description: + * Duplicate open file data in the new file structure. + * + ****************************************************************************/ + +static int devfreq_dup(FAR const struct file *oldp, FAR struct file *newp) +{ + newp->f_priv = oldp->f_priv; + return 0; +} + +/**************************************************************************** + * Name: devfreq_opendir + * + * Description: + * Open a directory for read access + * + ****************************************************************************/ + +static int devfreq_opendir(FAR const char *relpath, + FAR struct fs_dirent_s **dir) +{ + FAR struct procfs_dir_priv_s *level1; + + level1 = kmm_zalloc(sizeof(struct procfs_dir_priv_s)); + if (!level1) + { + *dir = NULL; + return -ENOMEM; + } + + level1->level = 1; + + level1->nentries = UINT16_MAX; + + *dir = (FAR struct fs_dirent_s *)level1; + return 0; +} + +/**************************************************************************** + * Name: devfreq_closedir + * + * Description: + * Close the directory listing + * + ****************************************************************************/ + +static int devfreq_closedir(FAR struct fs_dirent_s *dir) +{ + kmm_free(dir); + return 0; +} + +/**************************************************************************** + * Name: devfreq_readdir + * + * Description: + * Read the next directory entry + * + ****************************************************************************/ + +static int devfreq_readdir(FAR struct fs_dirent_s *dir, + FAR struct dirent *entry) +{ + FAR struct devfreq_s *devfreq; + FAR struct procfs_dir_priv_s *level1; + + DEBUGASSERT(dir); + level1 = (FAR struct procfs_dir_priv_s *)dir; + devfreq = devfreq_find_by_index(level1->index); + if (!devfreq) + { + return -ENOENT; + } + + entry->d_type = DTYPE_FILE; + strlcpy(entry->d_name, devfreq->name, NAME_MAX); + level1->index++; + return 0; +} + +/**************************************************************************** + * Name: devfreq_rewinddir + * + * Description: + * Reset directory read to the first entry + * + ****************************************************************************/ + +static int devfreq_rewinddir(FAR struct fs_dirent_s *dir) +{ + FAR struct procfs_dir_priv_s *level1; + + DEBUGASSERT(dir); + level1 = (FAR struct procfs_dir_priv_s *)dir; + level1->index = 0; + return 0; +} + +/**************************************************************************** + * Name: devfreq_stat + * + * Description: + * Return information about a file or directory + * + ****************************************************************************/ + +static int devfreq_stat(FAR const char *relpath, FAR struct stat *buf) +{ + FAR struct devfreq_s *devfreq; + + memset(buf, 0, sizeof(struct stat)); + + if (strcmp(relpath, "devfreq") == 0 || strcmp(relpath, "devfreq/") == 0) + { + buf->st_mode = S_IFDIR | S_IROTH | S_IRGRP | S_IRUSR; + } + else + { + relpath += strlen("devfreq/"); + devfreq = devfreq_find_by_name(relpath); + if (!devfreq) + { + return -ENOENT; + } + + buf->st_mode = S_IFREG | S_IROTH | S_IRGRP | S_IRUSR; + } + + return 0; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: devfreq_procfs_initialize + * + * Description: + * initialize procfs for devfreq, called by devfreq_initialize() + * + * Input Parameters: + * None + * + * Returned Value: + * None + * + ****************************************************************************/ + +void devfreq_procfs_initialize(void) +{ + int ret; + + ret = procfs_register(&g_devfreq_procfs_root); + if (ret == 0) + { + ret = procfs_register(&g_devfreq_procfs_entry); + } + + DEBUGASSERT(ret == 0); +} diff --git a/drivers/devfreq/devfreq_qos.c b/drivers/devfreq/devfreq_qos.c index 251b25f0880cb..501f30e3d63e0 100644 --- a/drivers/devfreq/devfreq_qos.c +++ b/drivers/devfreq/devfreq_qos.c @@ -25,6 +25,7 @@ #include #include #include +#include /**************************************************************************** * Public Functions @@ -89,6 +90,10 @@ FAR struct qos_request_s *qos_add_request( plist_add(&req->min_req, &constraints->min_requests); plist_add(&req->max_req, &constraints->max_requests); +#ifdef CONFIG_DEVFREQ_PROCFS_QOS + req->backtrace = backtrace_record(0); +#endif + return req; } @@ -118,6 +123,10 @@ int qos_remove_request(FAR struct qos_constraints_s *constraints, plist_del(&req->min_req, &constraints->min_requests); plist_del(&req->max_req, &constraints->max_requests); +#ifdef CONFIG_DEVFREQ_PROCFS_QOS + backtrace_remove(req->backtrace); +#endif + kmm_free(req); return 0; diff --git a/drivers/drivers_initialize.c b/drivers/drivers_initialize.c index 968abaf282119..43dce646413b8 100644 --- a/drivers/drivers_initialize.c +++ b/drivers/drivers_initialize.c @@ -26,6 +26,7 @@ #include #include +#include #include #include #include @@ -125,6 +126,10 @@ void drivers_initialize(void) serial_rtt_initialize(); #endif +#if defined(CONFIG_DEVFREQ_PROCFS) + devfreq_procfs_initialize(); +#endif + #if defined(CONFIG_DEV_NULL) devnull_register(); /* Standard /dev/null */ #endif diff --git a/include/nuttx/devfreq.h b/include/nuttx/devfreq.h index 367986e5b5076..2487a05bf1e91 100644 --- a/include/nuttx/devfreq.h +++ b/include/nuttx/devfreq.h @@ -310,6 +310,38 @@ int devfreq_qos_remove_request(FAR struct devfreq_s *devfreq, FAR struct devfreq_s *devfreq_find_by_name(FAR const char *name); +/**************************************************************************** + * Name: devfreq_find_by_index + * + * Description: + * find a devfreq entry from global list by index + * + * Input Parameters: + * index - devfreq index + * + * Returned Value: + * devfreq handle + * + ****************************************************************************/ + +FAR struct devfreq_s *devfreq_find_by_index(size_t index); + +/**************************************************************************** + * Name: devfreq_procfs_initialize + * + * Description: + * initialize procfs for devfreq, called by devfreq_initialize() + * + * Input Parameters: + * None + * + * Returned Value: + * None + * + ****************************************************************************/ + +void devfreq_procfs_initialize(void); + #ifdef CONFIG_DEVFREQ_DEFAULT_GOV_PASSIVE #define devfreq_default_governor() NULL #else diff --git a/include/nuttx/devfreq/devfreq_qos.h b/include/nuttx/devfreq/devfreq_qos.h index 47837b4c6e055..d85e135033f99 100644 --- a/include/nuttx/devfreq/devfreq_qos.h +++ b/include/nuttx/devfreq/devfreq_qos.h @@ -41,6 +41,9 @@ struct qos_request_s { struct plist_node min_req; struct plist_node max_req; +#ifdef CONFIG_DEVFREQ_PROCFS_QOS + int backtrace; +#endif }; struct qos_constraints_s From 65886921f1ac8b1030da59759c4f8ac519c9f2d9 Mon Sep 17 00:00:00 2001 From: guanyi3 Date: Tue, 30 Sep 2025 12:15:42 +0800 Subject: [PATCH 03/15] drivers/devfreq: remove default governor It's better not to use global governor, as modifying one device will cause all devices' governor to be modified. Signed-off-by: guanyi3 --- drivers/devfreq/Kconfig | 21 --------------------- drivers/devfreq/devfreq.c | 11 ++++++----- drivers/devfreq/devfreq_performance.c | 6 +----- drivers/devfreq/devfreq_powersave.c | 6 +----- include/nuttx/devfreq.h | 9 +++------ 5 files changed, 11 insertions(+), 42 deletions(-) diff --git a/drivers/devfreq/Kconfig b/drivers/devfreq/Kconfig index cd0b562913f9c..509d6238d2dba 100644 --- a/drivers/devfreq/Kconfig +++ b/drivers/devfreq/Kconfig @@ -12,27 +12,6 @@ config DEVFREQ if DEVFREQ -choice - prompt "DEVFREQ_DEFAULT_GOVERNOR" - default DEVFREQ_DEFAULT_GOV_PERFORMANCE - -config DEVFREQ_DEFAULT_GOV_PERFORMANCE - bool "devfreq_performance" - ---help--- - devfreq performance governor, always choose the highest frequency - -config DEVFREQ_DEFAULT_GOV_POWERSAVE - bool "devfreq_powersave" - ---help--- - devfreq powersave governor, always choose the lowest frequency - -config DEVFREQ_DEFAULT_GOV_PASSIVE - bool "devfreq_passive" - ---help--- - devfreq passive governor, a device-defined governor - -endchoice - config DEVFREQ_PROCFS bool "devfreq_procfs" default n diff --git a/drivers/devfreq/devfreq.c b/drivers/devfreq/devfreq.c index c2116315b5ce6..28eaa2b60965e 100644 --- a/drivers/devfreq/devfreq.c +++ b/drivers/devfreq/devfreq.c @@ -485,6 +485,9 @@ FAR struct devfreq_s *devfreq_register( devfreq->priv = priv; devfreq->suspended = false; devfreq->freq_table = driver->get_table(devfreq); + devfreq->min = 0; + devfreq->max = UINT32_MAX; + devfreq->cur = driver->get_frequency(devfreq); if (!devfreq->freq_table) { goto out; @@ -497,13 +500,11 @@ FAR struct devfreq_s *devfreq_register( if (!governor) { - devfreq->governor = devfreq_default_governor(); - } - else - { - devfreq->governor = governor; + goto out; } + devfreq->governor = governor; + if (devfreq_init_governor(devfreq) < 0) { goto out; diff --git a/drivers/devfreq/devfreq_performance.c b/drivers/devfreq/devfreq_performance.c index 5e582607f9ea2..1b39ce3341c68 100644 --- a/drivers/devfreq/devfreq_performance.c +++ b/drivers/devfreq/devfreq_performance.c @@ -24,8 +24,6 @@ #include -#ifdef CONFIG_DEVFREQ_DEFAULT_GOV_PERFORMANCE - /**************************************************************************** * Private Function Prototypes ****************************************************************************/ @@ -55,9 +53,7 @@ static uint32_t devfreq_performance_limit(FAR struct devfreq_s *devfreq) * Public Functions ****************************************************************************/ -FAR struct devfreq_governor_s *devfreq_default_governor(void) +FAR struct devfreq_governor_s *devfreq_performance(void) { return &g_devfreq_gov_performance; } - -#endif /* CONFIG_DEVFREQ_DEFAULT_GOV_PERFORMANCE */ diff --git a/drivers/devfreq/devfreq_powersave.c b/drivers/devfreq/devfreq_powersave.c index bc4e991dd1243..6d272c1753a20 100644 --- a/drivers/devfreq/devfreq_powersave.c +++ b/drivers/devfreq/devfreq_powersave.c @@ -24,8 +24,6 @@ #include -#ifdef CONFIG_DEVFREQ_DEFAULT_GOV_POWERSAVE - /**************************************************************************** * Private Function Prototypes ****************************************************************************/ @@ -55,9 +53,7 @@ static uint32_t devfreq_powersave_limit(FAR struct devfreq_s *devfreq) * Public Functions ****************************************************************************/ -FAR struct devfreq_governor_s *devfreq_default_governor(void) +FAR struct devfreq_governor_s *devfreq_powersave(void) { return &g_devfreq_gov_powersave; } - -#endif /* CONFIG_DEVFREQ_DEFAULT_GOV_POWERSAVE */ diff --git a/include/nuttx/devfreq.h b/include/nuttx/devfreq.h index 2487a05bf1e91..426d3fb585f1b 100644 --- a/include/nuttx/devfreq.h +++ b/include/nuttx/devfreq.h @@ -222,7 +222,7 @@ int devfreq_unregister_notifier(FAR struct devfreq_s *devfreq, FAR struct notifier_block *nb); /**************************************************************************** - * Name: devfreq_get + * Name: devfreq_get_frequency * * Description: * get the current device frequency (in kHz) @@ -342,11 +342,8 @@ FAR struct devfreq_s *devfreq_find_by_index(size_t index); void devfreq_procfs_initialize(void); -#ifdef CONFIG_DEVFREQ_DEFAULT_GOV_PASSIVE -#define devfreq_default_governor() NULL -#else -FAR struct devfreq_governor_s *devfreq_default_governor(void); -#endif +FAR struct devfreq_governor_s *devfreq_performance(void); +FAR struct devfreq_governor_s *devfreq_powersave(void); #undef EXTERN #if defined(__cplusplus) From e1f0dfda4249b72f2a25e35739cf10f59ca4cbee Mon Sep 17 00:00:00 2001 From: guanyi3 Date: Thu, 9 Oct 2025 17:50:12 +0800 Subject: [PATCH 04/15] drivers/devfreq: add const to devfreq_governor_s and devfreq_driver_s we do not hope the governor and driver in devfreq to be modified. Signed-off-by: guanyi3 --- drivers/devfreq/devfreq.c | 6 +++--- include/nuttx/devfreq.h | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/devfreq/devfreq.c b/drivers/devfreq/devfreq.c index 28eaa2b60965e..ee9a28de5d93a 100644 --- a/drivers/devfreq/devfreq.c +++ b/drivers/devfreq/devfreq.c @@ -458,9 +458,9 @@ static int devfreq_driver_target(FAR struct devfreq_s *devfreq, ****************************************************************************/ FAR struct devfreq_s *devfreq_register( - const char *name, - FAR struct devfreq_governor_s *governor, - FAR struct devfreq_driver_s *driver, + FAR const char *name, + FAR const struct devfreq_governor_s *governor, + FAR const struct devfreq_driver_s *driver, FAR void *priv) { FAR struct devfreq_s *devfreq = devfreq_find_by_name(name); diff --git a/include/nuttx/devfreq.h b/include/nuttx/devfreq.h index 426d3fb585f1b..79fd6ca441e1d 100644 --- a/include/nuttx/devfreq.h +++ b/include/nuttx/devfreq.h @@ -54,8 +54,8 @@ struct devfreq_s char name[NAME_MAX]; struct list_node node; - FAR struct devfreq_governor_s *governor; - FAR struct devfreq_driver_s *driver; + FAR const struct devfreq_governor_s *governor; + FAR const struct devfreq_driver_s *driver; FAR const uint32_t *freq_table; @@ -133,8 +133,8 @@ extern "C" FAR struct devfreq_s *devfreq_register( FAR const char *name, - FAR struct devfreq_governor_s *governor, - FAR struct devfreq_driver_s *driver, + FAR const struct devfreq_governor_s *governor, + FAR const struct devfreq_driver_s *driver, FAR void *priv); /**************************************************************************** From 235fbca936728e24245cb64c94e2c7ecb0643427 Mon Sep 17 00:00:00 2001 From: guanyi3 Date: Sat, 8 Aug 2026 11:35:35 +0800 Subject: [PATCH 05/15] drivers/devfreq: add ondemand governor 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 --- drivers/devfreq/devfreq_ondemand.c | 209 +++++++++++++++++++++++++++++ 1 file changed, 209 insertions(+) create mode 100644 drivers/devfreq/devfreq_ondemand.c diff --git a/drivers/devfreq/devfreq_ondemand.c b/drivers/devfreq/devfreq_ondemand.c new file mode 100644 index 0000000000000..7c753c7846604 --- /dev/null +++ b/drivers/devfreq/devfreq_ondemand.c @@ -0,0 +1,209 @@ +/**************************************************************************** + * drivers/devfreq/devfreq_ondemand.c + * + * 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. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define DEVFREQ_MIN_SAMPLING_INTERVAL (2 * USEC_PER_TICK) +#define DEVFREQ_LOAD_THRESHOLD_MAX (100) + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct devfreq_ondemand_s +{ + struct work_s work; + uint32_t threshold; + uint32_t sample_rate; + uint32_t target_freq; + FAR struct qos_request_s *req; +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static int devfreq_gov_ondemand_init(FAR struct devfreq_s *dev); +static int devfreq_gov_ondemand_exit(FAR struct devfreq_s *dev); +static int devfreq_gov_ondemand_start(FAR struct devfreq_s *dev); +static void devfreq_gov_ondemand_stop(FAR struct devfreq_s *dev); +static uint32_t devfreq_gov_ondemand_limit(FAR struct devfreq_s *dev); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static struct devfreq_governor_s g_devfreq_gov_ondemand = +{ + .name = "ondemand", + .init = devfreq_gov_ondemand_init, + .exit = devfreq_gov_ondemand_exit, + .start = devfreq_gov_ondemand_start, + .stop = devfreq_gov_ondemand_stop, + .limit = devfreq_gov_ondemand_limit, +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static uint32_t devfreq_gov_ondemand_cpuload(void) +{ + struct cpuload_s loadavg; + uint32_t idleload = 0; + int cpu; + + for (cpu = 0; cpu < CONFIG_SMP_NCPUS; cpu++) + { + clock_cpuload(cpu, &loadavg); + idleload += loadavg.active * 100 / loadavg.total; + } + + return 100 - idleload; +} + +static void devfreq_ondemand_worker(FAR void *arg) +{ + FAR struct devfreq_s *dev = arg; + FAR struct devfreq_ondemand_s *data = dev->governor_data; + uint32_t cpuload; + + cpuload = devfreq_gov_ondemand_cpuload(); + nxmutex_lock(&dev->lock); + if (cpuload > CONFIG_DEVFREQ_LOAD_THRESHOLD) + { + if (dev->cur < dev->max) + { + data->target_freq = dev->max; + } + else + { + data->target_freq = dev->cur; + } + } + else + { + data->target_freq = dev->min + cpuload * + (dev->max - dev->min) / 100; + } + + nxmutex_unlock(&dev->lock); + + devfreq_qos_update_request(dev, data->req, dev->min, dev->max); + work_queue(HPWORK, + &data->work, + devfreq_ondemand_worker, + dev, + data->sample_rate / USEC_PER_TICK); +} + +static int devfreq_gov_ondemand_init(FAR struct devfreq_s *dev) +{ + FAR struct devfreq_ondemand_s *data; + + data = kmm_zalloc(sizeof(struct devfreq_ondemand_s)); + if (!data) + { + return -ENOMEM; + } + + data->req = devfreq_qos_add_request(dev, dev->min, dev->max); + data->threshold = MIN(DEVFREQ_LOAD_THRESHOLD_MAX, + CONFIG_DEVFREQ_LOAD_THRESHOLD); + data->sample_rate = MAX(DEVFREQ_MIN_SAMPLING_INTERVAL, + CONFIG_DEVFREQ_SAMPLE_RATE); + dev->governor_data = data; + return 0; +} + +static int devfreq_gov_ondemand_exit(FAR struct devfreq_s *dev) +{ + FAR struct devfreq_ondemand_s *data = dev->governor_data; + + devfreq_qos_remove_request(dev, data->req); + + kmm_free(data); + return 0; +} + +static int devfreq_gov_ondemand_start(FAR struct devfreq_s *dev) +{ + FAR struct devfreq_ondemand_s *data = dev->governor_data; + + work_queue(HPWORK, + &data->work, + devfreq_ondemand_worker, + dev, + 0); + return 0; +} + +static void devfreq_gov_ondemand_stop(FAR struct devfreq_s *dev) +{ + FAR struct devfreq_ondemand_s *data = dev->governor_data; + + if (sched_idletask()) + { + work_cancel(HPWORK, &data->work); + } + else + { + work_cancel_sync(HPWORK, &data->work); + } +} + +static uint32_t devfreq_gov_ondemand_limit(FAR struct devfreq_s *dev) +{ + FAR struct devfreq_ondemand_s *data = dev->governor_data; + uint32_t freq = data->target_freq; + + if (freq > dev->max) + { + freq = dev->max; + } + + if (freq < dev->min) + { + freq = dev->min; + } + + return freq; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +FAR struct devfreq_governor_s *devfreq_ondemand(void) +{ + return &g_devfreq_gov_ondemand; +} From ebdae3cd0f5f7ff88e6eee80fe8895c76e9f426f Mon Sep 17 00:00:00 2001 From: guanyi3 Date: Fri, 14 Nov 2025 16:12:56 +0800 Subject: [PATCH 06/15] drivers/devfreq: ondemand should init governor_data before use it 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 --- drivers/devfreq/devfreq_ondemand.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/devfreq/devfreq_ondemand.c b/drivers/devfreq/devfreq_ondemand.c index 7c753c7846604..0c1f3a8195402 100644 --- a/drivers/devfreq/devfreq_ondemand.c +++ b/drivers/devfreq/devfreq_ondemand.c @@ -136,12 +136,12 @@ static int devfreq_gov_ondemand_init(FAR struct devfreq_s *dev) return -ENOMEM; } - data->req = devfreq_qos_add_request(dev, dev->min, dev->max); data->threshold = MIN(DEVFREQ_LOAD_THRESHOLD_MAX, CONFIG_DEVFREQ_LOAD_THRESHOLD); data->sample_rate = MAX(DEVFREQ_MIN_SAMPLING_INTERVAL, CONFIG_DEVFREQ_SAMPLE_RATE); dev->governor_data = data; + data->req = devfreq_qos_add_request(dev, dev->min, dev->max); return 0; } From 4136679861d7816eaddc4b7ed7b0580192bbf659 Mon Sep 17 00:00:00 2001 From: guanyi3 Date: Mon, 17 Nov 2025 11:57:26 +0800 Subject: [PATCH 07/15] drivers/devfreq: replace mutex to spinlock 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 --- drivers/devfreq/devfreq.c | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/drivers/devfreq/devfreq.c b/drivers/devfreq/devfreq.c index ee9a28de5d93a..906fac4c5c654 100644 --- a/drivers/devfreq/devfreq.c +++ b/drivers/devfreq/devfreq.c @@ -27,6 +27,7 @@ #include #include #include +#include /**************************************************************************** * Pre-processor Definitions @@ -37,7 +38,7 @@ ****************************************************************************/ static struct list_node g_devfreq_list = LIST_INITIAL_VALUE(g_devfreq_list); -static mutex_t g_devfreq_list_lock = NXMUTEX_INITIALIZER; +static spinlock_t g_devfreq_list_lock = SP_UNLOCKED; /**************************************************************************** * Private Function Prototypes @@ -464,6 +465,7 @@ FAR struct devfreq_s *devfreq_register( FAR void *priv) { FAR struct devfreq_s *devfreq = devfreq_find_by_name(name); + irqstate_t flags; if (devfreq || !driver) { @@ -512,9 +514,9 @@ FAR struct devfreq_s *devfreq_register( devfreq_start_governor(devfreq); - nxmutex_lock(&g_devfreq_list_lock); + flags = spin_lock_irqsave(&g_devfreq_list_lock); list_add_tail(&g_devfreq_list, &devfreq->node); - nxmutex_unlock(&g_devfreq_list_lock); + spin_unlock_irqrestore(&g_devfreq_list_lock, flags); return devfreq; @@ -541,14 +543,16 @@ FAR struct devfreq_s *devfreq_register( int devfreq_unregister(FAR struct devfreq_s *devfreq) { + irqstate_t flags; + if (!devfreq) { return -EINVAL; } - nxmutex_lock(&g_devfreq_list_lock); + flags = spin_lock_irqsave(&g_devfreq_list_lock); list_delete(&devfreq->node); - nxmutex_unlock(&g_devfreq_list_lock); + spin_unlock_irqrestore(&g_devfreq_list_lock, flags); devfreq_stop_governor(devfreq); devfreq_exit_governor(devfreq); @@ -836,24 +840,25 @@ int devfreq_qos_remove_request(FAR struct devfreq_s *devfreq, FAR struct devfreq_s *devfreq_find_by_name(FAR const char *name) { FAR struct devfreq_s *devfreq; + irqstate_t flags; if (!name) { return NULL; } - nxmutex_lock(&g_devfreq_list_lock); + flags = spin_lock_irqsave(&g_devfreq_list_lock); list_for_every_entry(&g_devfreq_list, devfreq, struct devfreq_s, node) { if (!strcmp(devfreq->name, name)) { - nxmutex_unlock(&g_devfreq_list_lock); + spin_unlock_irqrestore(&g_devfreq_list_lock, flags); return devfreq; } } - nxmutex_unlock(&g_devfreq_list_lock); + spin_unlock_irqrestore(&g_devfreq_list_lock, flags); return NULL; } @@ -874,19 +879,20 @@ FAR struct devfreq_s *devfreq_find_by_name(FAR const char *name) FAR struct devfreq_s *devfreq_find_by_index(size_t index) { FAR struct devfreq_s *devfreq; + irqstate_t flags; size_t i = 0; - nxmutex_lock(&g_devfreq_list_lock); + flags = spin_lock_irqsave(&g_devfreq_list_lock); list_for_every_entry(&g_devfreq_list, devfreq, struct devfreq_s, node) { if (index == i++) { - nxmutex_unlock(&g_devfreq_list_lock); + spin_unlock_irqrestore(&g_devfreq_list_lock, flags); return devfreq; } } - nxmutex_unlock(&g_devfreq_list_lock); + spin_unlock_irqrestore(&g_devfreq_list_lock, flags); return NULL; } From 49fa30b4fd31529bb74fa147062bb5de49128708 Mon Sep 17 00:00:00 2001 From: guanyi3 Date: Tue, 17 Mar 2026 21:31:03 +0800 Subject: [PATCH 08/15] devfreq/procfs: add write support for frequency QoS constraints Add the ability to set frequency constraints via procfs write. Supported formats: echo , > /proc/devfreq/ - set frequency range echo 0,0 > /proc/devfreq/ - 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 (cherry picked from commit 70ae195c84f35a4d0b85fcc14187989b60fc0280) --- drivers/devfreq/devfreq_procfs.c | 76 +++++++++++++++++++++++++++++++- include/nuttx/devfreq.h | 4 ++ 2 files changed, 79 insertions(+), 1 deletion(-) diff --git a/drivers/devfreq/devfreq_procfs.c b/drivers/devfreq/devfreq_procfs.c index 700e1a960aa22..bf1fe7b09be46 100644 --- a/drivers/devfreq/devfreq_procfs.c +++ b/drivers/devfreq/devfreq_procfs.c @@ -239,11 +239,84 @@ static ssize_t devfreq_read(FAR struct file *filep, /**************************************************************************** * Name: devfreq_write + * + * Description: + * Handle write to devfreq procfs entry. + * Format: " " in kHz. + * This creates or updates a QoS request to constrain the frequency. + * Write "0 0" to remove the QoS constraint. + * ****************************************************************************/ static ssize_t devfreq_write(FAR struct file *filep, FAR const char *buffer, size_t buflen) { + FAR struct devfreq_procfs_s *devfreq_procfs = filep->f_priv; + FAR struct devfreq_s *devfreq = devfreq_procfs->devfreq; + uint32_t min_freq; + uint32_t max_freq; + FAR char *endptr; + char tmp[32]; + int ret; + + if (buflen == 0 || buflen >= sizeof(tmp)) + { + return -EINVAL; + } + + memcpy(tmp, buffer, buflen); + tmp[buflen] = '\0'; + + min_freq = strtoul(tmp, &endptr, 10); + if (endptr == tmp) + { + return buflen; + } + + if (*endptr == ',' || *endptr == ' ') + { + endptr++; + } + + max_freq = strtoul(endptr, &endptr, 10); + + /* Write "0 0" to remove the QoS constraint */ + + if (min_freq == 0 && max_freq == 0) + { + if (devfreq->procfs_qos) + { + devfreq_qos_remove_request(devfreq, devfreq->procfs_qos); + devfreq->procfs_qos = NULL; + } + + return buflen; + } + + if (min_freq > max_freq) + { + return -EINVAL; + } + + if (devfreq->procfs_qos) + { + ret = devfreq_qos_update_request(devfreq, devfreq->procfs_qos, + min_freq, max_freq); + if (ret < 0) + { + return ret; + } + } + else + { + devfreq->procfs_qos = devfreq_qos_add_request(devfreq, + min_freq, max_freq); + if (!devfreq->procfs_qos) + { + return -ENOMEM; + } + } + return buflen; } @@ -376,7 +449,8 @@ static int devfreq_stat(FAR const char *relpath, FAR struct stat *buf) return -ENOENT; } - buf->st_mode = S_IFREG | S_IROTH | S_IRGRP | S_IRUSR; + buf->st_mode = S_IFREG | S_IROTH | S_IRGRP | S_IRUSR | + S_IWOTH | S_IWGRP | S_IWUSR; } return 0; diff --git a/include/nuttx/devfreq.h b/include/nuttx/devfreq.h index 79fd6ca441e1d..5189e6e1895c5 100644 --- a/include/nuttx/devfreq.h +++ b/include/nuttx/devfreq.h @@ -72,6 +72,10 @@ struct devfreq_s mutex_t lock; FAR void *priv; + +#ifdef CONFIG_DEVFREQ_PROCFS + FAR struct qos_request_s *procfs_qos; +#endif }; struct devfreq_governor_s From d9ea877052bf6fa972936f373896a2b321c4752e Mon Sep 17 00:00:00 2001 From: guanyi3 Date: Fri, 6 Mar 2026 11:31:51 +0800 Subject: [PATCH 09/15] drivers/devfreq: add conflict_policy to devfreq_driver_s 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 --- drivers/devfreq/devfreq.c | 9 ++++++++- include/nuttx/devfreq.h | 6 ++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/drivers/devfreq/devfreq.c b/drivers/devfreq/devfreq.c index 906fac4c5c654..5fad5626549ab 100644 --- a/drivers/devfreq/devfreq.c +++ b/drivers/devfreq/devfreq.c @@ -355,7 +355,14 @@ static void devfreq_refresh_limit(FAR struct devfreq_s *devfreq) if (min > max) { - min = max; + if (devfreq->driver->conflict_policy == DEVFREQ_CONFLICT_PREFER_HIGH) + { + max = min; + } + else + { + min = max; + } } idx = devfreq_table_find_freq(devfreq, min, DEVFREQ_RELATION_L); diff --git a/include/nuttx/devfreq.h b/include/nuttx/devfreq.h index 5189e6e1895c5..e17044be4ec23 100644 --- a/include/nuttx/devfreq.h +++ b/include/nuttx/devfreq.h @@ -45,6 +45,11 @@ #define DEVFREQ_RELATION_L 0 /* lowest frequency at or above target */ #define DEVFREQ_RELATION_H 1 /* highest frequency below or at target */ +/* QoS conflict policy when min > max (no overlap between requests) */ + +#define DEVFREQ_CONFLICT_PREFER_HIGH 0 /* clamp to min, choose higher freq */ +#define DEVFREQ_CONFLICT_PREFER_LOW 1 /* clamp to max, choose lower freq */ + /**************************************************************************** * Public Types ****************************************************************************/ @@ -90,6 +95,7 @@ struct devfreq_governor_s struct devfreq_driver_s { + int conflict_policy; /* DEVFREQ_CONFLICT_PREFER_HIGH or LOW */ CODE FAR const uint32_t * (*get_table)(FAR struct devfreq_s *devfreq); CODE int (*target_index)(FAR struct devfreq_s *devfreq, From 6b31fe2301dacb0b1beeb800e0421295b185845e Mon Sep 17 00:00:00 2001 From: guanyi3 Date: Tue, 10 Mar 2026 11:52:54 +0800 Subject: [PATCH 10/15] devfreq/ondemand: fix use-after-free in ondemand worker 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 --- drivers/devfreq/devfreq.c | 31 ++++++++++++++++++--------- drivers/devfreq/devfreq_ondemand.c | 34 +++++++++++++++++++++++++++--- 2 files changed, 52 insertions(+), 13 deletions(-) diff --git a/drivers/devfreq/devfreq.c b/drivers/devfreq/devfreq.c index 5fad5626549ab..bf152c73ec843 100644 --- a/drivers/devfreq/devfreq.c +++ b/drivers/devfreq/devfreq.c @@ -173,7 +173,7 @@ static int devfreq_start_governor(FAR struct devfreq_s *devfreq) static void devfreq_stop_governor(FAR struct devfreq_s *devfreq) { - if (devfreq->suspended || !devfreq->governor) + if (!devfreq->governor) { return; } @@ -587,21 +587,29 @@ int devfreq_unregister(FAR struct devfreq_s *devfreq) int devfreq_suspend(FAR struct devfreq_s *devfreq) { nxmutex_lock(&devfreq->lock); + devfreq->suspended = true; + nxmutex_unlock(&devfreq->lock); devfreq_stop_governor(devfreq); if (devfreq->driver->suspend) { - int ret = devfreq->driver->suspend(devfreq); + int ret; + + nxmutex_lock(&devfreq->lock); + ret = devfreq->driver->suspend(devfreq); + nxmutex_unlock(&devfreq->lock); + if (ret < 0) { + nxmutex_lock(&devfreq->lock); + devfreq->suspended = false; nxmutex_unlock(&devfreq->lock); + devfreq_start_governor(devfreq); return ret; } } - devfreq->suspended = true; - nxmutex_unlock(&devfreq->lock); return 0; } @@ -621,22 +629,25 @@ int devfreq_suspend(FAR struct devfreq_s *devfreq) int devfreq_resume(struct devfreq_s *devfreq) { - nxmutex_lock(&devfreq->lock); - if (devfreq->driver->resume) { - int ret = devfreq->driver->resume(devfreq); + int ret; + + nxmutex_lock(&devfreq->lock); + ret = devfreq->driver->resume(devfreq); + nxmutex_unlock(&devfreq->lock); + if (ret < 0) { - nxmutex_unlock(&devfreq->lock); return ret; } } + nxmutex_lock(&devfreq->lock); devfreq->suspended = false; - devfreq_start_governor(devfreq); - nxmutex_unlock(&devfreq->lock); + + devfreq_start_governor(devfreq); return 0; } diff --git a/drivers/devfreq/devfreq_ondemand.c b/drivers/devfreq/devfreq_ondemand.c index 0c1f3a8195402..a5b3e52404667 100644 --- a/drivers/devfreq/devfreq_ondemand.c +++ b/drivers/devfreq/devfreq_ondemand.c @@ -94,11 +94,19 @@ static uint32_t devfreq_gov_ondemand_cpuload(void) static void devfreq_ondemand_worker(FAR void *arg) { FAR struct devfreq_s *dev = arg; - FAR struct devfreq_ondemand_s *data = dev->governor_data; + FAR struct devfreq_ondemand_s *data; + FAR struct qos_request_s *req; uint32_t cpuload; cpuload = devfreq_gov_ondemand_cpuload(); nxmutex_lock(&dev->lock); + data = dev->governor_data; + if (data == NULL) + { + nxmutex_unlock(&dev->lock); + return; + } + if (cpuload > CONFIG_DEVFREQ_LOAD_THRESHOLD) { if (dev->cur < dev->max) @@ -116,14 +124,21 @@ static void devfreq_ondemand_worker(FAR void *arg) (dev->max - dev->min) / 100; } - nxmutex_unlock(&dev->lock); + /* Re-queue before releasing the lock so that exit/stop can + * reliably cancel the pending work after setting governor_data + * to NULL. All accesses to 'data' must happen while holding + * the lock to avoid use-after-free. + */ - devfreq_qos_update_request(dev, data->req, dev->min, dev->max); + req = data->req; work_queue(HPWORK, &data->work, devfreq_ondemand_worker, dev, data->sample_rate / USEC_PER_TICK); + nxmutex_unlock(&dev->lock); + + devfreq_qos_update_request(dev, req, dev->min, dev->max); } static int devfreq_gov_ondemand_init(FAR struct devfreq_s *dev) @@ -151,6 +166,19 @@ static int devfreq_gov_ondemand_exit(FAR struct devfreq_s *dev) devfreq_qos_remove_request(dev, data->req); + /* First, mark governor_data as NULL so that any in-flight worker + * will see it and bail out without re-queuing. + */ + + nxmutex_lock(&dev->lock); + dev->governor_data = NULL; + nxmutex_unlock(&dev->lock); + + /* Cancel any pending work that was queued before we cleared + * governor_data, then it is safe to free. + */ + + work_cancel_sync(HPWORK, &data->work); kmm_free(data); return 0; } From e5d5668d1877e3fdc4e712b4cf277f9c497ba0df Mon Sep 17 00:00:00 2001 From: guanyi3 Date: Fri, 6 Mar 2026 14:17:58 +0800 Subject: [PATCH 11/15] drivers/devfreq: use hardware frequency instead of cached value in driver_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 --- drivers/devfreq/devfreq.c | 20 +++++++++++++++----- drivers/devfreq/devfreq_ondemand.c | 5 +++-- drivers/devfreq/devfreq_procfs.c | 2 +- include/nuttx/devfreq.h | 1 - 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/drivers/devfreq/devfreq.c b/drivers/devfreq/devfreq.c index bf152c73ec843..5b0a17c046585 100644 --- a/drivers/devfreq/devfreq.c +++ b/drivers/devfreq/devfreq.c @@ -401,6 +401,7 @@ static int devfreq_driver_target(FAR struct devfreq_s *devfreq, int relation) { struct devfreq_notifier_s freq; + uint32_t cur_freq; ssize_t idx; int ret; @@ -416,12 +417,18 @@ static int devfreq_driver_target(FAR struct devfreq_s *devfreq, } target_freq = devfreq->freq_table[idx]; - if (target_freq == devfreq->cur) + + /* Get current hardware frequency to check if transition is needed, + * and to record the old frequency for notifier chain. + */ + + cur_freq = devfreq_get_frequency(devfreq); + if (target_freq == cur_freq) { return 0; } - freq.old = devfreq->cur; + freq.old = cur_freq; freq.new = target_freq; blocking_notifier_call_chain(&devfreq->notifier_list, @@ -431,8 +438,13 @@ static int devfreq_driver_target(FAR struct devfreq_s *devfreq, DEVFREQ_POSTCHANGE, &freq); if (ret < 0) { + /* Frequency transition failed. Re-read the actual hardware frequency + * and send a compensating PRECHANGE/POSTCHANGE pair so that all + * notifier listeners stay in sync with the real hardware state. + */ + freq.old = target_freq; - freq.new = devfreq->cur; + freq.new = devfreq_get_frequency(devfreq); blocking_notifier_call_chain(&devfreq->notifier_list, DEVFREQ_PRECHANGE, &freq); blocking_notifier_call_chain(&devfreq->notifier_list, @@ -440,7 +452,6 @@ static int devfreq_driver_target(FAR struct devfreq_s *devfreq, return ret; } - devfreq->cur = target_freq; return 0; } @@ -496,7 +507,6 @@ FAR struct devfreq_s *devfreq_register( devfreq->freq_table = driver->get_table(devfreq); devfreq->min = 0; devfreq->max = UINT32_MAX; - devfreq->cur = driver->get_frequency(devfreq); if (!devfreq->freq_table) { goto out; diff --git a/drivers/devfreq/devfreq_ondemand.c b/drivers/devfreq/devfreq_ondemand.c index a5b3e52404667..b6557ee38d27f 100644 --- a/drivers/devfreq/devfreq_ondemand.c +++ b/drivers/devfreq/devfreq_ondemand.c @@ -109,13 +109,14 @@ static void devfreq_ondemand_worker(FAR void *arg) if (cpuload > CONFIG_DEVFREQ_LOAD_THRESHOLD) { - if (dev->cur < dev->max) + uint32_t cur_freq = devfreq_get_frequency(dev); + if (cur_freq < dev->max) { data->target_freq = dev->max; } else { - data->target_freq = dev->cur; + data->target_freq = cur_freq; } } else diff --git a/drivers/devfreq/devfreq_procfs.c b/drivers/devfreq/devfreq_procfs.c index bf1fe7b09be46..99f92d50ac25f 100644 --- a/drivers/devfreq/devfreq_procfs.c +++ b/drivers/devfreq/devfreq_procfs.c @@ -184,7 +184,7 @@ static ssize_t devfreq_read(FAR struct file *filep, " suspended: %s\n", devfreq->name, devfreq->governor->name, - devfreq->cur, + devfreq_get_frequency(devfreq), devfreq->suspended ? "True" : "False"); if (devfreq->freq_table) diff --git a/include/nuttx/devfreq.h b/include/nuttx/devfreq.h index e17044be4ec23..13c28d1787b69 100644 --- a/include/nuttx/devfreq.h +++ b/include/nuttx/devfreq.h @@ -70,7 +70,6 @@ struct devfreq_s uint32_t min; /* in kHz */ uint32_t max; /* in kHz */ - uint32_t cur; /* in kHz */ bool suspended; From 6d9ac0b89101810c499c01c15e605d96dfae888b Mon Sep 17 00:00:00 2001 From: guanyi3 Date: Fri, 6 Mar 2026 16:26:00 +0800 Subject: [PATCH 12/15] drivers/devfreq: fix qos_get_value returning wrong min/max aggregation 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 --- drivers/devfreq/devfreq_qos.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/devfreq/devfreq_qos.c b/drivers/devfreq/devfreq_qos.c index 501f30e3d63e0..9c0a34f453a0c 100644 --- a/drivers/devfreq/devfreq_qos.c +++ b/drivers/devfreq/devfreq_qos.c @@ -191,12 +191,12 @@ uint32_t qos_get_value(FAR struct qos_constraints_s *constraints, { case QOS_REQ_MIN: { - return plist_first(&constraints->min_requests)->prio; + return plist_last(&constraints->min_requests)->prio; } case QOS_REQ_MAX: { - return plist_last(&constraints->max_requests)->prio; + return plist_first(&constraints->max_requests)->prio; } } From b3237c2aa2150e5aff694d78ffd5edbdfcec13b2 Mon Sep 17 00:00:00 2001 From: guanyi3 Date: Fri, 13 Mar 2026 16:41:23 +0800 Subject: [PATCH 13/15] drivers/devfreq: guard backtrace code with CONFIG_LIBC_BACKTRACE_DEPTH 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 --- drivers/devfreq/devfreq_procfs.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/devfreq/devfreq_procfs.c b/drivers/devfreq/devfreq_procfs.c index 99f92d50ac25f..d97b2f3deb124 100644 --- a/drivers/devfreq/devfreq_procfs.c +++ b/drivers/devfreq/devfreq_procfs.c @@ -169,8 +169,10 @@ static ssize_t devfreq_read(FAR struct file *filep, FAR struct devfreq_s *devfreq = devfreq_procfs->devfreq; #ifdef CONFIG_DEVFREQ_PROCFS_QOS FAR struct qos_request_s *qos; - void **stack; +#if defined(CONFIG_LIBC_BACKTRACE_BUFFSIZE) && CONFIG_LIBC_BACKTRACE_BUFFSIZE > 0 + FAR void **stack; int depth; +#endif #endif off_t offset = filep->f_pos; size_t i; @@ -209,14 +211,16 @@ static ssize_t devfreq_read(FAR struct file *filep, " qos_list(min, max, backtrace):\n"); plist_for_each_entry(qos, &devfreq->constraints.min_requests, min_req) { - stack = backtrace_get(qos->backtrace, &depth); procfs_sprintf(buffer, buflen, &offset, " %"PRIu32", %"PRIu32",", qos->min_req.prio, qos->max_req.prio); +#if defined(CONFIG_LIBC_BACKTRACE_BUFFSIZE) && CONFIG_LIBC_BACKTRACE_BUFFSIZE > 0 + stack = backtrace_get(qos->backtrace, &depth); for (i = 0; i < depth; i++) { procfs_sprintf(buffer, buflen, &offset, " %p", stack[i]); } +#endif procfs_sprintf(buffer, buflen, &offset, "\n"); } From a0b00fa32d52a5fa4414e4002ddc9e1f068fd439 Mon Sep 17 00:00:00 2001 From: guanyi3 Date: Sat, 8 Aug 2026 11:37:55 +0800 Subject: [PATCH 14/15] drivers/devfreq: add ondemand governor build support 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 --- drivers/devfreq/CMakeLists.txt | 4 ++++ drivers/devfreq/Kconfig | 23 +++++++++++++++++++++++ drivers/devfreq/Make.defs | 6 ++++++ 3 files changed, 33 insertions(+) diff --git a/drivers/devfreq/CMakeLists.txt b/drivers/devfreq/CMakeLists.txt index 9fb34381e49b6..8ae171fb02cdf 100644 --- a/drivers/devfreq/CMakeLists.txt +++ b/drivers/devfreq/CMakeLists.txt @@ -25,5 +25,9 @@ if(CONFIG_DEVFREQ) list(APPEND SRCS devfreq_procfs.c) endif() + if(CONFIG_DEVFREQ_GOV_ONDEMAND) + list(APPEND SRCS devfreq_ondemand.c) + endif() + target_sources(drivers PRIVATE ${SRCS}) endif() diff --git a/drivers/devfreq/Kconfig b/drivers/devfreq/Kconfig index 509d6238d2dba..b503100adf7fa 100644 --- a/drivers/devfreq/Kconfig +++ b/drivers/devfreq/Kconfig @@ -27,4 +27,27 @@ config DEVFREQ_PROCFS_QOS ---help--- devfreq procfs show qos requests and their callers +config DEVFREQ_GOV_ONDEMAND + bool "devfreq_gov_ondemand" + default n + depends on !SCHED_CPULOAD_NONE + ---help--- + devfreq_ondemand governor + +if DEVFREQ_GOV_ONDEMAND + +config DEVFREQ_SAMPLE_RATE + int "the default sample rate (us) to get the cpuload" + default 1000000 + ---help--- + Ondemand sample rate + +config DEVFREQ_LOAD_THRESHOLD + int "the default cpu load threshold up to max cpu freq" + default 80 + ---help--- + Ondemand cpu load threshold + +endif + endif diff --git a/drivers/devfreq/Make.defs b/drivers/devfreq/Make.defs index 59baad2d02382..dc63153b77c5a 100644 --- a/drivers/devfreq/Make.defs +++ b/drivers/devfreq/Make.defs @@ -30,6 +30,12 @@ CSRCS += devfreq_procfs.c endif +ifeq ($(CONFIG_DEVFREQ_GOV_ONDEMAND),y) + +CSRCS += devfreq_ondemand.c + +endif + DEPPATH += --dep-path devfreq VPATH += devfreq From 14137c5dd4526f00c9783fd30229fbf18150b6b6 Mon Sep 17 00:00:00 2001 From: guanyi3 Date: Sat, 8 Aug 2026 12:11:50 +0800 Subject: [PATCH 15/15] Documentation: add devfreq framework documentation 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 --- .../components/drivers/special/devfreq.rst | 258 ++++++++++++++++++ .../components/drivers/special/index.rst | 1 + 2 files changed, 259 insertions(+) create mode 100644 Documentation/components/drivers/special/devfreq.rst diff --git a/Documentation/components/drivers/special/devfreq.rst b/Documentation/components/drivers/special/devfreq.rst new file mode 100644 index 0000000000000..85e2f142897c1 --- /dev/null +++ b/Documentation/components/drivers/special/devfreq.rst @@ -0,0 +1,258 @@ +======================== +Device Frequency Scaling +======================== + +The device frequency framework (devfreq) lets several unrelated parts of the +system have an opinion about how fast a device 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. A governor +decides, from moment to moment, where inside the arbitrated window the device +should sit. Everything else is arbitration. + +Unlike the CPU frequency framework, which manages a single system-wide CPU +policy, devfreq manages any number of independent devices, each registered by +name. A GPU, a memory bus, and a DSP can each have their own devfreq instance, +table and governor. + +It is enabled with ``CONFIG_DEVFREQ``. + +Design +====== + +A devfreq instance is created by a driver calling ``devfreq_register()`` with +a name, a governor and a lower half. From then on two independent forces act +on the frequency: + +- **QoS requests** narrow the allowed window. Each requester installs a + ``[min, max]`` window it can live with, and the framework aggregates every + window into a single ``[min, max]`` clamp on the device. +- **The governor** picks a target inside that clamp. The ``performance`` + governor always asks for the top of the window, ``powersave`` always asks + for the bottom, and ``ondemand`` moves between them according to load. + +Whenever the set of requests changes, the framework recomputes the aggregate +window and lets the governor re-pick. The chosen frequency is then snapped to +a real table entry and applied through the lower half. + +Frequencies are expressed in kHz throughout. + +Resolving Requests +------------------ + +Each QoS request carries a ``min`` and a ``max``. The aggregate window is the +intersection of all of them: the highest ``min`` across every request, and +the lowest ``max``. A floor is honoured here, so a requester that needs a +device to run *at least* some speed can guarantee it, and a ceiling caps it. + +When the requests do not intersect (the aggregate ``min`` ends up above the +aggregate ``max``) the driver's ``conflict_policy`` decides who wins: + +- ``DEVFREQ_CONFLICT_PREFER_HIGH`` clamps to the floor and chooses the higher + frequency. A device that would rather waste power than stall picks this. +- ``DEVFREQ_CONFLICT_PREFER_LOW`` clamps to the ceiling and chooses the lower + frequency. A device protecting a thermal or power budget picks this. + +The resolved ``[min, max]`` is then snapped to the table: ``min`` rounds up to +the lowest entry at or above it, ``max`` rounds down to the highest entry at +or below it. The governor picks within that snapped range, and the lower half +is told only "go to table entry N". + +The Lower Half +============== + +A platform provides a ``struct devfreq_driver_s``. ``get_table`` and +``target_index`` are mandatory; the rest may be NULL: + +.. code-block:: c + + struct devfreq_driver_s + { + int conflict_policy; + CODE FAR const uint32_t * + (*get_table)(FAR struct devfreq_s *devfreq); + CODE int (*target_index)(FAR struct devfreq_s *devfreq, + size_t index); + CODE uint32_t (*get_frequency)(FAR struct devfreq_s *devfreq); + CODE int (*suspend)(FAR struct devfreq_s *devfreq); + CODE int (*resume)(FAR struct devfreq_s *devfreq); + }; + +``conflict_policy`` + ``DEVFREQ_CONFLICT_PREFER_HIGH`` or ``DEVFREQ_CONFLICT_PREFER_LOW``, applied + when QoS windows do not intersect, as described above. + +``get_table`` + Returns the frequency table, an array of ``uint32_t`` in kHz. It must + ascend, and it must end with an entry equal to ``DEVFREQ_ENTRY_END``. An + entry of ``DEVFREQ_ENTRY_INVALID`` is skipped, which lets a driver punch a + hole in an otherwise fixed table. + +``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 kHz. The framework consults it + rather than trusting a cached value, so an external change is noticed. + +``suspend`` and ``resume`` + Called from ``devfreq_suspend()`` and ``devfreq_resume()``. + +Register the device once its hardware is ready: + +.. code-block:: c + + static const struct devfreq_driver_s g_mydev_devfreq = + { + .conflict_policy = DEVFREQ_CONFLICT_PREFER_LOW, + .get_table = mydev_get_table, + .target_index = mydev_target_index, + .get_frequency = mydev_get_frequency, + }; + + devfreq_register("gpu", devfreq_performance(), + &g_mydev_devfreq, priv); + +``devfreq_register()`` returns a handle, or NULL on failure, including when a +device of the same name is already registered. Pass the governor you want the +device to start with; ``devfreq_performance()`` and ``devfreq_powersave()`` +return the two built-in governors, and the ondemand governor is available when +``CONFIG_DEVFREQ_GOV_ONDEMAND`` is built in. + +.. code-block:: c + + int devfreq_unregister(FAR struct devfreq_s *devfreq); + +``devfreq_unregister()`` stops the governor, tears the instance down and frees +it. + +Governors +========= + +A governor is a small ``struct devfreq_governor_s`` with lifecycle callbacks +and a ``limit`` that returns the frequency the governor currently wants. The +framework clamps that want to the QoS window before applying it. + +``performance`` + Always wants the maximum of the window. Built in. + +``powersave`` + Always wants the minimum of the window. Built in. + +``ondemand`` + Samples CPU load periodically and scales between the window's bounds. When + load crosses ``CONFIG_DEVFREQ_LOAD_THRESHOLD`` it asks for the top; + otherwise it scales proportionally. The sampling interval defaults to + ``CONFIG_DEVFREQ_SAMPLE_RATE`` microseconds. Enabled with + ``CONFIG_DEVFREQ_GOV_ONDEMAND``. + +A driver may also supply its own governor to ``devfreq_register()`` instead of +a built-in one. + +In-kernel Requests +================== + +Kernel code constrains a device's frequency through three calls: + +.. code-block:: c + + FAR struct qos_request_s *qos; + + qos = devfreq_qos_add_request(devfreq, + 200000, /* min kHz */ + 800000); /* max kHz */ + + devfreq_qos_update_request(devfreq, qos, 400000, 800000); + + devfreq_qos_remove_request(devfreq, qos); + +Each call re-resolves the window and lets the governor re-pick before +returning. ``devfreq_qos_remove_request()`` frees the request. + +The current frequency can be read at any time: + +.. code-block:: c + + uint32_t khz = devfreq_get_frequency(devfreq); + +A device is looked up by name or by index when its handle is not already held: + +.. code-block:: c + + FAR struct devfreq_s *devfreq = devfreq_find_by_name("gpu"); + +Change Notifications +==================== + +Interested code can register a notifier block to hear about every frequency +transition. The chain is called with ``DEVFREQ_PRECHANGE`` before the change +and ``DEVFREQ_POSTCHANGE`` after, each carrying a ``struct devfreq_notifier_s`` +with the old and new frequencies. If the lower half's ``target_index`` fails, +a compensating pair is sent so listeners always end on the hardware's true +state. + +.. code-block:: c + + devfreq_register_notifier(devfreq, &nb); + devfreq_unregister_notifier(devfreq, &nb); + +procfs +====== + +With ``CONFIG_DEVFREQ_PROCFS`` each registered device appears under +``/proc/devfreq/``. Reading it reports the device name, its current +governor, the current frequency, whether it is suspended, and the frequency +table: + +.. code-block:: text + + nsh> cat /proc/devfreq/gpu + devfreq: gpu + governor: ondemand + cur_freq: 400000 + suspended: False + freq_table: 200000 400000 600000 800000 + +Writing to the entry installs a frequency QoS constraint from user space, so +an application can cap or floor a device without kernel code. + +With ``CONFIG_DEVFREQ_PROCFS_QOS`` the read also lists every outstanding QoS +request as ``min, max`` pairs. When ``CONFIG_LIBC_BACKTRACE_DEPTH`` is greater +than zero, each request is annotated with the call stack that installed it, +which turns "who is holding this device down?" into a question with an answer. + +Suspend and Resume +================== + +.. code-block:: c + + devfreq_suspend(devfreq); + devfreq_resume(devfreq); + +These pass through to the lower half's ``suspend`` and ``resume`` and stop or +restart the governor. While suspended the governor does not touch the +hardware; requests are still accepted and recorded, and whatever they resolve +to takes effect on resume. + +Configuration +============= + +``CONFIG_DEVFREQ`` + Enables the framework. + +``CONFIG_DEVFREQ_PROCFS`` + Exposes each device under ``/proc/devfreq``. Requires ``CONFIG_FS_PROCFS``. + +``CONFIG_DEVFREQ_PROCFS_QOS`` + Lists outstanding QoS requests, with call stacks when backtrace is + available, in the procfs output. Requires ``CONFIG_DEVFREQ_PROCFS``. + +``CONFIG_DEVFREQ_GOV_ONDEMAND`` + Builds the ondemand governor. Requires CPU-load sampling + (``!CONFIG_SCHED_CPULOAD_NONE``). + +``CONFIG_DEVFREQ_SAMPLE_RATE`` + The ondemand governor's sampling interval, in microseconds. + +``CONFIG_DEVFREQ_LOAD_THRESHOLD`` + The load percentage at which ondemand jumps to the maximum frequency. diff --git a/Documentation/components/drivers/special/index.rst b/Documentation/components/drivers/special/index.rst index 1444d74eb9b04..f3296845c8ab8 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 + devfreq.rst devicetree.rst devmem.rst dma.rst