nuttx/drivers/devfreq/devfreq_procfs.c
guanyi3 7a1c62911d devfreq/procfs: add write support for frequency QoS constraints
Add the ability to set frequency constraints via procfs write.
Supported formats:
  echo <min>,<max> > /proc/devfreq/<name>  - set frequency range
  echo 0,0 > /proc/devfreq/<name>          - remove constraint

The QoS request is bound to the devfreq device lifetime so that
shell commands like echo (which open, write, close immediately)
work correctly. Leading whitespace in the write buffer is skipped
to handle extra writes from nsh echo (e.g. trailing newline).

Also add write permissions in devfreq_stat() and a procfs_qos
field in devfreq_s guarded by CONFIG_DEVFREQ_PROCFS.

Signed-off-by: guanyi3 <guanyi3@xiaomi.com>
(cherry picked from commit 70ae195c84f35a4d0b85fcc14187989b60fc0280)
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->cur,
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);
}