nuttx/drivers/devfreq/devfreq_ondemand.c
guanyi3 674e5ef4d8 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 <guanyi3@xiaomi.com>
2026-08-08 15:23:54 -03:00

238 lines
6.7 KiB
C

/****************************************************************************
* 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 <nuttx/config.h>
#include <nuttx/devfreq.h>
#include <nuttx/kmalloc.h>
#include <nuttx/wqueue.h>
#include <sys/param.h>
/****************************************************************************
* 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;
}