/***********************license start***************
* Copyright (c) 2003-2010 Cavium Networks (support@cavium.com). All rights
* reserved.
*
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * 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.
* * Neither the name of Cavium Networks 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, including technical data, may be subject to U.S. export control
* laws, including the U.S. Export Administration Act and its associated
* regulations, and may be subject to export or import regulations in other
* countries.
* TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE SOFTWARE IS PROVIDED "AS IS"
* AND WITH ALL FAULTS AND CAVIUM NETWORKS MAKES NO PROMISES, REPRESENTATIONS OR
* WARRANTIES, EITHER EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, WITH RESPECT TO
* THE SOFTWARE, INCLUDING ITS CONDITION, ITS CONFORMITY TO ANY REPRESENTATION OR
* DESCRIPTION, OR THE EXISTENCE OF ANY LATENT OR PATENT DEFECTS, AND CAVIUM
* SPECIFICALLY DISCLAIMS ALL IMPLIED (IF ANY) WARRANTIES OF TITLE,
* MERCHANTABILITY, NONINFRINGEMENT, FITNESS FOR A PARTICULAR PURPOSE, LACK OF
* VIRUSES, ACCURACY OR COMPLETENESS, QUIET ENJOYMENT, QUIET POSSESSION OR
* CORRESPONDENCE TO DESCRIPTION. THE ENTIRE RISK ARISING OUT OF USE OR
* PERFORMANCE OF THE SOFTWARE LIES WITH YOU.
***********************license end**************************************/
/**
* @file
*
* Interface to the hardware work queue timers.
*
`*
$Revision: 49448 $
*/
#ifndef __CVMX_TIM_H__
#define __CVMX_TIM_H__
#include "cvmx-clock.h"
#include "cvmx-fpa.h"
#include "cvmx-wqe.h"
#include "executive-config.h"
#ifdef CVMX_ENABLE_TIMER_FUNCTIONS
#include "cvmx-config.h"
#endif
#ifdef __cplusplus
extern "C" {
#endif
#define CVMX_TIM_NUM_TIMERS 16
#define CVMX_TIM_NUM_BUCKETS 2048
typedef enum
{
CVMX_TIM_STATUS_SUCCESS = 0,
CVMX_TIM_STATUS_NO_MEMORY = -1,
CVMX_TIM_STATUS_TOO_FAR_AWAY = -2,
CVMX_TIM_STATUS_BUSY = -3
} cvmx_tim_status_t;
/**
* Each timer bucket contains a list of work queue entries to
* schedule when the timer fires. The list is implemented as
* a linked list of blocks. Each block contains an array of
* work queue entries followed by a next block pointer. Since
* these blocks are dynamically allocated off of a hardware
* memory pool, there actual size isn't known compile time.
* The next block pointer is stored in the last 8 bytes of
* the memory block.
*/
typedef struct cvmx_tim_entry_chunk
{
volatile uint64_t entries[0];
} cvmx_tim_entry_chunk_t;
/**
* Each timer contains an array of buckets. Each bucket
* represents the list of work queue entries that should be
* scheduled when the timer fires. The first 3 entries are used
* byt the hardware.
*/
typedef struct
{
volatile uint64_t first_chunk_addr;
volatile uint32_t num_entries; /**< Zeroed by HW after traversing list */
volatile uint32_t chunk_remainder;/**< Zeroed by HW after traversing list */
// the remaining 16 bytes are not touched by hardware
volatile cvmx_tim_entry_chunk_t *last_chunk;
uint64_t pad;
} cvmx_tim_bucket_entry_t;
/**
* Structure representing an individual timer. Each timer has
* a timer period, a memory management pool, and a list of
* buckets.
*/
typedef struct
{
cvmx_tim_bucket_entry_t*bucket; /**< The timer buckets. Array of [CVMX_TIM_NUM_TIMERS][CVMX_TIM_NUM_BUCKETS] */
uint64_t tick_cycles; /**< How long a bucket represents */
uint64_t start_time; /**< Time the timer started in cycles */
uint32_t bucket_shift; /**< How long a bucket represents in ms */
uint32_t num_buckets; /**< How many buckets per wheel */
uint32_t max_ticks; /**< maximum number of ticks allowed for timer */
} cvmx_tim_t;
/**
* Structure used to store state information needed to delete
* an already scheduled timer entry. An instance of this
* structure must be passed to cvmx_tim_add_entry in order
* to be able to delete an entry later with
* cvmx_tim_delete_entry.
*
* NOTE: This structure should be considered opaque by the application,
* and the application should not access its members
*/
typedef struct
{
uint64_t commit_cycles; /**< After this time the timer can't be changed */
uint64_t * timer_entry_ptr;/**< Where the work entry is. Zero this
location to delete the entry */
} cvmx_tim_delete_t;
/**
* Global structure holding the state of all timers.
*/
extern cvmx_tim_t cvmx_tim;
#ifdef CVMX_ENABLE_TIMER_FUNCTIONS
/**
* Setup a timer for use. Must be called before the timer
* can be used.
*
* @param tick Time between each bucket in microseconds. This must not be
* smaller than 1024/(clock frequency in MHz).
* @param max_ticks The maximum number of ticks the timer must be able
* to schedule in the future. There are guaranteed to be enough
* timer buckets such that:
* number of buckets >= max_ticks.
* @return Zero on success. Negative on error. Failures are possible
* if the number of buckets needed is too large or memory
* allocation fails for creating the buckets.
*/
int cvmx_tim_setup(uint64_t tick, uint64_t max_ticks);
#endif
/**
* Start the hardware timer processing
*/
extern void cvmx_tim_start(void);
/**
* Stop the hardware timer processing. Timers stay configured.
*/
extern void cvmx_tim_stop(void);
/**
* Stop the timer. After this the timer must be setup again
* before use.
*/
#ifdef CVMX_ENABLE_TIMER_FUNCTIONS
extern void cvmx_tim_shutdown(void);
#endif
#ifdef CVMX_ENABLE_TIMER_FUNCTIONS
/**
* Add a work queue entry to the timer.
*
* @param work_entry Work queue entry to add.
* @param ticks_from_now
* @param delete_info
* Optional pointer where to store information needed to
* delete the timer entry. If non NULL information needed
* to delete the timer entry before it fires is stored here.
* If you don't need to be able to delete the timer, pass
* NULL.
* @return Result return code
*/
static inline cvmx_tim_status_t cvmx_tim_add_entry(cvmx_wqe_t *work_entry, uint64_t ticks_from_now, cvmx_tim_delete_t *delete_info)
{
cvmx_tim_bucket_entry_t* work_bucket_ptr;
uint64_t current_bucket;
uint64_t work_bucket;
volatile uint64_t * tim_entry_ptr; /* pointer to wqe address in timer chunk */
uint64_t entries_per_chunk;
const uint64_t cycles = cvmx_clock_get_count(CVMX_CLOCK_TIM); /* Get our reference time early for accuracy */
const uint64_t core_num = cvmx_get_core_num(); /* One timer per processor, so use this to select */
/* Make sure the specified time won't wrap our bucket list */
if (ticks_from_now > cvmx_tim.max_ticks)
{
cvmx_dprintf("cvmx_tim_add_entry: Tried to schedule work too far away.\n");
return CVMX_TIM_STATUS_TOO_FAR_AWAY;
}
/* Since we have no way to synchronize, we can't update a timer that is
being used by the hardware. Two buckets forward should be safe */
if (ticks_from_now < 2)
{
cvmx_dprintf("cvmx_tim_add_entry: Tried to schedule work too soon. Delaying it.\n");
ticks_from_now = 2;
}
/* Get the bucket this work queue entry should be in. Remember the bucket
array is circular */
current_bucket = ((cycles - cvmx_tim.start_time)
>> cvmx_tim.bucket_shift);
work_bucket = (((ticks_from_now * cvmx_tim.tick_cycles) + cycles - cvmx_tim.start_time)
>> cvmx_tim.bucket_shift);
work_bucket_ptr = cvmx_tim.bucket + core_num * cvmx_tim.num_buckets + (work_bucket & (cvmx_tim.num_buckets - 1));
entries_per_chunk = (CVMX_FPA_TIMER_POOL_SIZE/8 - 1);
/* Check if we have room to add this entry into the existing list */
if (work_bucket_ptr->chunk_remainder)
{
/* Adding the work entry to the end of the existing list */
tim_entry_ptr = &(work_bucket_ptr->last_chunk->entries[entries_per_chunk - work_bucket_ptr->chunk_remainder]);
*tim_entry_ptr = cvmx_ptr_to_phys(work_entry);
work_bucket_ptr->chunk_remainder--;
work_bucket_ptr->num_entries++;
}
else
{
/* Current list is either completely empty or completely full. We need
to allocate a new chunk for storing this work entry */
cvmx_tim_entry_chunk_t *new_chunk = (cvmx_tim_entry_chunk_t *)cvmx_fpa_alloc(CVMX_FPA_TIMER_POOL);
if (new_chunk == NULL)
{
cvmx_dprintf("cvmx_tim_add_entry: Failed to allocate memory for new chunk.\n");
return CVMX_TIM_STATUS_NO_MEMORY;
}
/* Does a chunk currently exist? We have to check num_entries since
the hardware doesn't NULL out the chunk pointers on free */
if (work_bucket_ptr->num_entries)
{
/* This chunk must be appended to an existing list by putting
** its address in the last spot of the existing chunk. */
work_bucket_ptr->last_chunk->entries[entries_per_chunk] = cvmx_ptr_to_phys(new_chunk);
work_bucket_ptr->num_entries++;
}
else
{
/* This is the very first chunk. Add it */
work_bucket_ptr->first_chunk_addr = cvmx_ptr_to_phys(new_chunk);
work_bucket_ptr->num_entries = 1;
}
work_bucket_ptr->last_chunk = new_chunk;
work_bucket_ptr->chunk_remainder = entries_per_chunk - 1;
tim_entry_ptr = &(new_chunk->entries[0]);
*tim_entry_ptr = cvmx_ptr_to_phys(work_entry);
}
/* If the user supplied a delete info structure then fill it in */
if (delete_info)
{
/* It would be very bad to delete a timer entry after, or during the
timer's processing. During the processing could yield unpredicatable
results, but after would always be bad. Modifying the entry after
processing means we would be changing data in a buffer that has been
freed, and possible allocated again. For this reason we store a
commit cycle count in the delete structure. If we are after this
count we will refuse to delete the timer entry. */
delete_info->commit_cycles = cycles + (ticks_from_now - 2) * cvmx_tim.tick_cycles;
delete_info->timer_entry_ptr = (uint64_t *)tim_entry_ptr; /* Cast to non-volatile type */
}
CVMX_SYNCWS; /* Make sure the hardware timer unit can access valid data from L2 */
return CVMX_TIM_STATUS_SUCCESS;
}
#endif
/**
* Delete a timer entry scheduled using cvmx_tim_add_entry.
* Deleting a timer will fail if it has already triggered or
* might be in progress. The actual state of the work queue
* entry isn't changed. You need to dispose of it properly.
*
* @param delete_info
* Structure passed to cvmx_tim_add_entry to store the
* information needed to delete a timer entry.
* @return CVMX_TIM_STATUS_BUSY if the timer was not deleted, otherwise
* CVMX_TIM_STATUS_SUCCESS.
*/
static inline cvmx_tim_status_t cvmx_tim_delete_entry(cvmx_tim_delete_t *delete_info)
{
const uint64_t cycles = cvmx_clock_get_count(CVMX_CLOCK_TIM);
if ((int64_t)(cycles - delete_info->commit_cycles) < 0)
{
/* Timer is far enough away. Safe to delete */
*delete_info->timer_entry_ptr = 0;
return CVMX_TIM_STATUS_SUCCESS;
}
else
{
/* Timer is passed the commit time. It cannot be stopped */
return CVMX_TIM_STATUS_BUSY;
}
}
#ifdef __cplusplus
}
#endif
#endif // __CVMX_TIM_H__