mirror of
https://github.com/apache/nuttx.git
synced 2026-08-10 06:55:13 +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>
488 lines
14 KiB
C
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);
|
|
}
|