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 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..8ae171fb02cdf --- /dev/null +++ b/drivers/devfreq/CMakeLists.txt @@ -0,0 +1,33 @@ +# ############################################################################## +# 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) + + if(CONFIG_DEVFREQ_PROCFS) + 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 new file mode 100644 index 0000000000000..b503100adf7fa --- /dev/null +++ b/drivers/devfreq/Kconfig @@ -0,0 +1,53 @@ +# +# 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 + +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 + +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 new file mode 100644 index 0000000000000..dc63153b77c5a --- /dev/null +++ b/drivers/devfreq/Make.defs @@ -0,0 +1,42 @@ +############################################################################ +# 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 + +ifeq ($(CONFIG_DEVFREQ_PROCFS),y) + +CSRCS += devfreq_procfs.c + +endif + +ifeq ($(CONFIG_DEVFREQ_GOV_ONDEMAND),y) + +CSRCS += devfreq_ondemand.c + +endif + +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..5b0a17c046585 --- /dev/null +++ b/drivers/devfreq/devfreq.c @@ -0,0 +1,926 @@ +/**************************************************************************** + * 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 +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static struct list_node g_devfreq_list = LIST_INITIAL_VALUE(g_devfreq_list); +static spinlock_t g_devfreq_list_lock = SP_UNLOCKED; + +/**************************************************************************** + * 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->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) + { + if (devfreq->driver->conflict_policy == DEVFREQ_CONFLICT_PREFER_HIGH) + { + max = min; + } + else + { + 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; + uint32_t cur_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]; + + /* 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 = cur_freq; + 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) + { + /* 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_get_frequency(devfreq); + blocking_notifier_call_chain(&devfreq->notifier_list, + DEVFREQ_PRECHANGE, &freq); + blocking_notifier_call_chain(&devfreq->notifier_list, + DEVFREQ_POSTCHANGE, &freq); + return ret; + } + + 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( + 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); + irqstate_t flags; + + 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); + + strlcpy(devfreq->name, name, NAME_MAX); + devfreq->driver = driver; + devfreq->priv = priv; + devfreq->suspended = false; + devfreq->freq_table = driver->get_table(devfreq); + devfreq->min = 0; + devfreq->max = UINT32_MAX; + if (!devfreq->freq_table) + { + goto out; + } + + if (devfreq_table_validate(devfreq) < 0) + { + goto out; + } + + if (!governor) + { + goto out; + } + + devfreq->governor = governor; + + if (devfreq_init_governor(devfreq) < 0) + { + goto out; + } + + devfreq_start_governor(devfreq); + + flags = spin_lock_irqsave(&g_devfreq_list_lock); + list_add_tail(&g_devfreq_list, &devfreq->node); + spin_unlock_irqrestore(&g_devfreq_list_lock, flags); + + 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) +{ + irqstate_t flags; + + if (!devfreq) + { + return -EINVAL; + } + + flags = spin_lock_irqsave(&g_devfreq_list_lock); + list_delete(&devfreq->node); + spin_unlock_irqrestore(&g_devfreq_list_lock, flags); + + 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->suspended = true; + nxmutex_unlock(&devfreq->lock); + + devfreq_stop_governor(devfreq); + + if (devfreq->driver->suspend) + { + 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; + } + } + + 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) +{ + if (devfreq->driver->resume) + { + int ret; + + nxmutex_lock(&devfreq->lock); + ret = devfreq->driver->resume(devfreq); + nxmutex_unlock(&devfreq->lock); + + if (ret < 0) + { + return ret; + } + } + + nxmutex_lock(&devfreq->lock); + devfreq->suspended = false; + nxmutex_unlock(&devfreq->lock); + + devfreq_start_governor(devfreq); + 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; + irqstate_t flags; + + if (!name) + { + return NULL; + } + + 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)) + { + spin_unlock_irqrestore(&g_devfreq_list_lock, flags); + return devfreq; + } + } + + spin_unlock_irqrestore(&g_devfreq_list_lock, flags); + 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; + irqstate_t flags; + size_t i = 0; + + flags = spin_lock_irqsave(&g_devfreq_list_lock); + + list_for_every_entry(&g_devfreq_list, devfreq, struct devfreq_s, node) + { + if (index == i++) + { + spin_unlock_irqrestore(&g_devfreq_list_lock, flags); + return devfreq; + } + } + + spin_unlock_irqrestore(&g_devfreq_list_lock, flags); + return NULL; +} diff --git a/drivers/devfreq/devfreq_ondemand.c b/drivers/devfreq/devfreq_ondemand.c new file mode 100644 index 0000000000000..b6557ee38d27f --- /dev/null +++ b/drivers/devfreq/devfreq_ondemand.c @@ -0,0 +1,238 @@ +/**************************************************************************** + * 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; + 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) + { + uint32_t cur_freq = devfreq_get_frequency(dev); + if (cur_freq < dev->max) + { + data->target_freq = dev->max; + } + else + { + data->target_freq = cur_freq; + } + } + else + { + data->target_freq = dev->min + cpuload * + (dev->max - dev->min) / 100; + } + + /* 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. + */ + + 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) +{ + FAR struct devfreq_ondemand_s *data; + + data = kmm_zalloc(sizeof(struct devfreq_ondemand_s)); + if (!data) + { + return -ENOMEM; + } + + 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; +} + +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); + + /* 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; +} + +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; +} diff --git a/drivers/devfreq/devfreq_performance.c b/drivers/devfreq/devfreq_performance.c new file mode 100644 index 0000000000000..1b39ce3341c68 --- /dev/null +++ b/drivers/devfreq/devfreq_performance.c @@ -0,0 +1,59 @@ +/**************************************************************************** + * 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 + +/**************************************************************************** + * 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_performance(void) +{ + return &g_devfreq_gov_performance; +} diff --git a/drivers/devfreq/devfreq_powersave.c b/drivers/devfreq/devfreq_powersave.c new file mode 100644 index 0000000000000..6d272c1753a20 --- /dev/null +++ b/drivers/devfreq/devfreq_powersave.c @@ -0,0 +1,59 @@ +/**************************************************************************** + * 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 + +/**************************************************************************** + * 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_powersave(void) +{ + return &g_devfreq_gov_powersave; +} diff --git a/drivers/devfreq/devfreq_procfs.c b/drivers/devfreq/devfreq_procfs.c new file mode 100644 index 0000000000000..d97b2f3deb124 --- /dev/null +++ b/drivers/devfreq/devfreq_procfs.c @@ -0,0 +1,492 @@ +/**************************************************************************** + * 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; +#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; + + 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_get_frequency(devfreq), + 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) + { + 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"); + } +#endif + + nxmutex_unlock(&devfreq->lock); + + if (offset < 0) + { + offset = -offset; + } + else + { + offset = 0; + } + + filep->f_pos += offset; + return offset; +} + +/**************************************************************************** + * 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; +} + +/**************************************************************************** + * 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 | + S_IWOTH | S_IWGRP | S_IWUSR; + } + + 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 new file mode 100644 index 0000000000000..9c0a34f453a0c --- /dev/null +++ b/drivers/devfreq/devfreq_qos.c @@ -0,0 +1,204 @@ +/**************************************************************************** + * 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 +#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); + +#ifdef CONFIG_DEVFREQ_PROCFS_QOS + req->backtrace = backtrace_record(0); +#endif + + 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); + +#ifdef CONFIG_DEVFREQ_PROCFS_QOS + backtrace_remove(req->backtrace); +#endif + + 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_last(&constraints->min_requests)->prio; + } + + case QOS_REQ_MAX: + { + return plist_first(&constraints->max_requests)->prio; + } + } + + 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 new file mode 100644 index 0000000000000..13c28d1787b69 --- /dev/null +++ b/include/nuttx/devfreq.h @@ -0,0 +1,362 @@ +/**************************************************************************** + * 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 */ + +/* 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 + ****************************************************************************/ + +struct devfreq_s +{ + char name[NAME_MAX]; + struct list_node node; + + FAR const struct devfreq_governor_s *governor; + FAR const 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 */ + + bool suspended; + + mutex_t lock; + + FAR void *priv; + +#ifdef CONFIG_DEVFREQ_PROCFS + FAR struct qos_request_s *procfs_qos; +#endif +}; + +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 +{ + 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, + 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 const struct devfreq_governor_s *governor, + FAR const 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_frequency + * + * 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); + +/**************************************************************************** + * 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); + +FAR struct devfreq_governor_s *devfreq_performance(void); +FAR struct devfreq_governor_s *devfreq_powersave(void); + +#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..d85e135033f99 --- /dev/null +++ b/include/nuttx/devfreq/devfreq_qos.h @@ -0,0 +1,69 @@ +/**************************************************************************** + * 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; +#ifdef CONFIG_DEVFREQ_PROCFS_QOS + int backtrace; +#endif +}; + +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 */