mirror of
https://github.com/apache/nuttx.git
synced 2026-08-14 08:53:15 +00:00
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>
238 lines
6.7 KiB
C
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;
|
|
}
|