diff --git a/fs/procfs/Kconfig b/fs/procfs/Kconfig index 9dc4e96ba0..77aa1d2f5e 100644 --- a/fs/procfs/Kconfig +++ b/fs/procfs/Kconfig @@ -154,5 +154,9 @@ config FS_PROCFS_EXCLUDE_VERSION bool "Exclude version" default DEFAULT_SMALL +config FS_PROCFS_INCLUDE_PRESSURE + bool "Include memory pressure notification" + default n + endmenu # Exclude individual procfs entries endif # FS_PROCFS diff --git a/fs/procfs/Make.defs b/fs/procfs/Make.defs index 318cca570e..594b7e004d 100644 --- a/fs/procfs/Make.defs +++ b/fs/procfs/Make.defs @@ -26,6 +26,10 @@ CSRCS += fs_procfscritmon.c fs_procfsfdt.c fs_procfsiobinfo.c CSRCS += fs_procfsmeminfo.c fs_procfsproc.c fs_procfstcbinfo.c CSRCS += fs_procfsuptime.c fs_procfsutil.c fs_procfsversion.c +ifeq ($(CONFIG_FS_PROCFS_INCLUDE_PRESSURE),y) +CSRCS += fs_procfspressure.c +endif + # Include procfs build support DEPPATH += --dep-path procfs diff --git a/fs/procfs/fs_procfs.c b/fs/procfs/fs_procfs.c index 27e45acbfb..50381b7190 100644 --- a/fs/procfs/fs_procfs.c +++ b/fs/procfs/fs_procfs.c @@ -68,6 +68,7 @@ extern const struct procfs_operations g_proc_operations; extern const struct procfs_operations g_tcbinfo_operations; extern const struct procfs_operations g_uptime_operations; extern const struct procfs_operations g_version_operations; +extern const struct procfs_operations g_pressure_operations; /* This is not good. These are implemented in other sub-systems. Having to * deal with them here is not a good coupling. What is really needed is a @@ -176,6 +177,11 @@ static const struct procfs_entry_s g_procfs_entries[] = { "pm/**", &g_pm_operations, PROCFS_UNKOWN_TYPE }, #endif +#ifdef CONFIG_FS_PROCFS_INCLUDE_PRESSURE + { "pressure", &g_pressure_operations, PROCFS_DIR_TYPE }, + { "pressure/**", &g_pressure_operations, PROCFS_FILE_TYPE }, +#endif + #ifndef CONFIG_FS_PROCFS_EXCLUDE_PROCESS { "self", &g_proc_operations, PROCFS_DIR_TYPE }, { "self/**", &g_proc_operations, PROCFS_UNKOWN_TYPE }, diff --git a/fs/procfs/fs_procfspressure.c b/fs/procfs/fs_procfspressure.c new file mode 100644 index 0000000000..fa9f2c4bc5 --- /dev/null +++ b/fs/procfs/fs_procfspressure.c @@ -0,0 +1,453 @@ +/**************************************************************************** + * fs/procfs/fs_procfspressure.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 + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct pressure_file_s +{ + struct procfs_file_s base; /* Base open file structure */ + dq_entry_t entry; /* Supports a linked list */ + FAR struct pollfd *fds; /* Polling structure of waiting thread */ + size_t threshold; /* Memory notification threshold */ + clock_t lasttick; /* Last time notified */ + clock_t interval; /* Notification interval in us */ +}; + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static dq_queue_t g_pressure_memory_queue; +static spinlock_t g_pressure_lock; +static size_t g_remaining; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static int pressure_open(FAR struct file *filep, FAR const char *relpath, + int oflags, mode_t mode); +static int pressure_close(FAR struct file *filep); +static ssize_t pressure_read(FAR struct file *filep, FAR char *buffer, + size_t buflen); +static ssize_t pressure_write(FAR struct file *filep, FAR const char *buffer, + size_t buflen); +static int pressure_poll(FAR struct file *filep, FAR struct pollfd *fds, + bool setup); +static int pressure_dup(FAR const struct file *oldp, + FAR struct file *newp); +static int pressure_opendir(FAR const char *relpath, + FAR struct fs_dirent_s **dir); +static int pressure_closedir(FAR struct fs_dirent_s *dir); +static int pressure_readdir(FAR struct fs_dirent_s *dir, + FAR struct dirent *entry); +static int pressure_rewinddir(FAR struct fs_dirent_s *dir); +static int pressure_stat(FAR const char *relpath, FAR struct stat *buf); + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +const struct procfs_operations g_pressure_operations = +{ + pressure_open, /* open */ + pressure_close, /* close */ + pressure_read, /* read */ + pressure_write, /* write */ + pressure_poll, /* poll */ + pressure_dup, /* dup */ + pressure_opendir, /* opendir */ + pressure_closedir, /* closedir */ + pressure_readdir, /* readdir */ + pressure_rewinddir, /* rewinddir */ + pressure_stat /* stat */ +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: pressure_open + ****************************************************************************/ + +static int pressure_open(FAR struct file *filep, FAR const char *relpath, + int oflags, mode_t mode) +{ + FAR struct pressure_file_s *priv; + uint32_t flags; + + if (strcmp(relpath, "pressure/memory") != 0) + { + ferr("ERROR: relpath is invalid: %s\n", relpath); + return -ENOENT; + } + + priv = kmm_zalloc(sizeof(struct pressure_file_s)); + if (!priv) + { + return -ENOMEM; + } + + flags = spin_lock_irqsave(&g_pressure_lock); + priv->interval = CLOCK_MAX; + filep->f_priv = priv; + dq_addfirst(&priv->entry, &g_pressure_memory_queue); + spin_unlock_irqrestore(&g_pressure_lock, flags); + return OK; +} + +/**************************************************************************** + * Name: pressure_close + ****************************************************************************/ + +static int pressure_close(FAR struct file *filep) +{ + FAR struct pressure_file_s *priv = filep->f_priv; + uint32_t flags; + + flags = spin_lock_irqsave(&g_pressure_lock); + dq_rem(&priv->entry, &g_pressure_memory_queue); + spin_unlock_irqrestore(&g_pressure_lock, flags); + free(priv); + return OK; +} + +/**************************************************************************** + * Name: pressure_read + ****************************************************************************/ + +static ssize_t pressure_read(FAR struct file *filep, FAR char *buffer, + size_t buflen) +{ + char buf[128]; + uint32_t flags; + ssize_t ret; + + flags = spin_lock_irqsave(&g_pressure_lock); + ret = procfs_snprintf(buf, sizeof(buf), "remaining %zu\n", + g_remaining); + spin_unlock_irqrestore(&g_pressure_lock, flags); + + if (ret > buflen) + { + return -ENOMEM; + } + + memcpy(buffer, buf, ret); + return ret; +} + +/**************************************************************************** + * Name: pressure_write + ****************************************************************************/ + +static ssize_t pressure_write(FAR struct file *filep, FAR const char *buffer, + size_t buflen) +{ + FAR struct pressure_file_s *priv = filep->f_priv; + FAR char *endptr; + size_t threshold; + clock_t interval; + uint32_t flags; + + if (buffer == NULL) + { + return -EINVAL; + } + + threshold = strtoul(buffer, &endptr, 0); + if (threshold == 0) + { + return -EINVAL; + } + + interval = strtol(endptr + 1, NULL, 0); + + /* Check if the interval is valid, -1 means only notify once */ + + if (interval == -1) + { + interval = CLOCK_MAX; + } + else + { + interval = USEC2TICK(interval); + } + + flags = spin_lock_irqsave(&g_pressure_lock); + + /* We should trigger the first event immediately */ + + priv->lasttick = CLOCK_MAX; + priv->threshold = threshold; + priv->interval = interval; + spin_unlock_irqrestore(&g_pressure_lock, flags); + return buflen; +} + +/**************************************************************************** + * Name: pressure_poll + ****************************************************************************/ + +static int pressure_poll(FAR struct file *filep, FAR struct pollfd *fds, + bool setup) +{ + FAR struct pressure_file_s *priv = filep->f_priv; + clock_t current = clock_systime_ticks(); + uint32_t flags; + + flags = spin_lock_irqsave(&g_pressure_lock); + if (setup) + { + if (priv->fds == NULL) + { + priv->fds = fds; + fds->priv = &priv->fds; + + /* If the remaining memory is less than the threshold and + * lasttick is CLOCK_MAX, it means the event is triggered for + * the first time and we should always send a notification. + */ + + if (g_remaining <= priv->threshold && (priv->lasttick == + CLOCK_MAX || current - priv->lasttick >= priv->interval)) + { + priv->lasttick = current; + spin_unlock_irqrestore(&g_pressure_lock, flags); + poll_notify(&priv->fds, 1, POLLPRI); + return OK; + } + } + else + { + spin_unlock_irqrestore(&g_pressure_lock, flags); + return -EBUSY; + } + } + else if (fds->priv) + { + priv->fds = NULL; + fds->priv = NULL; + } + + spin_unlock_irqrestore(&g_pressure_lock, flags); + return OK; +} + +/**************************************************************************** + * Name: pressure_dup + ****************************************************************************/ + +static int pressure_dup(FAR const struct file *oldp, FAR struct file *newp) +{ + FAR struct pressure_file_s *oldpriv = oldp->f_priv; + FAR struct pressure_file_s *newpriv; + uint32_t flags; + + newpriv = kmm_zalloc(sizeof(struct pressure_file_s)); + if (newpriv == NULL) + { + return -ENOMEM; + } + + flags = spin_lock_irqsave(&g_pressure_lock); + memcpy(newpriv, oldpriv, sizeof(struct pressure_file_s)); + dq_addfirst(&newpriv->entry, &g_pressure_memory_queue); + newpriv->fds = NULL; + newp->f_priv = newpriv; + spin_unlock_irqrestore(&g_pressure_lock, flags); + return OK; +} + +/**************************************************************************** + * Name: pressure_opendir + ****************************************************************************/ + +static int pressure_opendir(FAR const char *relpath, + FAR struct fs_dirent_s **dir) +{ + FAR struct procfs_dir_priv_s *level; + + finfo("relpath: \"%s\"\n", relpath ? relpath : "NULL"); + DEBUGASSERT(relpath); + + level = kmm_zalloc(sizeof(struct procfs_dir_priv_s)); + if (level == NULL) + { + return -ENOMEM; + } + + level->level = 1; + level->nentries = 1; + + *dir = (FAR struct fs_dirent_s *)level; + return OK; +} + +/**************************************************************************** + * Name: pressure_closedir + ****************************************************************************/ + +static int pressure_closedir(FAR struct fs_dirent_s *dir) +{ + DEBUGASSERT(dir); + kmm_free(dir); + return OK; +} + +/**************************************************************************** + * Name: pressure_readdir + ****************************************************************************/ + +static int pressure_readdir(FAR struct fs_dirent_s *dir, + FAR struct dirent *entry) +{ + FAR struct procfs_dir_priv_s *level; + + DEBUGASSERT(dir); + level = (FAR struct procfs_dir_priv_s *)dir; + if (level->index >= level->nentries) + { + finfo("No more entries\n"); + return -ENOENT; + } + + entry->d_type = DTYPE_FILE; + strncpy(entry->d_name, "memory", sizeof(entry->d_name)); + level->index++; + return OK; +} + +/**************************************************************************** + * Name: pressure_rewinddir + ****************************************************************************/ + +static int pressure_rewinddir(FAR struct fs_dirent_s *dir) +{ + FAR struct procfs_dir_priv_s *level; + + DEBUGASSERT(dir); + level = (FAR struct procfs_dir_priv_s *)dir; + level->index = 0; + return OK; +} + +/**************************************************************************** + * Name: pressure_stat + ****************************************************************************/ + +static int pressure_stat(const char *relpath, struct stat *buf) +{ + memset(buf, 0, sizeof(struct stat)); + + if (strcmp(relpath, "pressure") == 0 || strcmp(relpath, "pressure/") == 0) + { + buf->st_mode = S_IFDIR | S_IROTH | S_IRGRP | S_IRUSR; + } + else if (strcmp(relpath, "pressure/memory") == 0) + { + buf->st_mode = S_IFREG | S_IROTH | S_IRGRP | S_IRUSR | S_IWOTH | + S_IWGRP | S_IWUSR; + } + else + { + ferr("ERROR: No such file or directory: %s\n", relpath); + return -ENOENT; + } + + return OK; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * mm_notify_pressure + ****************************************************************************/ + +void mm_notify_pressure(size_t remaining) +{ + clock_t current = clock_systime_ticks(); + FAR dq_entry_t *entry; + FAR dq_entry_t *tmp; + uint32_t flags; + + flags = spin_lock_irqsave(&g_pressure_lock); + g_remaining = remaining; + dq_for_every_safe(&g_pressure_memory_queue, entry, tmp) + { + FAR struct pressure_file_s *pressure = + container_of(entry, struct pressure_file_s, entry); + + if (remaining > pressure->threshold) + { + continue; + } + + /* If lasttick is CLOCK_MAX, it means that the event is triggered + * for the first time and we should always send notifications. + */ + + if (pressure->lasttick != CLOCK_MAX && current - pressure->lasttick < + pressure->interval) + { + continue; + } + + /* If fds is NULL, it means no one is listening for the event and + * we should delay sending the notification. + */ + + if (pressure->fds == NULL) + { + continue; + } + + pressure->lasttick = current; + spin_unlock_irqrestore(&g_pressure_lock, flags); + poll_notify(&pressure->fds, 1, POLLPRI); + flags = spin_lock_irqsave(&g_pressure_lock); + } + + spin_unlock_irqrestore(&g_pressure_lock, flags); +} + diff --git a/include/nuttx/mm/mm.h b/include/nuttx/mm/mm.h index 6ba6891176..05149705ee 100644 --- a/include/nuttx/mm/mm.h +++ b/include/nuttx/mm/mm.h @@ -432,6 +432,14 @@ FAR void kmm_checkcorruption(void); #endif /* CONFIG_DEBUG_MM */ +/* Functions contained in fs_procfspressure.c *******************************/ + +#ifdef CONFIG_FS_PROCFS_INCLUDE_PRESSURE +void mm_notify_pressure(size_t remaining); +#else +# define mm_notify_pressure(remaining) +#endif + #undef EXTERN #ifdef __cplusplus }