From c0f2b7811ececfe24927af9239c59c17551da372 Mon Sep 17 00:00:00 2001 From: ligd Date: Mon, 20 Jun 2022 18:16:42 +0800 Subject: [PATCH] pm: add pm procfs support Signed-off-by: ligd --- drivers/power/Kconfig | 5 + drivers/power/Make.defs | 6 + drivers/power/pm.h | 7 + drivers/power/pm_activity.c | 38 +++ drivers/power/pm_changestate.c | 34 ++ drivers/power/pm_procfs.c | 552 +++++++++++++++++++++++++++++++++ fs/procfs/fs_procfs.c | 6 + include/nuttx/power/pm.h | 7 + 8 files changed, 655 insertions(+) create mode 100644 drivers/power/pm_procfs.c diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index 11fc41225d..28e2aaae7b 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -24,6 +24,11 @@ config PM_NDOMAINS Network domain, shutting down the network when it is not be used, from the UI domain, shutting down the UI when it is not in use. +config PM_PROCFS + bool "PM proc fs support" + ---help--- + Enable procfs for pm. + config PM_GOVERNOR_GREEDY bool "Greedy governor" ---help--- diff --git a/drivers/power/Make.defs b/drivers/power/Make.defs index 7c1ecb0f43..bed1beaf0a 100644 --- a/drivers/power/Make.defs +++ b/drivers/power/Make.defs @@ -25,6 +25,12 @@ ifeq ($(CONFIG_PM),y) CSRCS += pm_initialize.c pm_activity.c pm_changestate.c pm_checkstate.c CSRCS += pm_register.c pm_unregister.c pm_autoupdate.c pm_governor.c pm_lock.c +ifeq ($(CONFIG_PM_PROCFS),y) + +CSRCS += pm_procfs.c + +endif + # Governor implementations ifeq ($(CONFIG_PM_GOVERNOR_ACTIVITY),y) diff --git a/drivers/power/pm.h b/drivers/power/pm.h index 131ec8c5db..a01dd5a22d 100644 --- a/drivers/power/pm.h +++ b/drivers/power/pm.h @@ -58,6 +58,13 @@ struct pm_domain_s struct dq_queue_s wakelock[PM_COUNT]; +#ifdef CONFIG_PM_PROCFS + struct dq_queue_s wakelockall; + struct timespec start; + struct timespec wake[PM_COUNT]; + struct timespec sleep[PM_COUNT]; +#endif + /* Auto update or not */ bool auto_update; diff --git a/drivers/power/pm_activity.c b/drivers/power/pm_activity.c index 0ca44abb7e..487b369331 100644 --- a/drivers/power/pm_activity.c +++ b/drivers/power/pm_activity.c @@ -51,6 +51,40 @@ static void pm_waklock_cb(wdparm_t arg) pm_wakelock_relax((FAR struct pm_wakelock_s *)arg); } +#ifdef CONFIG_PM_PROCFS +static void pm_wakelock_stats_rm(FAR struct pm_wakelock_s *wakelock) +{ + FAR struct pm_domain_s *pdom = &g_pmglobals.domain[wakelock->domain]; + + dq_rem(&wakelock->fsnode, &pdom->wakelockall); +} + +static void pm_wakelock_stats(FAR struct pm_wakelock_s *wakelock, bool stay) +{ + FAR struct pm_domain_s *pdom = &g_pmglobals.domain[wakelock->domain]; + struct timespec ts; + + if (stay) + { + if (!wakelock->fsnode.blink && !wakelock->fsnode.flink) + { + dq_addlast(&wakelock->fsnode, &pdom->wakelockall); + } + + clock_systime_timespec(&wakelock->start); + } + else + { + clock_systime_timespec(&ts); + clock_timespec_subtract(&ts, &wakelock->start, &ts); + clock_timespec_add(&ts, &wakelock->elapse, &wakelock->elapse); + } +} +#else +#define pm_wakelock_stats_rm(w) +#define pm_wakelock_stats(w, s) +#endif + /**************************************************************************** * Public Functions ****************************************************************************/ @@ -279,6 +313,7 @@ void pm_wakelock_uninit(FAR struct pm_wakelock_s *wakelock) wakelock->count = 0; wd_cancel(wdog); + pm_wakelock_stats_rm(wakelock); pm_unlock(domain, flags); } @@ -323,6 +358,7 @@ void pm_wakelock_stay(FAR struct pm_wakelock_s *wakelock) if (wakelock->count++ == 0) { dq_addfirst(&wakelock->node, dq); + pm_wakelock_stats(wakelock, true); } pm_unlock(domain, flags); @@ -369,6 +405,7 @@ void pm_wakelock_relax(FAR struct pm_wakelock_s *wakelock) if (--wakelock->count == 0) { dq_rem(&wakelock->node, dq); + pm_wakelock_stats(wakelock, false); } pm_unlock(domain, flags); @@ -422,6 +459,7 @@ void pm_wakelock_staytimeout(FAR struct pm_wakelock_s *wakelock, int ms) if (wakelock->count++ == 0) { dq_addfirst(&wakelock->node, dq); + pm_wakelock_stats(wakelock, true); } } diff --git a/drivers/power/pm_changestate.c b/drivers/power/pm_changestate.c index c387c5d429..602018e728 100644 --- a/drivers/power/pm_changestate.c +++ b/drivers/power/pm_changestate.c @@ -168,6 +168,35 @@ static inline void pm_changeall(int domain, enum pm_state_e newstate) } } +#ifdef CONFIG_PM_PROCFS +static void pm_stats(FAR struct pm_domain_s *dom, int curstate, int newstate) +{ + struct timespec ts; + + clock_systime_timespec(&ts); + clock_timespec_subtract(&ts, &dom->start, &ts); + + if (newstate == PM_RESTORE) + { + /* Wakeup from WFI */ + + clock_timespec_add(&ts, &dom->sleep[curstate], &dom->sleep[curstate]); + } + else + { + /* Sleep to WFI */ + + clock_timespec_add(&ts, &dom->wake[curstate], &dom->wake[curstate]); + } + + /* Update start */ + + clock_systime_timespec(&dom->start); +} +#else +#define pm_stats(dom, curstate, newstate) +#endif + /**************************************************************************** * Public Functions ****************************************************************************/ @@ -233,6 +262,11 @@ int pm_changestate(int domain, enum pm_state_e newstate) } } + /* Statistics */ + + pm_stats(&g_pmglobals.domain[domain], + g_pmglobals.domain[domain].state, newstate); + /* All drivers have agreed to the state change (or, one or more have * disagreed and the state has been reverted). Set the new state. */ diff --git a/drivers/power/pm_procfs.c b/drivers/power/pm_procfs.c new file mode 100644 index 0000000000..2f862d40d3 --- /dev/null +++ b/drivers/power/pm_procfs.c @@ -0,0 +1,552 @@ +/**************************************************************************** + * drivers/power/pm_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 +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "pm.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define STHDR "DOMAIN%d WAKE SLEEP TOTAL\n" +#define STFMT "%-8s %8lus %02lu%% %8lus %02lu%% %8lus %02lu%%\n" + +#define WAHDR "DOMAIN%d STATE COUNT TIME\n" +#define WAFMT "%-12s %-10s %4lu %8lus\n" + +/* Determines the size of an intermediate buffer that must be large enough + * to handle the longest line generated by this logic (plus a couple of + * bytes). + */ + +#define PM_LINELEN 128 + +#ifndef ARRAY_SIZE +# define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) +#endif + +typedef ssize_t (*pm_read_t)(FAR struct file *filep, + FAR char *buffer, size_t buflen); + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/* This structure describes one open "file" */ + +struct pm_file_s +{ + struct procfs_file_s base; /* Base open file structure */ + char line[PM_LINELEN]; /* Pre-allocated buffer for formatted lines */ + int domain; /* Domain index */ + pm_read_t read; /* Read function */ +}; + +struct pm_file_ops_s +{ + FAR const char *name; + pm_read_t read; +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/* File system methods */ + +static int pm_open(FAR struct file *filep, FAR const char *relpath, + int oflags, mode_t mode); +static int pm_close(FAR struct file *filep); +static ssize_t pm_read_state(FAR struct file *filep, FAR char *buffer, + size_t buflen); +static ssize_t pm_read_wakelock(FAR struct file *filep, FAR char *buffer, + size_t buflen); +static ssize_t pm_read(FAR struct file *filep, FAR char *buffer, + size_t buflen); +static int pm_dup(FAR const struct file *oldp, + FAR struct file *newp); + +static int pm_opendir(FAR const char *relpath, + FAR struct fs_dirent_s *dir); +static int pm_closedir(FAR struct fs_dirent_s *dir); +static int pm_readdir(FAR struct fs_dirent_s *dir); +static int pm_rewinddir(FAR struct fs_dirent_s *dir); + +static int pm_stat(FAR const char *relpath, FAR struct stat *buf); + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +/* See fs_mount.c -- this structure is explicitly extern'ed there. + * We use the old-fashioned kind of initializers so that this will compile + * with any compiler. + */ + +const struct procfs_operations pm_operations = +{ + pm_open, /* open */ + pm_close, /* close */ + pm_read, /* read */ + NULL, /* write */ + + pm_dup, /* dup */ + + pm_opendir, /* opendir */ + pm_closedir, /* closedir */ + pm_readdir, /* readdir */ + pm_rewinddir, /* rewinddir */ + + pm_stat /* stat */ +}; + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct pm_file_ops_s g_pm_files[] = +{ + {"state", pm_read_state}, + {"wakelock", pm_read_wakelock}, +}; + +static FAR const char *g_pm_state[PM_COUNT] = +{ + "normal", "idle", "standby", "sleep" +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: pm_open + ****************************************************************************/ + +static int pm_open(FAR struct file *filep, FAR const char *relpath, + int oflags, mode_t mode) +{ + FAR struct pm_file_s *pmfile; + int i; + + finfo("Open '%s'\n", relpath); + + /* This PROCFS file is read-only. Any attempt to open with write access + * is not permitted. + */ + + if ((oflags & O_WRONLY) != 0 || (oflags & O_RDONLY) == 0) + { + ferr("ERROR: Only O_RDONLY supported\n"); + return -EACCES; + } + + /* Allocate a container to hold the file attributes */ + + pmfile = (FAR struct pm_file_s *)kmm_zalloc(sizeof(struct pm_file_s)); + if (!pmfile) + { + ferr("ERROR: Failed to allocate file attributes\n"); + return -ENOMEM; + } + + relpath += strlen("pm/"); + for (i = 0; i < ARRAY_SIZE(g_pm_files); i++) + { + if (strncmp(relpath, g_pm_files[i].name, + strlen(g_pm_files[i].name)) == 0) + { + pmfile->read = g_pm_files[i].read; + break; + } + } + + pmfile->domain = atoi(relpath + strlen(g_pm_files[i].name)); + + DEBUGASSERT(pmfile->read); + DEBUGASSERT(pmfile->domain < CONFIG_PM_NDOMAINS); + + /* Save the attributes as the open-specific state in filep->f_priv */ + + filep->f_priv = (FAR void *)pmfile; + return OK; +} + +/**************************************************************************** + * Name: pm_close + ****************************************************************************/ + +static int pm_close(FAR struct file *filep) +{ + FAR struct pm_file_s *pmfile; + + /* Recover our private data from the struct file instance */ + + pmfile = (FAR struct pm_file_s *)filep->f_priv; + DEBUGASSERT(pmfile); + + /* Release the file attributes structure */ + + kmm_free(pmfile); + filep->f_priv = NULL; + return OK; +} + +static ssize_t pm_read_state(FAR struct file *filep, FAR char *buffer, + size_t buflen) +{ + FAR struct pm_domain_s *dom; + FAR struct pm_file_s *pmfile; + irqstate_t flags; + size_t totalsize = 0; + size_t linesize; + size_t copysize; + off_t offset; + uint32_t sum = 0; + uint32_t state; + + finfo("buffer=%p buflen=%d\n", buffer, (int)buflen); + + /* Recover our private data from the struct file instance */ + + pmfile = (FAR struct pm_file_s *)filep->f_priv; + dom = &g_pmglobals.domain[pmfile->domain]; + DEBUGASSERT(pmfile); + DEBUGASSERT(dom); + + /* Save the file offset and the user buffer information */ + + offset = filep->f_pos; + + /* Then list the power state */ + + linesize = snprintf(pmfile->line, PM_LINELEN, STHDR, pmfile->domain); + copysize = procfs_memcpy(pmfile->line, linesize, buffer, + buflen, &offset); + + totalsize += copysize; + + flags = pm_lock(pmfile->domain); + + for (state = 0; state < PM_COUNT; state++) + { + sum += dom->wake[state].tv_sec + dom->sleep[state].tv_sec; + } + + sum = sum ? sum : 1; + + for (state = 0; state < PM_COUNT && totalsize < buflen; state++) + { + time_t total; + + total = dom->wake[state].tv_sec + dom->sleep[state].tv_sec; + + linesize = snprintf(pmfile->line, PM_LINELEN, STFMT, + g_pm_state[state], + dom->wake[state].tv_sec, + 100 * dom->wake[state].tv_sec / sum, + dom->sleep[state].tv_sec, + 100 * dom->sleep[state].tv_sec / sum, + total, + 100 * total / sum); + buffer += copysize; + buflen -= copysize; + + copysize = procfs_memcpy(pmfile->line, linesize, buffer, + buflen, &offset); + + totalsize += copysize; + } + + pm_unlock(pmfile->domain, flags); + + filep->f_pos += totalsize; + return totalsize; +} + +static ssize_t pm_read_wakelock(FAR struct file *filep, FAR char *buffer, + size_t buflen) +{ + FAR struct pm_domain_s *dom; + FAR struct pm_file_s *pmfile; + FAR dq_entry_t *entry; + irqstate_t flags; + size_t totalsize = 0; + size_t linesize; + size_t copysize; + off_t offset; + + finfo("buffer=%p buflen=%d\n", buffer, (int)buflen); + + /* Recover our private data from the struct file instance */ + + pmfile = (FAR struct pm_file_s *)filep->f_priv; + dom = &g_pmglobals.domain[pmfile->domain]; + DEBUGASSERT(pmfile); + DEBUGASSERT(dom); + + /* Save the file offset and the user buffer information */ + + offset = filep->f_pos; + + /* Then list the power state */ + + linesize = snprintf(pmfile->line, PM_LINELEN, + WAHDR, pmfile->domain); + copysize = procfs_memcpy(pmfile->line, linesize, buffer, + buflen, &offset); + + totalsize += copysize; + + flags = pm_lock(pmfile->domain); + + entry = dq_peek(&dom->wakelockall); + for (; entry && totalsize < buflen; entry = dq_next(entry)) + { + FAR struct pm_wakelock_s *wakelock = + container_of(entry, struct pm_wakelock_s, fsnode); + + buffer += copysize; + buflen -= copysize; + + linesize = snprintf(pmfile->line, PM_LINELEN, WAFMT, + wakelock->name, + g_pm_state[wakelock->state], + wakelock->count, + wakelock->elapse.tv_sec); + + copysize = procfs_memcpy(pmfile->line, linesize, buffer, + buflen, &offset); + + totalsize += copysize; + } + + pm_unlock(pmfile->domain, flags); + + filep->f_pos += totalsize; + return totalsize; +} + +/**************************************************************************** + * Name: pm_read + ****************************************************************************/ + +static ssize_t pm_read(FAR struct file *filep, FAR char *buffer, + size_t buflen) +{ + FAR struct pm_file_s *pmfile; + + pmfile = (FAR struct pm_file_s *)filep->f_priv; + + return pmfile->read(filep, buffer, buflen); +} + +/**************************************************************************** + * Name: pm_dup + * + * Description: + * Duplicate open file data in the new file structure. + * + ****************************************************************************/ + +static int pm_dup(FAR const struct file *oldp, FAR struct file *newp) +{ + FAR struct pm_file_s *oldattr; + FAR struct pm_file_s *newattr; + + finfo("Dup %p->%p\n", oldp, newp); + + /* Recover our private data from the old struct file instance */ + + oldattr = (FAR struct pm_file_s *)oldp->f_priv; + DEBUGASSERT(oldattr); + + /* Allocate a new container to hold the task and attribute selection */ + + newattr = (FAR struct pm_file_s *)kmm_malloc(sizeof(struct pm_file_s)); + if (!newattr) + { + ferr("ERROR: Failed to allocate file attributes\n"); + return -ENOMEM; + } + + /* The copy the file attributes from the old attributes to the new */ + + memcpy(newattr, oldattr, sizeof(struct pm_file_s)); + + /* Save the new attributes in the new file structure */ + + newp->f_priv = (FAR void *)newattr; + return OK; +} + +/**************************************************************************** + * Name: pm_opendir + * + * Description: + * Open a directory for read access + * + ****************************************************************************/ + +static int pm_opendir(FAR const char *relpath, FAR struct fs_dirent_s *dir) +{ + FAR struct procfs_dir_priv_s *level1; + + finfo("relpath: \"%s\"\n", relpath ? relpath : "NULL"); + DEBUGASSERT(relpath && dir && !dir->u.procfs); + + /* Assume that path refers to the 1st level subdirectory. Allocate the + * level1 the dirent structure before checking. + */ + + level1 = kmm_zalloc(sizeof(struct procfs_dir_priv_s)); + if (level1 == NULL) + { + ferr("ERROR: Failed to allocate the level1 directory structure\n"); + return -ENOMEM; + } + + /* Initialize base structure components */ + + level1->level = 1; + level1->nentries = CONFIG_PM_NDOMAINS * ARRAY_SIZE(g_pm_files); + + dir->u.procfs = (FAR void *)level1; + return OK; +} + +/**************************************************************************** + * Name: pm_closedir + * + * Description: Close the directory listing + * + ****************************************************************************/ + +static int pm_closedir(FAR struct fs_dirent_s *dir) +{ + FAR struct procfs_dir_priv_s *level1; + + DEBUGASSERT(dir && dir->u.procfs); + level1 = dir->u.procfs; + + kmm_free(level1); + + dir->u.procfs = NULL; + return OK; +} + +/**************************************************************************** + * Name: pm_readdir + * + * Description: Read the next directory entry + * + ****************************************************************************/ + +static int pm_readdir(FAR struct fs_dirent_s *dir) +{ + FAR struct procfs_dir_priv_s *level1; + int index; + int domain; + int fpos; + + DEBUGASSERT(dir && dir->u.procfs); + level1 = dir->u.procfs; + + index = level1->index; + if (index >= level1->nentries) + { + /* We signal the end of the directory by returning the special + * error -ENOENT + */ + + finfo("Entry %d: End of directory\n", index); + return -ENOENT; + } + + domain = index / ARRAY_SIZE(g_pm_files); + fpos = index % ARRAY_SIZE(g_pm_files); + + dir->fd_dir.d_type = DTYPE_FILE; + snprintf(dir->fd_dir.d_name, NAME_MAX + 1, "%s%d", + g_pm_files[fpos].name, domain); + + level1->index++; + return OK; +} + +/**************************************************************************** + * Name: pm_rewindir + * + * Description: Reset directory read to the first entry + * + ****************************************************************************/ + +static int pm_rewinddir(FAR struct fs_dirent_s *dir) +{ + FAR struct procfs_dir_priv_s *level1; + + DEBUGASSERT(dir && dir->u.procfs); + level1 = dir->u.procfs; + + level1->index = 0; + return OK; +} + +/**************************************************************************** + * Name: pm_stat + * + * Description: Return information about a file or directory + * + ****************************************************************************/ + +static int pm_stat(FAR const char *relpath, FAR struct stat *buf) +{ + memset(buf, 0, sizeof(struct stat)); + + if (strcmp(relpath, "pm") == 0 || strcmp(relpath, "pm/") == 0) + { + buf->st_mode = S_IFDIR | S_IROTH | S_IRGRP | S_IRUSR; + } + else + { + buf->st_mode = S_IFREG | S_IROTH | S_IRGRP | S_IRUSR; + } + + return OK; +} diff --git a/fs/procfs/fs_procfs.c b/fs/procfs/fs_procfs.c index f9e5aa06dd..30b0f074c0 100644 --- a/fs/procfs/fs_procfs.c +++ b/fs/procfs/fs_procfs.c @@ -62,6 +62,7 @@ ****************************************************************************/ extern const struct procfs_operations proc_operations; +extern const struct procfs_operations pm_operations; extern const struct procfs_operations irq_operations; extern const struct procfs_operations cpuload_operations; extern const struct procfs_operations critmon_operations; @@ -158,6 +159,11 @@ static const struct procfs_entry_s g_procfs_entries[] = { "partitions", &part_procfsoperations, PROCFS_FILE_TYPE }, #endif +#if defined(CONFIG_PM) && defined(CONFIG_PM_PROCFS) + { "pm", &pm_operations, PROCFS_DIR_TYPE }, + { "pm/**", &pm_operations, PROCFS_UNKOWN_TYPE }, +#endif + #ifndef CONFIG_FS_PROCFS_EXCLUDE_PROCESS { "self", &proc_operations, PROCFS_DIR_TYPE }, { "self/**", &proc_operations, PROCFS_UNKOWN_TYPE }, diff --git a/include/nuttx/power/pm.h b/include/nuttx/power/pm.h index 4eb29422c6..03a3796c95 100644 --- a/include/nuttx/power/pm.h +++ b/include/nuttx/power/pm.h @@ -57,6 +57,7 @@ #include #include +#include #include #ifdef CONFIG_PM @@ -303,6 +304,12 @@ struct pm_wakelock_s uint32_t count; struct dq_entry_s node; struct wdog_s wdog; + +#ifdef CONFIG_PM_PROCFS + struct dq_entry_s fsnode; + struct timespec start; + struct timespec elapse; +#endif }; /****************************************************************************