| /**************************************************************************** |
| * 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; |
| #if defined(CONFIG_LIBC_BACKTRACE_BUFFSIZE) && CONFIG_LIBC_BACKTRACE_BUFFSIZE > 0 |
| FAR void **stack; |
| int depth; |
| #endif |
| #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) |
| { |
| procfs_sprintf(buffer, buflen, &offset, |
| " %"PRIu32", %"PRIu32",", |
| qos->min_req.prio, qos->max_req.prio); |
| #if defined(CONFIG_LIBC_BACKTRACE_BUFFSIZE) && CONFIG_LIBC_BACKTRACE_BUFFSIZE > 0 |
| stack = backtrace_get(qos->backtrace, &depth); |
| for (i = 0; i < depth; i++) |
| { |
| procfs_sprintf(buffer, buflen, &offset, " %p", stack[i]); |
| } |
| #endif |
| |
| 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); |
| } |