diff --git a/configs/stm32f4discovery/src/stm32_critmon.c b/configs/stm32f4discovery/src/stm32_critmon.c index 38e20b31dae..5456b9854a9 100644 --- a/configs/stm32f4discovery/src/stm32_critmon.c +++ b/configs/stm32f4discovery/src/stm32_critmon.c @@ -51,7 +51,7 @@ #ifdef CONFIG_SCHED_CRITMONITOR /************************************************************************************ - * Public Functions + * Pre-processor Definitions ************************************************************************************/ /* Cycle count register in the Data Watchpoint and Trace (DWT) Unit */ diff --git a/fs/procfs/Make.defs b/fs/procfs/Make.defs index c322f215ffe..c728d56fe4b 100644 --- a/fs/procfs/Make.defs +++ b/fs/procfs/Make.defs @@ -40,6 +40,10 @@ ASRCS += CSRCS += fs_procfs.c fs_procfsutil.c fs_procfsproc.c fs_procfsuptime.c CSRCS += fs_procfscpuload.c fs_procfsmeminfo.c fs_procfsversion.c +ifeq ($(CONFIG_SCHED_CRITMONITOR),y) +CSRCS += fs_procfscritmon.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 de73dfba990..1d0eecf0807 100644 --- a/fs/procfs/fs_procfs.c +++ b/fs/procfs/fs_procfs.c @@ -73,12 +73,13 @@ #define PROCFS_NATTRS 2 /**************************************************************************** - * External Definitons + * External Definitions ****************************************************************************/ extern const struct procfs_operations proc_operations; extern const struct procfs_operations irq_operations; extern const struct procfs_operations cpuload_operations; +extern const struct procfs_operations critmon_operations; extern const struct procfs_operations meminfo_operations; extern const struct procfs_operations module_operations; extern const struct procfs_operations uptime_operations; @@ -125,6 +126,10 @@ static const struct procfs_entry_s g_procfs_entries[] = { "cpuload", &cpuload_operations, PROCFS_FILE_TYPE }, #endif +#if defined(CONFIG_SCHED_CRITMONITOR) + { "critmon", &critmon_operations, PROCFS_FILE_TYPE }, +#endif + #ifdef CONFIG_SCHED_IRQMONITOR { "irqs", &irq_operations, PROCFS_FILE_TYPE }, #endif diff --git a/fs/procfs/fs_procfscritmon.c b/fs/procfs/fs_procfscritmon.c new file mode 100644 index 00000000000..45c784d2993 --- /dev/null +++ b/fs/procfs/fs_procfscritmon.c @@ -0,0 +1,403 @@ +/**************************************************************************** + * fs/procfs/fs_procfscritmon.c + * + * Copyright (C) 2018 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#if !defined(CONFIG_DISABLE_MOUNTPOINT) && defined(CONFIG_FS_PROCFS) && \ + defined(CONFIG_SCHED_CRITMONITOR) + +/**************************************************************************** + * 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 CRITMON_LINELEN 64 + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/* This structure describes one open "file" */ + +struct critmon_file_s +{ + struct procfs_file_s base; /* Base open file structure */ + unsigned int linesize; /* Number of valid characters in line[] */ + char line[CRITMON_LINELEN]; /* Pre-allocated buffer for formatted lines */ +}; + +/**************************************************************************** + * External Function Prototypes + ****************************************************************************/ + +/* If CONFIG_SCHED_CRITMONITOR is selected, then platform-specific logic + * must provide the following interface. This function converts platform- + * specific elapsed time into a well-known time format. + */ + +void up_critmon_convert(uint32_t elapsed, FAR struct timespec *ts); + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/* File system methods */ + +static int critmon_open(FAR struct file *filep, FAR const char *relpath, + int oflags, mode_t mode); +static int critmon_close(FAR struct file *filep); +static ssize_t critmon_read(FAR struct file *filep, FAR char *buffer, + size_t buflen); +static int critmon_dup(FAR const struct file *oldp, + FAR struct file *newp); +static int critmon_stat(FAR const char *relpath, FAR struct stat *buf); + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +/* See fs_mount.c -- this structure is explicitly externed there. + * We use the old-fashioned kind of initializers so that this will compile + * with any compiler. + */ + +const struct procfs_operations critmon_operations = +{ + critmon_open, /* open */ + critmon_close, /* close */ + critmon_read, /* read */ + NULL, /* write */ + + critmon_dup, /* dup */ + + NULL, /* opendir */ + NULL, /* closedir */ + NULL, /* readdir */ + NULL, /* rewinddir */ + + critmon_stat /* stat */ +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: critmon_open + ****************************************************************************/ + +static int critmon_open(FAR struct file *filep, FAR const char *relpath, + int oflags, mode_t mode) +{ + FAR struct critmon_file_s *attr; + + finfo("Open '%s'\n", relpath); + + /* PROCFS is read-only. Any attempt to open with any kind of write + * access is not permitted. + * + * REVISIT: Write-able proc files could be quite useful. + */ + + if ((oflags & O_WRONLY) != 0 || (oflags & O_RDONLY) == 0) + { + ferr("ERROR: Only O_RDONLY supported\n"); + return -EACCES; + } + + /* "critmon" is the only acceptable value for the relpath */ + + if (strcmp(relpath, "critmon") != 0) + { + ferr("ERROR: relpath is '%s'\n", relpath); + return -ENOENT; + } + + /* Allocate a container to hold the file attributes */ + + attr = (FAR struct critmon_file_s *)kmm_zalloc(sizeof(struct critmon_file_s)); + if (!attr) + { + ferr("ERROR: Failed to allocate file attributes\n"); + return -ENOMEM; + } + + /* Save the attributes as the open-specific state in filep->f_priv */ + + filep->f_priv = (FAR void *)attr; + return OK; +} + +/**************************************************************************** + * Name: critmon_close + ****************************************************************************/ + +static int critmon_close(FAR struct file *filep) +{ + FAR struct critmon_file_s *attr; + + /* Recover our private data from the struct file instance */ + + attr = (FAR struct critmon_file_s *)filep->f_priv; + DEBUGASSERT(attr); + + /* Release the file attributes structure */ + + kmm_free(attr); + filep->f_priv = NULL; + return OK; +} + +/**************************************************************************** + * Name: critmon_read_cpu + ****************************************************************************/ + +static ssize_t critmon_read_cpu(FAR struct critmon_file_s *attr, + FAR char *buffer, size_t buflen, + FAR off_t *offset, int cpu) +{ + struct timespec maxtime; + size_t remaining; + size_t linesize; + size_t copysize; + size_t totalsize; + + remaining = buflen; + totalsize = 0; + + /* Convert the for maximum time pre-emption disabled */ + + if (g_premp_max[cpu] > 0) + { + up_critmon_convert(g_premp_max[cpu], &maxtime); + } + else + { + maxtime.tv_sec = 0; + maxtime.tv_nsec = 0; + } + + /* Reset the maximum */ + + g_premp_max[cpu] = 0; + + /* Generate output for maximum time pre-emption disabled */ + + linesize = snprintf(attr->line, CRITMON_LINELEN, "%2d,%lu.%09lu,", + cpu, (unsigned long)maxtime.tv_sec, + (unsigned long)maxtime.tv_nsec); + copysize = procfs_memcpy(attr->line, linesize, buffer, buflen, offset); + + totalsize += copysize; + buffer += copysize; + remaining -= copysize; + + if (totalsize >= buflen) + { + return totalsize; + } + + /* Convert and generate output for maximum time in a critical section */ + + if (g_crit_max[cpu] > 0) + { + up_critmon_convert(g_crit_max[cpu], &maxtime); + } + else + { + maxtime.tv_sec = 0; + maxtime.tv_nsec = 0; + } + + /* Reset the maximum */ + + g_crit_max[cpu] = 0; + + /* Generate output for maximum time in a critical section */ + + linesize = snprintf(attr->line, CRITMON_LINELEN, "%lu.%09lu\n", + (unsigned long)maxtime.tv_sec, + (unsigned long)maxtime.tv_nsec); + copysize = procfs_memcpy(attr->line, linesize, buffer, buflen, offset); + + totalsize += copysize; + return totalsize; +} + +/**************************************************************************** + * Name: critmon_read + ****************************************************************************/ + +static ssize_t critmon_read(FAR struct file *filep, FAR char *buffer, + size_t buflen) +{ + FAR struct critmon_file_s *attr; + off_t offset; + ssize_t ret; +#ifdef CONFIG_SMP + int cpu; +#endif + + finfo("buffer=%p buflen=%d\n", buffer, (int)buflen); + + /* Recover our private data from the struct file instance */ + + attr = (FAR struct critmon_file_s *)filep->f_priv; + DEBUGASSERT(attr); + + ret = 0; + +#ifdef CONFIG_SMP + /* Get the status for each CPU */ + + for (cpu = 0; cpu < CONFIG_SMP_NCPUS; cpu++) + { + ret += critmon_read_cpu(attr, buffer + ret, buflen -ret, + &offset, cpu); + if (ret > buflen) + { + break; + } + } + +#else + /* Get status for the single CPU */ + + ret = critmon_read_cpu(attr, buffer + ret, buflen -ret, &offset, 0); +#endif + + if (ret > 0) + { + filep->f_pos += ret; + } + + return ret; +} + +/**************************************************************************** + * Name: critmon_dup + * + * Description: + * Duplicate open file data in the new file structure. + * + ****************************************************************************/ + +static int critmon_dup(FAR const struct file *oldp, FAR struct file *newp) +{ + FAR struct critmon_file_s *oldattr; + FAR struct critmon_file_s *newattr; + + finfo("Dup %p->%p\n", oldp, newp); + + /* Recover our private data from the old struct file instance */ + + oldattr = (FAR struct critmon_file_s *)oldp->f_priv; + DEBUGASSERT(oldattr); + + /* Allocate a new container to hold the task and attribute selection */ + + newattr = (FAR struct critmon_file_s *)kmm_malloc(sizeof(struct critmon_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 critmon_file_s)); + + /* Save the new attributes in the new file structure */ + + newp->f_priv = (FAR void *)newattr; + return OK; +} + +/**************************************************************************** + * Name: critmon_stat + * + * Description: Return information about a file or directory + * + ****************************************************************************/ + +static int critmon_stat(const char *relpath, struct stat *buf) +{ + /* "critmon" is the only acceptable value for the relpath */ + + if (strcmp(relpath, "critmon") != 0) + { + ferr("ERROR: relpath is '%s'\n", relpath); + return -ENOENT; + } + + /* "critmon" is the name for a read-only file */ + + memset(buf, 0, sizeof(struct stat)); + buf->st_mode = S_IFREG | S_IROTH | S_IRGRP | S_IRUSR; + return OK; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +#endif /* !CONFIG_DISABLE_MOUNTPOINT && CONFIG_FS_PROCFS && CONFIG_SCHED_CRITMONITOR */ diff --git a/include/nuttx/sched.h b/include/nuttx/sched.h index e564e21409d..0ffc612bad4 100644 --- a/include/nuttx/sched.h +++ b/include/nuttx/sched.h @@ -809,6 +809,18 @@ extern "C" #define EXTERN extern #endif +#ifdef CONFIG_SCHED_CRITMONITOR +/* Maximum time with pre-emption disabled or within critical section. */ + +#ifdef CONFIG_SMP_NCPUS +EXTERN uint32_t g_premp_max[CONFIG_SMP_NCPUS]; +EXTERN uint32_t g_crit_max[CONFIG_SMP_NCPUS]; +#else +EXTERN uint32_t g_premp_max[1]; +EXTERN uint32_t g_crit_max[1]; +#endif +#endif /* CONFIG_SCHED_CRITMONITOR */ + /******************************************************************************** * Public Function Prototypes ********************************************************************************/ diff --git a/sched/sched/sched_critmonitor.c b/sched/sched/sched_critmonitor.c index c6ffcb01911..95dace58357 100644 --- a/sched/sched/sched_critmonitor.c +++ b/sched/sched/sched_critmonitor.c @@ -49,17 +49,17 @@ /* Flags used by the critical section monitor */ -#define CRITMON_PREEMPT (1 << 0) /* Bit 0: Pre-emption is disabled */ -#define CRITMON_CSECTION (1 << 1) /* Bit 1: In a critical section */ +#define CRITMON_PREEMPT (1 << 0) /* Bit 0: Pre-emption is disabled */ +#define CRITMON_CSECTION (1 << 1) /* Bit 1: In a critical section */ -#define DISABLE_PREEMPT(t) do { (t)->flags |= CRITMON_PREEMPT; } while (0) -#define ENTER_CSECTION(t) do { (t)->flags |= CRITMON_PREEMPT; } while (0) +#define DISABLE_PREEMPT(t) do { (t)->flags |= CRITMON_PREEMPT; } while (0) +#define ENTER_CSECTION(t) do { (t)->flags |= CRITMON_PREEMPT; } while (0) -#define ENABLE_PREEMPT(t) do { (t)->flags &= ~CRITMON_PREEMPT; } while (0) -#define LEAVE_CSECTION(t) do { (t)->flags &= ~CRITMON_PREEMPT; } while (0) +#define ENABLE_PREEMPT(t) do { (t)->flags &= ~CRITMON_PREEMPT; } while (0) +#define LEAVE_CSECTION(t) do { (t)->flags &= ~CRITMON_PREEMPT; } while (0) -#define PREEMPT_DISABLED(t) (((t)->crit_flags & CRITMON_PREEMPT) != 0) -#define IN_CSECTION(t) (((t)->crit_flags & CRITMON_CSECTION) != 0) +#define PREEMPT_ISDISABLED(t) (((t)->crit_flags & CRITMON_PREEMPT) != 0) +#define IN_CSECTION(t) (((t)->crit_flags & CRITMON_CSECTION) != 0) /**************************************************************************** * External Function Prototypes @@ -80,6 +80,34 @@ uint32_t up_critmon_gettime(void); +/************************************************************************************ + * Private Data + ************************************************************************************/ + +/* Start time when pre-emption disabled or critical section entered. */ + +#ifdef CONFIG_SMP_NCPUS +static uint32_t g_premp_start[CONFIG_SMP_NCPUS]; +static uint32_t g_crit_start[CONFIG_SMP_NCPUS]; +#else +static uint32_t g_premp_start[1]; +static uint32_t g_crit_start[1]; +#endif + +/************************************************************************************ + * Public Data + ************************************************************************************/ + +/* Maximum time with pre-emption disabled or within critical section. */ + +#ifdef CONFIG_SMP_NCPUS +uint32_t g_premp_max[CONFIG_SMP_NCPUS]; +uint32_t g_crit_max[CONFIG_SMP_NCPUS]; +#else +uint32_t g_premp_max[1]; +uint32_t g_crit_max[1]; +#endif + /**************************************************************************** * Public Functions ****************************************************************************/ @@ -97,24 +125,31 @@ uint32_t up_critmon_gettime(void); void sched_critmon_preemption(FAR struct tcb_s *tcb, bool state) { + int cpu = this_cpu(); + /* Are we enabling or disabling pre-emption */ if (state) { - DEBUGASSERT(tcb->premp_start == 0 && !PREEMPT_DISABLED(tcb)); + DEBUGASSERT(tcb->premp_start == 0 && !PREEMPT_ISDISABLED(tcb)); - /* Disabling.. Save the start time */ + /* Disabling.. Save the thread start time */ tcb->premp_start = up_critmon_gettime(); DISABLE_PREEMPT(tcb); + + /* Save the global start time */ + + g_premp_start[cpu] = tcb->premp_start; } else if (tcb->premp_start != 0) { - DEBUGASSERT(PREEMPT_DISABLED(tcb)); + DEBUGASSERT(PREEMPT_ISDISABLED(tcb)); /* Re-enabling.. Check for the max elapsed time */ - uint32_t elapsed = tcb->premp_start - up_critmon_gettime(); + uint32_t now = up_critmon_gettime(); + uint32_t elapsed = now - tcb->premp_start; tcb->premp_start = 0; if (elapsed > tcb->premp_max) @@ -123,6 +158,16 @@ void sched_critmon_preemption(FAR struct tcb_s *tcb, bool state) } ENABLE_PREEMPT(tcb); + + /* Check for the global max elapsed time */ + + elapsed = now - g_premp_start[cpu]; + g_premp_start[cpu] = 0; + + if (elapsed > g_premp_max[cpu]) + { + g_premp_max[cpu] = elapsed; + } } } @@ -139,22 +184,27 @@ void sched_critmon_preemption(FAR struct tcb_s *tcb, bool state) void sched_critmon_csection(FAR struct tcb_s *tcb, bool state) { + int cpu = this_cpu(); + /* Are we entering or leaving the critical section? */ if (state) { /* Entering... Save the start time. */ - DEBUGASSERT(tcb->crit_start == 0 && !IN_CSECTION(tcb)); + DEBUGASSERT(tcb->crit_start == 0); tcb->crit_start = up_critmon_gettime(); + + /* Set the global start time */ + + g_crit_start[cpu] = tcb->crit_start; } else if (tcb->crit_start != 0) { - DEBUGASSERT(IN_CSECTION(tcb)); - /* Leaving .. Check for the max elapsed time */ - uint32_t elapsed = tcb->crit_start - up_critmon_gettime(); + uint32_t now = up_critmon_gettime(); + uint32_t elapsed = now - tcb->crit_start; tcb->crit_start = 0; if (elapsed > tcb->crit_max) @@ -163,6 +213,16 @@ void sched_critmon_csection(FAR struct tcb_s *tcb, bool state) } ENTER_CSECTION(tcb); + + /* Check for the global max elapsed time */ + + elapsed = now - g_crit_start[cpu]; + g_crit_start[cpu] = 0; + + if (elapsed > g_crit_max[cpu]) + { + g_crit_max[cpu] = elapsed; + } } } @@ -180,15 +240,34 @@ void sched_critmon_csection(FAR struct tcb_s *tcb, bool state) void sched_critmon_resume(FAR struct tcb_s *tcb) { + uint32_t elapsed; + int cpu = this_cpu(); + DEBUGASSERT(tcb->premp_start == 0 && tcb->crit_start == 0); /* Did this task disable pre-emption? */ - if (PREEMPT_DISABLED(tcb)) + if (PREEMPT_ISDISABLED(tcb)) { /* Yes.. Save the start time */ tcb->premp_start = up_critmon_gettime(); + if (g_premp_start[cpu] == 0) + { + g_premp_start[cpu] = tcb->premp_start; + } + } + else if (g_premp_start[cpu] != 0) + { + /* Check for the global max elapsed time */ + + elapsed = up_critmon_gettime() - g_premp_start[cpu]; + g_premp_start[cpu] = 0; + + if (elapsed > g_premp_max[cpu]) + { + g_premp_max[cpu] = elapsed; + } } /* Was this task in a critical section? */ @@ -198,6 +277,22 @@ void sched_critmon_resume(FAR struct tcb_s *tcb) /* Yes.. Save the start time */ tcb->crit_start = up_critmon_gettime(); + if (g_crit_start[cpu] == 0) + { + g_crit_start[cpu] = tcb->crit_start; + } + } + else if (g_crit_start[cpu] != 0) + { + /* Check for the global max elapsed time */ + + elapsed = up_critmon_gettime() - g_crit_start[cpu]; + g_crit_start[cpu] = 0; + + if (elapsed > g_crit_max[cpu]) + { + g_crit_max[cpu] = elapsed; + } } } @@ -215,13 +310,15 @@ void sched_critmon_resume(FAR struct tcb_s *tcb) void sched_critmon_suspend(FAR struct tcb_s *tcb) { + uint32_t elapsed; + /* Did this task disable pre-emption? */ - if (PREEMPT_DISABLED(tcb)) + if (PREEMPT_ISDISABLED(tcb)) { - /* Re-enabling.. Check for the max elapsed time */ + /* Possibly re-enabling.. Check for the max elapsed time */ - uint32_t elapsed = tcb->premp_start - up_critmon_gettime(); + elapsed = up_critmon_gettime() - tcb->premp_start; tcb->premp_start = 0; if (elapsed > tcb->premp_max) @@ -234,9 +331,9 @@ void sched_critmon_suspend(FAR struct tcb_s *tcb) if (IN_CSECTION(tcb)) { - /* Leaving .. Check for the max elapsed time */ + /* Possibly leaving .. Check for the max elapsed time */ - uint32_t elapsed = tcb->crit_start - up_critmon_gettime(); + elapsed = up_critmon_gettime() - tcb->crit_start; tcb->crit_start = 0; if (elapsed > tcb->crit_max)