nuttx/drivers/devfreq/devfreq_procfs.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

488 lines
14 KiB
C

/****************************************************************************
* 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 <sys/types.h>
#include <sys/stat.h>
#include <assert.h>
#include <debug.h>
#include <errno.h>
#include <execinfo.h>
#include <stdio.h>
#include <string.h>
#include <nuttx/devfreq.h>
#include <nuttx/fs/fs.h>
#include <nuttx/fs/procfs.h>
#include <nuttx/kmalloc.h>
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* Determines the size of an intermediate buffer that must be large enough
* to handle the longest line generated by this logic.
*/
#define DEVFREQ_LINELEN 256
/****************************************************************************
* Private Types
****************************************************************************/
struct devfreq_procfs_s
{
struct procfs_file_s base;
FAR struct devfreq_s *devfreq;
};
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
/* File system methods */
static int devfreq_open(FAR struct file *filep,
FAR const char *relpath,
int oflags, mode_t mode);
static int devfreq_close(FAR struct file *filep);
static ssize_t devfreq_read(FAR struct file *filep,
FAR char *buffer,
size_t buflen);
static ssize_t devfreq_write(FAR struct file *filep,
FAR const char *buffer,
size_t buflen);
static int devfreq_dup(FAR const struct file *oldp,
FAR struct file *newp);
static int devfreq_opendir(FAR const char *relpath,
FAR struct fs_dirent_s **dir);
static int devfreq_readdir(FAR struct fs_dirent_s *dir,
FAR struct dirent *entry);
static int devfreq_closedir(FAR struct fs_dirent_s *dir);
static int devfreq_rewinddir(FAR struct fs_dirent_s *dir);
static int devfreq_stat(FAR const char *relpath, FAR struct stat *buf);
/****************************************************************************
* Private Data
****************************************************************************/
static const struct procfs_operations g_devfreq_operations =
{
.open = devfreq_open, /* open */
.close = devfreq_close, /* close */
.read = devfreq_read, /* read */
.write = devfreq_write, /* write */
.poll = NULL, /* poll */
.dup = devfreq_dup, /* dup */
.opendir = devfreq_opendir, /* opendir */
.closedir = devfreq_closedir, /* closedir */
.readdir = devfreq_readdir, /* readdir */
.rewinddir = devfreq_rewinddir, /* rewinddir */
.stat = devfreq_stat, /* stat */
};
static const struct procfs_entry_s g_devfreq_procfs_root =
{
"devfreq", &g_devfreq_operations, PROCFS_DIR_TYPE
};
static const struct procfs_entry_s g_devfreq_procfs_entry =
{
"devfreq/**", &g_devfreq_operations, PROCFS_UNKOWN_TYPE
};
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: devfreq_open
****************************************************************************/
static int devfreq_open(FAR struct file *filep, FAR const char *relpath,
int oflags, mode_t mode)
{
FAR struct devfreq_s *devfreq;
FAR struct devfreq_procfs_s *devfreq_procfs;
relpath += strlen("devfreq/");
devfreq = devfreq_find_by_name(relpath);
if (!devfreq)
{
return -ENOENT;
}
devfreq_procfs = kmm_zalloc(sizeof(struct devfreq_procfs_s));
if (!devfreq_procfs)
{
return -ENOMEM;
}
devfreq_procfs->devfreq = devfreq;
filep->f_priv = devfreq_procfs;
return 0;
}
/****************************************************************************
* Name: devfreq_close
****************************************************************************/
static int devfreq_close(FAR struct file *filep)
{
DEBUGASSERT(filep->f_priv);
kmm_free(filep->f_priv);
filep->f_priv = NULL;
return 0;
}
/****************************************************************************
* Name: devfreq_read
****************************************************************************/
static ssize_t devfreq_read(FAR struct file *filep,
FAR char *buffer, size_t buflen)
{
FAR struct devfreq_procfs_s *devfreq_procfs = filep->f_priv;
FAR struct devfreq_s *devfreq = devfreq_procfs->devfreq;
#ifdef CONFIG_DEVFREQ_PROCFS_QOS
FAR struct qos_request_s *qos;
void **stack;
int depth;
#endif
off_t offset = filep->f_pos;
size_t i;
nxmutex_lock(&devfreq->lock);
procfs_sprintf(buffer, buflen, &offset,
" devfreq: %s\n"
" governor: %s\n"
" cur_freq: %"PRIu32"\n"
" suspended: %s\n",
devfreq->name,
devfreq->governor->name,
devfreq_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)
{
stack = backtrace_get(qos->backtrace, &depth);
procfs_sprintf(buffer, buflen, &offset,
" %"PRIu32", %"PRIu32",",
qos->min_req.prio, qos->max_req.prio);
for (i = 0; i < depth; i++)
{
procfs_sprintf(buffer, buflen, &offset, " %p", stack[i]);
}
procfs_sprintf(buffer, buflen, &offset, "\n");
}
#endif
nxmutex_unlock(&devfreq->lock);
if (offset < 0)
{
offset = -offset;
}
else
{
offset = 0;
}
filep->f_pos += offset;
return offset;
}
/****************************************************************************
* Name: devfreq_write
*
* Description:
* Handle write to devfreq procfs entry.
* Format: "<min_freq> <max_freq>" 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);
}