Linux preempt-rt

Check our new training course

Real-Time Linux with PREEMPT_RT

Check our new training course
with Creative Commons CC-BY-SA
lecture and lab materials

Bootlin logo

Elixir Cross Referencer

/* int_latency_bench.c - interrupt latency benchmark support */

/*
 * Copyright (c) 2012-2015 Wind River Systems, Inc.
 *
 * Licensed 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.
 */

#include "toolchain.h"
#include "sections.h"
#include <stdint.h>	    /* uint32_t */
#include <limits.h>	    /* ULONG_MAX */
#include <misc/printk.h> /* printk */
#include <sys_clock.h>
#include <drivers/system_timer.h>

#define NB_CACHE_WARMING_DRY_RUN 7

/*
 * Timestamp corresponding to when interrupt were turned off.
 * A value of zero indicated interrupt are not currently locked.
 */
static uint32_t int_locked_timestamp;

/* stats tracking the minimum and maximum time when interrupts were locked */
static uint32_t int_locked_latency_min = ULONG_MAX;
static uint32_t int_locked_latency_max;

/* overhead added to intLock/intUnlock by this latency benchmark */
static uint32_t initial_start_delay;
static uint32_t nesting_delay;
static uint32_t stop_delay;

/* counter tracking intLock/intUnlock calls once interrupt are locked */
static uint32_t int_lock_unlock_nest;

/* indicate if the interrupt latency benchamrk is ready to be used */
static uint32_t int_latency_bench_ready;

/* min amount of time it takes from HW interrupt generation to 'C' handler */
uint32_t _hw_irq_to_c_handler_latency = ULONG_MAX;

/**
 *
 * @brief Start tracking time spent with interrupts locked
 *
 * calls to lock interrupt can nest, so this routine can be called numerous
 * times before interrupt are unlocked
 *
 * @return N/A
 *
 */
void _int_latency_start(void)
{
	/* when interrupts are not already locked, take time stamp */
	if (!int_locked_timestamp && int_latency_bench_ready) {
		int_locked_timestamp = sys_cycle_get_32();
		int_lock_unlock_nest = 0;
	}
	int_lock_unlock_nest++;
}

/**
 *
 * @brief Stop accumulating time spent for when interrupts are locked
 *
 * This is only call once when the interrupt are being reenabled
 *
 * @return N/A
 *
 */
void _int_latency_stop(void)
{
	uint32_t delta;
	uint32_t delayOverhead;
	uint32_t currentTime = sys_cycle_get_32();

	/* ensured intLatencyStart() was invoked first */
	if (int_locked_timestamp) {
		/*
		 * time spent with interrupt lock is:
		 * (current time - time when interrupt got disabled first) -
		 * (delay when invoking start + number nested calls to intLock *
		 * time it takes to call intLatencyStart + intLatencyStop)
		 */
		delta = (currentTime - int_locked_timestamp);

		/*
		 * Substract overhead introduce by the int latency benchmark
		 * only if
		 * it is bigger than delta.  It can be possible sometimes for
		 * delta to
		 * be smaller than the estimated overhead.
		 */
		delayOverhead =
			(initial_start_delay +
			 ((int_lock_unlock_nest - 1) * nesting_delay) + stop_delay);
		if (delta >= delayOverhead)
			delta -= delayOverhead;

		/* update max */
		if (delta > int_locked_latency_max)
			int_locked_latency_max = delta;

		/* update min */
		if (delta < int_locked_latency_min)
			int_locked_latency_min = delta;

		/* interrupts are now enabled, get ready for next interrupt lock
		 */
		int_locked_timestamp = 0;
	}
}

/**
 *
 * @brief Initialize interrupt latency benchmark
 *
 * @return N/A
 *
 */
void int_latency_init(void)
{
	uint32_t timeToReadTime;
	uint32_t cacheWarming = NB_CACHE_WARMING_DRY_RUN;

	int_latency_bench_ready = 1;

	/*
	 * measuring delay introduced by the interrupt latency benchmark few
	 * times to ensure we get the best possible values. The overhead of
	 * invoking the latency can changes runtime (i.e. cache hit or miss)
	 * but an estimated overhead is used to adjust Max interrupt latency.
	 * The overhead introduced by benchmark is composed of three values:
	 * initial_start_delay, nesting_delay, stop_delay.
	 */
	while (cacheWarming) {
		/* measure how much time it takes to read time */
		timeToReadTime = sys_cycle_get_32();
		timeToReadTime = sys_cycle_get_32() - timeToReadTime;

		/* measure time to call intLatencyStart() and intLatencyStop
		 * takes
		 */
		initial_start_delay = sys_cycle_get_32();
		_int_latency_start();
		initial_start_delay =
			sys_cycle_get_32() - initial_start_delay - timeToReadTime;

		nesting_delay = sys_cycle_get_32();
		_int_latency_start();
		nesting_delay = sys_cycle_get_32() - nesting_delay - timeToReadTime;

		stop_delay = sys_cycle_get_32();
		_int_latency_stop();
		stop_delay = sys_cycle_get_32() - stop_delay - timeToReadTime;

		/* re-initialize globals to default values */
		int_locked_latency_min = ULONG_MAX;
		int_locked_latency_max = 0;

		cacheWarming--;
	}
}

/**
 *
 * @brief Dumps interrupt latency values
 *
 * The interrupt latency value measures
 *
 * @return N/A
 *
 */
void int_latency_show(void)
{
	uint32_t intHandlerLatency = 0;

	if (!int_latency_bench_ready) {
		printk("error: int_latency_init() has not been invoked\n");
		return;
	}

	if (int_locked_latency_min != ULONG_MAX) {
		if (_hw_irq_to_c_handler_latency == ULONG_MAX) {
			intHandlerLatency = 0;
			printk(" Min latency from hw interrupt up to 'C' int. "
			       "handler: "
			       "not measured\n");
		} else {
			intHandlerLatency = _hw_irq_to_c_handler_latency;
			printk(" Min latency from hw interrupt up to 'C' int. "
			       "handler:"
			       " %d tcs = %d nsec\n",
			       intHandlerLatency,
			       SYS_CLOCK_HW_CYCLES_TO_NS(intHandlerLatency));
		}

		printk(" Max interrupt latency (includes hw int. to 'C' "
		       "handler):"
		       " %d tcs = %d nsec\n",
		       int_locked_latency_max + intHandlerLatency,
		       SYS_CLOCK_HW_CYCLES_TO_NS(int_locked_latency_max + intHandlerLatency));

		printk(" Overhead substracted from Max int. latency:\n"
		       "  for int. lock           : %d tcs = %d nsec\n"
		       "  each time int. lock nest: %d tcs = %d nsec\n"
		       "  for int. unlocked       : %d tcs = %d nsec\n",
		       initial_start_delay,
		       SYS_CLOCK_HW_CYCLES_TO_NS(initial_start_delay),
		       nesting_delay,
		       SYS_CLOCK_HW_CYCLES_TO_NS(nesting_delay),
		       stop_delay,
		       SYS_CLOCK_HW_CYCLES_TO_NS(stop_delay));
	} else {
		printk("interrupts were not locked and unlocked yet\n");
	}
	/*
	 * Lets start with new values so that one extra long path executed
	 * with interrupt disabled hide smaller paths with interrupt
	 * disabled.
	 */
	int_locked_latency_min = ULONG_MAX;
	int_locked_latency_max = 0;
}