| /**************************************************************************** |
| * sched/sched/sched_critmonitor.c |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| * |
| * 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 <nuttx/config.h> |
| |
| #include <sys/types.h> |
| #include <sched.h> |
| #include <assert.h> |
| #include <debug.h> |
| #include <time.h> |
| |
| #include "sched/sched.h" |
| |
| /**************************************************************************** |
| * Pre-processor Definitions |
| ****************************************************************************/ |
| |
| #if CONFIG_SCHED_CRITMONITOR_MAXTIME_PREEMPTION > 0 |
| # define CHECK_PREEMPTION(pid, elapsed) \ |
| do \ |
| { \ |
| if (pid > 0 && \ |
| elapsed > CONFIG_SCHED_CRITMONITOR_MAXTIME_PREEMPTION) \ |
| { \ |
| CRITMONITOR_PANIC("PID %d hold sched lock too long %"PRIu32"\n", \ |
| pid, elapsed); \ |
| } \ |
| } \ |
| while (0) |
| #else |
| # define CHECK_PREEMPTION(pid, elapsed) |
| #endif |
| |
| #if CONFIG_SCHED_CRITMONITOR_MAXTIME_CSECTION > 0 |
| # define CHECK_CSECTION(pid, elapsed) \ |
| do \ |
| { \ |
| if (pid > 0 && \ |
| elapsed > CONFIG_SCHED_CRITMONITOR_MAXTIME_CSECTION) \ |
| { \ |
| CRITMONITOR_PANIC("PID %d hold critical section too long %" \ |
| PRIu32 "\n", pid, elapsed); \ |
| } \ |
| } \ |
| while (0) |
| #else |
| # define CHECK_CSECTION(pid, elapsed) |
| #endif |
| |
| #if CONFIG_SCHED_CRITMONITOR_MAXTIME_THREAD > 0 |
| # define CHECK_THREAD(pid, elapsed) \ |
| do \ |
| { \ |
| if (pid > 0 && \ |
| elapsed > CONFIG_SCHED_CRITMONITOR_MAXTIME_THREAD) \ |
| { \ |
| CRITMONITOR_PANIC("PID %d execute too long %"PRIu32"\n", \ |
| pid, elapsed); \ |
| } \ |
| } \ |
| while (0) |
| #else |
| # define CHECK_THREAD(pid, elapsed) |
| #endif |
| |
| /**************************************************************************** |
| * Public Data |
| ****************************************************************************/ |
| |
| /* Maximum time with pre-emption disabled or within critical section. */ |
| |
| #if CONFIG_SCHED_CRITMONITOR_MAXTIME_PREEMPTION >= 0 |
| clock_t g_preemp_max[CONFIG_SMP_NCPUS]; |
| #endif |
| |
| #if CONFIG_SCHED_CRITMONITOR_MAXTIME_CSECTION >= 0 |
| clock_t g_crit_max[CONFIG_SMP_NCPUS]; |
| #endif |
| |
| /**************************************************************************** |
| * Private Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: nxsched_critmon_cpuload |
| * |
| * Description: |
| * Update the running time of all running threads when switching threads |
| * |
| * Input Parameters: |
| * tcb - The task that we are performing the load operations on. |
| * current - The current time |
| * tick - The ticks that we process in this cpuload. |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_SCHED_CPULOAD_CRITMONITOR |
| static void nxsched_critmon_cpuload(FAR struct tcb_s *tcb, clock_t current, |
| clock_t tick) |
| { |
| int i; |
| UNUSED(i); |
| |
| /* Update the cpuload of the thread ready to be suspended */ |
| |
| nxsched_process_taskload_ticks(tcb, tick); |
| |
| /* Update the cpuload of threads running on other CPUs */ |
| |
| # ifdef CONFIG_SMP |
| for (i = 0; i < CONFIG_SMP_NCPUS; i++) |
| { |
| FAR struct tcb_s *rtcb = current_task(i); |
| |
| if (tcb->cpu == rtcb->cpu) |
| { |
| continue; |
| } |
| |
| nxsched_process_taskload_ticks(rtcb, tick); |
| |
| /* Update start time, avoid repeated statistics when the next call */ |
| |
| rtcb->run_start = current; |
| } |
| # endif |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Public Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: nxsched_critmon_preemption |
| * |
| * Description: |
| * Called when there is any change in pre-emptible state of a thread. |
| * |
| * Assumptions: |
| * - Called within a critical section. |
| * - Never called from an interrupt handler |
| * - Caller is the address of the function that is changing the pre-emption |
| * |
| ****************************************************************************/ |
| |
| #if CONFIG_SCHED_CRITMONITOR_MAXTIME_PREEMPTION >= 0 |
| void nxsched_critmon_preemption(FAR struct tcb_s *tcb, bool state, |
| FAR void *caller) |
| { |
| clock_t current = perf_gettime(); |
| |
| /* Are we enabling or disabling pre-emption */ |
| |
| if (state) |
| { |
| /* Disabling.. Save the thread start time */ |
| |
| tcb->preemp_start = current; |
| tcb->preemp_caller = caller; |
| } |
| else |
| { |
| /* Re-enabling.. Check for the max elapsed time */ |
| |
| clock_t elapsed = current - tcb->preemp_start; |
| int cpu = this_cpu(); |
| |
| if (elapsed > tcb->preemp_max) |
| { |
| tcb->preemp_max = elapsed; |
| tcb->preemp_max_caller = tcb->preemp_caller; |
| CHECK_PREEMPTION(tcb->pid, elapsed); |
| } |
| |
| /* Check for the global max elapsed time */ |
| |
| if (elapsed > g_preemp_max[cpu]) |
| { |
| g_preemp_max[cpu] = elapsed; |
| } |
| } |
| } |
| #endif /* CONFIG_SCHED_CRITMONITOR_MAXTIME_PREEMPTION >= 0 */ |
| |
| /**************************************************************************** |
| * Name: nxsched_critmon_csection |
| * |
| * Description: |
| * Called when a thread enters or leaves a critical section. |
| * |
| * Assumptions: |
| * - Called within a critical section. |
| * - Never called from an interrupt handler |
| * - Caller is the address of the function that is entering the critical |
| * |
| ****************************************************************************/ |
| |
| #if CONFIG_SCHED_CRITMONITOR_MAXTIME_CSECTION >= 0 |
| void nxsched_critmon_csection(FAR struct tcb_s *tcb, bool state, |
| FAR void *caller) |
| { |
| clock_t current = perf_gettime(); |
| |
| /* Are we entering or leaving the critical section? */ |
| |
| if (state) |
| { |
| /* Entering... Save the start time. */ |
| |
| tcb->crit_start = current; |
| tcb->crit_caller = caller; |
| } |
| else |
| { |
| /* Leaving .. Check for the max elapsed time */ |
| |
| clock_t elapsed = current - tcb->crit_start; |
| int cpu = this_cpu(); |
| |
| if (elapsed > tcb->crit_max) |
| { |
| tcb->crit_max = elapsed; |
| tcb->crit_max_caller = tcb->crit_caller; |
| CHECK_CSECTION(tcb->pid, elapsed); |
| } |
| |
| /* Check for the global max elapsed time */ |
| |
| if (elapsed > g_crit_max[cpu]) |
| { |
| g_crit_max[cpu] = elapsed; |
| } |
| } |
| } |
| #endif /* CONFIG_SCHED_CRITMONITOR_MAXTIME_CSECTION >= 0 */ |
| |
| /**************************************************************************** |
| * Name: nxsched_switch_critmon |
| * |
| * Description: |
| * Called when a thread is switched, update the critical monitor data. |
| * |
| * Input Parameters: |
| * from - The thread that is being switched out. |
| * to - The thread that is being switched in. |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| void nxsched_switch_critmon(FAR struct tcb_s *from, FAR struct tcb_s *to) |
| { |
| clock_t current = perf_gettime(); |
| clock_t elapsed = current - from->run_start; |
| |
| #ifdef CONFIG_SCHED_CPULOAD_CRITMONITOR |
| clock_t tick = elapsed * CLOCKS_PER_SEC / perf_getfreq(); |
| nxsched_critmon_cpuload(from, current, tick); |
| #endif |
| |
| #if CONFIG_SCHED_CRITMONITOR_MAXTIME_THREAD >= 0 |
| from->run_time += elapsed; |
| to->run_time = current; |
| if (elapsed > from->run_max) |
| { |
| from->run_max = elapsed; |
| CHECK_THREAD(from->pid, elapsed); |
| } |
| #endif |
| |
| #if CONFIG_SCHED_CRITMONITOR_MAXTIME_PREEMPTION >= 0 |
| |
| /* Did this task disable preemption? */ |
| |
| if (nxsched_islocked_tcb(from)) |
| { |
| int cpu = this_cpu(); |
| |
| /* Possibly re-enabling.. Check for the max elapsed time */ |
| |
| elapsed = current - from->preemp_start; |
| if (elapsed > from->preemp_max) |
| { |
| from->preemp_max = elapsed; |
| from->preemp_max_caller = from->preemp_caller; |
| CHECK_PREEMPTION(from->pid, elapsed); |
| } |
| |
| /* Suspend percore preemptible statistic and if necessary will |
| * re-open in nxsched_resume_critmon. |
| */ |
| |
| if (elapsed > g_preemp_max[cpu]) |
| { |
| g_preemp_max[cpu] = elapsed; |
| } |
| } |
| |
| if (to->lockcount > 0) |
| { |
| to->premp_start = current; |
| } |
| #endif /* CONFIG_SCHED_CRITMONITOR_MAXTIME_PREEMPTION */ |
| |
| #if CONFIG_SCHED_CRITMONITOR_MAXTIME_CSECTION >= 0 |
| |
| /* Is this task in a critical section? */ |
| |
| if (from->irqcount > 0) |
| { |
| int cpu = this_cpu(); |
| |
| /* Possibly leaving .. Check for the max elapsed time */ |
| |
| elapsed = current - from->crit_start; |
| if (elapsed > from->crit_max) |
| { |
| from->crit_max = elapsed; |
| from->crit_max_caller = from->crit_caller; |
| CHECK_CSECTION(from->pid, elapsed); |
| } |
| |
| /* Check for the global max elapsed time */ |
| |
| if (elapsed > g_crit_max[cpu]) |
| { |
| g_crit_max[cpu] = elapsed; |
| } |
| } |
| |
| if (to->irqcount > 0) |
| { |
| to->crit_start = current; |
| } |
| |
| #endif /* CONFIG_SCHED_CRITMONITOR_MAXTIME_CSECTION */ |
| |
| #if CONFIG_SCHED_CRITMONITOR_MAXTIME_PREEMPTION >= 0 |
| |
| /* Did this task disable pre-emption? */ |
| |
| if (to->lockcount > 0) |
| { |
| /* Yes.. Save the start time */ |
| |
| to->premp_start = current; |
| } |
| #endif /* CONFIG_SCHED_CRITMONITOR_MAXTIME_PREEMPTION */ |
| } |
| |
| void nxsched_update_critmon(FAR struct tcb_s *tcb) |
| { |
| clock_t current = perf_gettime(); |
| clock_t elapsed = current - tcb->run_start; |
| |
| if (tcb->task_state != TSTATE_TASK_RUNNING) |
| { |
| return; |
| } |
| |
| #ifdef CONFIG_SCHED_CPULOAD_CRITMONITOR |
| clock_t tick = elapsed * CLOCKS_PER_SEC / perf_getfreq(); |
| nxsched_process_taskload_ticks(tcb, tick); |
| #endif |
| |
| tcb->run_start = current; |
| tcb->run_time += elapsed; |
| if (elapsed > tcb->run_max) |
| { |
| tcb->run_max = elapsed; |
| CHECK_THREAD(tcb->pid, elapsed); |
| } |
| } |