Add Atomthreads RTOS source files.

This commit is contained in:
Kelvin Lawson
2010-01-14 01:53:45 +00:00
parent 8521dcfe06
commit 6d599c6729
63 changed files with 16629 additions and 0 deletions

1161
kernel/Doxyfile Normal file

File diff suppressed because it is too large Load Diff

119
kernel/atom.h Executable file
View File

@@ -0,0 +1,119 @@
/*
* Copyright (c) 2010, Kelvin Lawson. All rights reserved.
*
* 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. No personal names or organizations' names associated with the
* Atomthreads project may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT 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 PROJECT 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.
*/
#ifndef __ATOM_H
#define __ATOM_H
#include "atomtimer.h"
#include "atomuser.h"
/* Data types */
/* Forward declaration */
struct atom_tcb;
typedef struct atom_tcb
{
/* Thread's current stack pointer. When a thread is scheduled
* out the architecture port can save*/
POINTER sp_save_ptr;
/* Thread priority (0-255) */
uint8_t priority;
/* Thread entry point and parameter */
void (*entry_point)(uint32_t);
uint32_t entry_param;
/* Queue pointers */
struct atom_tcb *prev_tcb; /* Previous TCB in doubly-linked TCB list */
struct atom_tcb *next_tcb; /* Next TCB in doubly-linked list */
/* Suspension data */
uint8_t suspended; /* TRUE if task is currently suspended */
uint8_t suspend_wake_status; /* Status returned to woken suspend calls */
ATOM_TIMER *suspend_timo_cb; /* Callback registered for suspension timeouts */
} ATOM_TCB;
/* Global data */
extern ATOM_TCB *tcbReadyQ;
extern uint8_t atomOSStarted;
/* Constants */
#define TRUE 1
#define FALSE 0
/* Error values */
#define ATOM_OK 0
#define ATOM_ERROR 1
#define ATOM_TIMEOUT 2
#define ATOM_WOULDBLOCK 3
#define ATOM_ERR_CONTEXT 200
#define ATOM_ERR_PARAM 201
#define ATOM_ERR_DELETED 202
#define ATOM_ERR_OVF 203
#define ATOM_ERR_QUEUE 204
#define ATOM_ERR_TIMER 205
#define ATOM_ERR_NOT_FOUND 206
#define ATOM_ERR_OWNERSHIP 207
/* Idle thread priority (lowest) */
#define IDLE_THREAD_PRIORITY 255
/* Function prototypes */
extern uint8_t atomOSInit (void *idle_thread_stack_top);
extern void atomOSStart (void);
extern void atomSched (uint8_t timer_tick);
extern void atomIntEnter (void);
extern void atomIntExit (uint8_t timer_tick);
extern uint8_t tcbEnqueuePriority (ATOM_TCB **tcb_queue_ptr, ATOM_TCB *tcb_ptr);
extern ATOM_TCB *tcbDequeueHead (ATOM_TCB **tcb_queue_ptr);
extern ATOM_TCB *tcbDequeueEntry (ATOM_TCB **tcb_queue_ptr, ATOM_TCB *tcb_ptr);
extern ATOM_TCB *tcbDequeuePriority (ATOM_TCB **tcb_queue_ptr, uint8_t priority);
extern ATOM_TCB *atomCurrentContext (void);
extern uint8_t atomThreadCreate (ATOM_TCB *tcb_ptr, uint8_t priority, void (*entry_point)(uint32_t), uint32_t entry_param, void *stack_top);
extern void archContextSwitch (ATOM_TCB *old_tcb_ptr, ATOM_TCB *new_tcb_ptr);
extern void archThreadContextInit (ATOM_TCB *tcb_ptr, void *stack_top, void (*entry_point)(uint32_t), uint32_t entry_param);
extern void archFirstThreadRestore(ATOM_TCB *new_tcb_ptr);
extern void atomTimerTick (void);
#endif /* __ATOM_H */

796
kernel/atomkernel.c Executable file
View File

@@ -0,0 +1,796 @@
/*
* Copyright (c) 2010, Kelvin Lawson. All rights reserved.
*
* 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. No personal names or organizations' names associated with the
* Atomthreads project may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT 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 PROJECT 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.
*/
#include <stddef.h>
#include "atom.h"
#include "atomuser.h"
/* Global data */
/**
* This is the head of the queue of threads that are ready to run. It is
* ordered by priority, with the higher priority threads coming first.
* Where there are multiple threads of the same priority, the TCB pointers
* are FIFO-ordered.
*
* Dequeuing the head is a fast operation because the list is ordered.
* Enqueuing may have to walk up to the end of the list. This means that
* context-switch times depend on the number of threads on the ready queue,
* but efficient use is made of available RAM on tiny systems by avoiding
* priority tables etc. This scheme can be easily swapped out for other
* scheduler schemes by replacing the TCB enqueue and dequeue functions.
*
* Once a thread is scheduled in, it is not present on the ready queue while
* it is running. When scheduled out it will be either placed back on the
* ready queue, or will be suspended on some OS primitive (e.g. on the
* suspended TCB queue for a semaphore, or in the timer list if suspended on
* a timer delay).
*/
ATOM_TCB *tcbReadyQ = NULL;
/** Set to TRUE when OS is started and running threads */
uint8_t atomOSStarted = FALSE;
/* Local data */
/** This is a pointer to the TCB for the currently-running thread */
static ATOM_TCB *curr_tcb = NULL;
/** Storage for the idle thread's TCB */
static ATOM_TCB idle_tcb;
/* Number of nested interrupts */
static int atomIntCnt = 0;
/* Forward declarations */
static void atomThreadSwitch(ATOM_TCB *old_tcb, ATOM_TCB *new_tcb);
static void atomIdleThread (uint32_t data);
/**
* \b atomSched
*
* This is an internal function not for use by application code.
*
* This is the main scheduler routine. It is called by the various OS
* library routines to check if any threads should be scheduled in now.
* If so, the context will be switched from the current thread to the
* new one.
*
* The scheduler is priority-based with round-robin performed on threads
* with the same priority. Round-robin is only performed on timer ticks
* however. During reschedules caused by an OS operation (e.g. after
* giving or taking a semaphore) we only allow the scheduling in of
* threads with higher priority than current priority. On timer ticks we
* also allow the scheduling of same-priority threads - in that case we
* schedule in the head of the ready list for that priority and put the
* current thread at the tail.
*
* @param[in] timer_tick Should be TRUE when called from the system tick
*
* @return None
*/
void atomSched (uint8_t timer_tick)
{
CRITICAL_STORE;
ATOM_TCB *new_tcb = NULL;
int16_t lowest_pri;
/**
* Check the OS has actually started. As long as the proper initialisation
* sequence is followed there should be no calls here until the OS is
* started, but we check to handle badly-behaved ports.
*/
if (atomOSStarted == FALSE)
{
/* Don't schedule anything in until the OS is started */
return;
}
/* Enter critical section */
CRITICAL_START ();
/**
* If the current thread is going into suspension, then
* unconditionally dequeue the next thread for execution.
*/
if (curr_tcb->suspended == TRUE)
{
/**
* Dequeue the next ready to run thread. There will always be
* at least the idle thread waiting. Note that this could
* actually be the suspending thread if it was unsuspended
* before the scheduler was called.
*/
new_tcb = tcbDequeueHead (&tcbReadyQ);
/**
* Don't need to add the current thread to any queue because
* it was suspended by another OS mechanism and will be
* sitting on a suspend queue or similar within one of the OS
* primitive libraries (e.g. semaphore).
*/
/* Switch to the new thread */
atomThreadSwitch (curr_tcb, new_tcb);
}
/**
* Otherwise the current thread is still ready, but check
* if any other threads are ready.
*/
else
{
/* Calculate which priority is allowed to be scheduled in */
if (timer_tick == TRUE)
{
/* Same priority or higher threads can preempt */
lowest_pri = (int16_t)curr_tcb->priority;
}
else if (curr_tcb->priority > 0)
{
/* Only higher priority threads can preempt, invalid for 0 (highest) */
lowest_pri = (int16_t)(curr_tcb->priority - 1);
}
else
{
/**
* Current priority is already highest (0), don't allow preempt by
* threads of any priority because this is not a time-slice.
*/
lowest_pri = -1;
}
/* Check if a reschedule is allowed */
if (lowest_pri >= 0)
{
/* Check for a thread at the given minimum priority level or higher */
new_tcb = tcbDequeuePriority (&tcbReadyQ, (uint8_t)lowest_pri);
/* If a thread was found, schedule it in */
if (new_tcb)
{
/* Add the current thread to the ready queue */
(void)tcbEnqueuePriority (&tcbReadyQ, curr_tcb);
/* Switch to the new thread */
atomThreadSwitch (curr_tcb, new_tcb);
}
}
}
/* Exit critical section */
CRITICAL_END ();
}
/**
* \b atomThreadSwitch
*
* This is an internal function not for use by application code.
*
* The function is called by the scheduler to perform a context switch.
* Execution will switch to the new thread's context, therefore the
* function doesn't actually return until the old thread is scheduled
* back in.
*
* @param[in] old_tcb Pointer to TCB for thread being scheduled out
* @param[in] new_tcb Pointer to TCB for thread being scheduled in
*
* @return None
*/
static void atomThreadSwitch(ATOM_TCB *old_tcb, ATOM_TCB *new_tcb)
{
/**
* Check if the new thread is actually the current one, in which
* case we don't need to do any context switch. This can happen
* if a thread goes into suspend but is unsuspended again before
* it is fully scheduled out.
*/
if (old_tcb != new_tcb)
{
/* Set the new currently-running thread pointer */
curr_tcb = new_tcb;
/* Call the architecture-specific context switch */
archContextSwitch (old_tcb, new_tcb);
}
/**
* The context switch shifted execution to a different thread. By the time
* we get back here, we are running in old_tcb context again. Clear its
* suspend status now that we're back.
*/
old_tcb->suspended = FALSE;
}
/**
* \b atomThreadCreate
*
* Creates and starts a new thread.
*
* Callers provide the ATOM_TCB structure storage, these are not obtained
* from an internal TCB free list.
*
* The function puts the new thread on the ready queue and calls the
* scheduler. If the priority is higher than the current priority, then the
* new thread may be scheduled in before the function returns.
*
* @param[in] tcb_ptr Pointer to the thread's TCB storage
* @param[in] priority Priority of the thread (0 to 255)
* @param[in] entry_point Thread entry point
* @param[in] entry_param Parameter passed to thread entry point
* @param[in] stack_top Top of the stack area
*
* @retval ATOM_OK Success
* @retval ATOM_ERR_PARAM Bad parameters
* @retval ATOM_ERR_QUEUE Error putting the thread on the ready queue
*/
uint8_t atomThreadCreate (ATOM_TCB *tcb_ptr, uint8_t priority, void (*entry_point)(uint32_t), uint32_t entry_param, void *stack_top)
{
CRITICAL_STORE;
uint8_t status;
if ((tcb_ptr == NULL) || (entry_point == NULL) || (stack_top == NULL))
{
/* Bad parameters */
status = ATOM_ERR_PARAM;
}
else
{
/* Set up the TCB initial values */
tcb_ptr->suspended = FALSE;
tcb_ptr->priority = priority;
tcb_ptr->prev_tcb = NULL;
tcb_ptr->next_tcb = NULL;
tcb_ptr->suspend_timo_cb = NULL;
/**
* Store the thread entry point and parameter in the TCB. This may
* not be necessary for all architecture ports if they put all of
* this information in the initial thread stack.
*/
tcb_ptr->entry_point = entry_point;
tcb_ptr->entry_param = entry_param;
/**
* Call the arch-specific routine to set up the stack. This routine
* is responsible for creating the context save area necessary for
* allowing atomThreadSwitch() to schedule it in. The initial
* archContextSwitch() call when this thread gets scheduled in the
* first time will then restore the program counter to the thread
* entry point, and any other necessary register values ready for
* it to start running.
*/
archThreadContextInit (tcb_ptr, stack_top, entry_point, entry_param);
/* Protect access to the OS queue */
CRITICAL_START ();
/* Put this thread on the ready queue */
if (tcbEnqueuePriority (&tcbReadyQ, tcb_ptr) != ATOM_OK)
{
/* Exit critical region */
CRITICAL_END ();
/* Queue-related error */
status = ATOM_ERR_QUEUE;
}
else
{
/* Exit critical region */
CRITICAL_END ();
/**
* If the OS is started and we're in thread context, check if we
* should be scheduled in now.
*/
if ((atomOSStarted == TRUE) && atomCurrentContext())
atomSched (FALSE);
/* Success */
status = ATOM_OK;
}
}
return (status);
}
/**
* \b atomIntEnter
*
* Interrupt handler entry routine.
*
* Must be called at the start of any interrupt handlers that may
* call an OS primitive and make a thread ready.
*
* @return None
*/
void atomIntEnter (void)
{
/* Increment the interrupt count */
atomIntCnt++;
}
/**
* \b atomIntExit
*
* Interrupt handler exit routine.
*
* Must be called at the end of any interrupt handlers that may
* call an OS primitive and make a thread ready.
*
* This is responsible for calling the scheduler at the end of
* interrupt handlers to determine whether a new thread has now
* been made ready and should be scheduled in.
*
* @param timer_tick TRUE if this is a timer tick
*
* @return None
*/
void atomIntExit (uint8_t timer_tick)
{
/* Decrement the interrupt count */
atomIntCnt--;
/* Call the scheduler */
atomSched (timer_tick);
}
/**
* \b atomCurrentContext
*
* Get the current thread context.
*
* Returns a pointer to the current thread's TCB, or NULL if not in
* thread-context (in interrupt context).
*
* @retval Pointer to current thread's TCB, NULL if in interrupt context
*/
ATOM_TCB *atomCurrentContext (void)
{
/* Return the current thread's TCB or NULL if in interrupt context */
if (atomIntCnt == 0)
return (curr_tcb);
else
return (NULL);
}
/**
* \b atomOSInit
*
* Initialise the atomthreads OS.
*
* Must be called before any application code uses the atomthreads APIs. No
* threads are actually started until the application calls atomOSStart().
*
* Callers must provide a pointer to some storage for the idle thread stack.
* The caller is responsible for calculating the appropriate space required
* for their particular architecture.
*
* Applications should use the following initialisation sequence:
*
* -> Call atomOSInit() before calling any atomthreads APIs
* -> Arrange for a timer to call atomTimerTick() periodically
* -> Create one or more application threads using atomThreadCreate()
* -> Start the OS using atomOSStart(). At this point the highest
* priority application thread created will be started.
*
* Interrupts should be disabled until the first thread restore is complete,
* to avoid any complications due to interrupts occurring while crucial
* operating system facilities are being initialised. They are normally
* enabled by the archFirstThreadRestore() routine in the architecture port.
*
* @param[in] idle_thread_stack_top Ptr to top of stack area for idle thread
*
* @retval ATOM_OK Success
* @retval ATOM_ERROR Initialisation error
*/
uint8_t atomOSInit (void *idle_thread_stack_top)
{
uint8_t status;
/* Initialise data */
curr_tcb = NULL;
tcbReadyQ = NULL;
atomOSStarted = FALSE;
/* Create the idle thread */
status = atomThreadCreate(&idle_tcb,
IDLE_THREAD_PRIORITY,
atomIdleThread,
0,
idle_thread_stack_top);
/* Return status */
return (status);
}
/**
* \b atomOSStart
*
* Start the highest priority thread running.
*
* This function must be called after all OS initialisation is complete, and
* at least one application thread has been created. It will start executing
* the highest priority thread created (or first created if multiple threads
* share the highest priority).
*
* Interrupts must still be disabled at this point. They must only be enabled
* when the first thread is restored and started by the architecture port's
* archFirstThreadRestore() routine.
*
* @return None
*/
void atomOSStart (void)
{
ATOM_TCB *new_tcb;
/**
* Enable the OS started flag. This stops routines like atomThreadCreate()
* attempting to schedule in a newly-created thread until the scheduler is
* up and running.
*/
atomOSStarted = TRUE;
/**
* Application calls to atomThreadCreate() should have added at least one
* thread to the ready queue. Take the highest priority one off and
* schedule it in. If no threads were created, the OS will simply start
* the idle thread (the lowest priority allowed to be scheduled is the
* idle thread's priority, 255).
*/
new_tcb = tcbDequeuePriority (&tcbReadyQ, 255);
if (new_tcb)
{
/* Set the new currently-running thread pointer */
curr_tcb = new_tcb;
/* Restore and run the first thread */
archFirstThreadRestore (new_tcb);
/* Never returns to here, execution shifts to new thread context */
}
else
{
/* No ready threads were found. atomOSInit() probably was not called */
}
}
/**
* \b atomIdleThread
*
* Entry point for idle thread.
*
* This thread must always be present, and will be the thread executed when
* no other threads are ready to run. It must not call any library routines
* which would cause it to block.
*
* @param[in] data Unused (optional thread entry parameter)
*
* @return None
*/
static void atomIdleThread (uint32_t data)
{
/* Loop forever */
while (1)
{
/** \todo Provide user idle hooks*/
}
}
/**
* \b tcbEnqueuePriority
*
* This is an internal function not for use by application code.
*
* Enqueues the TCB \c tcb_ptr on the TCB queue pointed to by \c tcb_queue_ptr.
* TCBs are placed on the queue in priority order. If there are existing TCBs
* at the same priority as the TCB to be enqueued, the enqueued TCB will be
* placed at the end of the same-priority TCBs. Calls to tcbDequeuePriority()
* will dequeue same-priority TCBs in FIFO order.
*
* \c tcb_queue_ptr may be modified by the routine if the enqueued TCB becomes
* the new list head. It is valid for tcb_queue_ptr to point to a NULL pointer,
* which is the case if the queue is currently empty.
*
* \b NOTE: Assumes that the caller is already in a critical section.
*
* @param[in,out] tcb_queue_ptr Pointer to TCB queue head pointer
* @param[in] tcb_ptr Pointer to TCB to enqueue
*
* @retval ATOM_OK Success
* @retval ATOM_ERR_PARAM Bad parameters
*/
uint8_t tcbEnqueuePriority (ATOM_TCB **tcb_queue_ptr, ATOM_TCB *tcb_ptr)
{
uint8_t status;
ATOM_TCB *prev_ptr, *next_ptr;
/* Parameter check */
if ((tcb_queue_ptr == NULL) || (tcb_ptr == NULL))
{
/* Return error */
status = ATOM_ERR_PARAM;
}
else
{
/* Walk the list and enqueue at the end of the TCBs at this priority */
prev_ptr = next_ptr = *tcb_queue_ptr;
do
{
/* Insert if:
* next_ptr = NULL (we're at the head of an empty queue or at the tail)
* the next TCB in the list is lower priority than the one we're enqueuing.
*/
if ((next_ptr == NULL) || (next_ptr->priority > tcb_ptr->priority))
{
/* Make this TCB the new listhead */
if (next_ptr == *tcb_queue_ptr)
{
*tcb_queue_ptr = tcb_ptr;
tcb_ptr->prev_tcb = NULL;
tcb_ptr->next_tcb = next_ptr;
if (next_ptr)
next_ptr->prev_tcb = tcb_ptr;
}
/* Insert between two TCBs or at the tail */
else
{
tcb_ptr->prev_tcb = prev_ptr;
tcb_ptr->next_tcb = next_ptr;
prev_ptr->next_tcb = tcb_ptr;
if (next_ptr)
next_ptr->prev_tcb = tcb_ptr;
}
/* Quit the loop, we've finished inserting */
break;
}
else
{
/* Not inserting here, try the next one */
prev_ptr = next_ptr;
next_ptr = next_ptr->next_tcb;
}
}
while (prev_ptr != NULL);
/* Successful */
status = ATOM_OK;
}
return (status);
}
/**
* \b tcbDequeueHead
*
* This is an internal function not for use by application code.
*
* Dequeues the highest priority TCB on the queue pointed to by
* \c tcb_queue_ptr.
*
* The TCB will be removed from the queue. Same priority TCBs are dequeued in
* FIFO order.
*
* \c tcb_queue_ptr will be modified by the routine if a TCB is dequeued,
* as this will be the list head. It is valid for tcb_queue_ptr to point to a
* NULL pointer, which is the case if the queue is currently empty. In this
* case the function returns NULL.
*
* \b NOTE: Assumes that the caller is already in a critical section.
*
* @param[in,out] tcb_queue_ptr Pointer to TCB queue head pointer
*
* @return Pointer to highest priority TCB on queue, or NULL if queue empty
*/
ATOM_TCB *tcbDequeueHead (ATOM_TCB **tcb_queue_ptr)
{
ATOM_TCB *ret_ptr;
/* Parameter check */
if (tcb_queue_ptr == NULL)
{
/* Return NULL */
ret_ptr = NULL;
}
/* Check for an empty queue */
else if (*tcb_queue_ptr == NULL)
{
/* Return NULL */
ret_ptr = NULL;
}
/* Remove and return the listhead */
else
{
ret_ptr = *tcb_queue_ptr;
*tcb_queue_ptr = ret_ptr->next_tcb;
if (*tcb_queue_ptr)
(*tcb_queue_ptr)->prev_tcb = NULL;
ret_ptr->next_tcb = ret_ptr->prev_tcb = NULL;
}
return (ret_ptr);
}
/**
* \b tcbDequeueEntry
*
* This is an internal function not for use by application code.
*
* Dequeues a particular TCB from the queue pointed to by \c tcb_queue_ptr.
*
* The TCB will be removed from the queue.
*
* \c tcb_queue_ptr may be modified by the routine if the dequeued TCB was
* the list head. It is valid for tcb_queue_ptr to point to a NULL pointer,
* which is the case if the queue is currently empty. In this case the
* function returns NULL.
*
* \b NOTE: Assumes that the caller is already in a critical section.
*
* @param[in,out] tcb_queue_ptr Pointer to TCB queue head pointer
* @param[in] tcb_ptr Pointer to TCB to dequeue
*
* @return Pointer to the dequeued TCB, or NULL if entry wasn't found
*/
ATOM_TCB *tcbDequeueEntry (ATOM_TCB **tcb_queue_ptr, ATOM_TCB *tcb_ptr)
{
ATOM_TCB *ret_ptr, *prev_ptr, *next_ptr;
/* Parameter check */
if (tcb_queue_ptr == NULL)
{
/* Return NULL */
ret_ptr = NULL;
}
/* Check for an empty queue */
else if (*tcb_queue_ptr == NULL)
{
/* Return NULL */
ret_ptr = NULL;
}
/* Find and remove/return the specified entry */
else
{
ret_ptr = NULL;
prev_ptr = next_ptr = *tcb_queue_ptr;
while (next_ptr)
{
/* Is this entry the one we're looking for? */
if (next_ptr == tcb_ptr)
{
if (next_ptr == *tcb_queue_ptr)
{
/* We're removing the list head */
*tcb_queue_ptr = next_ptr->next_tcb;
if (*tcb_queue_ptr)
(*tcb_queue_ptr)->prev_tcb = NULL;
}
else
{
/* We're removing a mid or tail TCB */
prev_ptr->next_tcb = next_ptr->next_tcb;
if (next_ptr->next_tcb)
next_ptr->next_tcb->prev_tcb = prev_ptr;
}
ret_ptr = next_ptr;
ret_ptr->prev_tcb = ret_ptr->next_tcb = NULL;
break;
}
/* Move on to the next in the list */
prev_ptr = next_ptr;
next_ptr = next_ptr->next_tcb;
}
}
return (ret_ptr);
}
/**
* \b tcbDequeuePriority
*
* This is an internal function not for use by application code.
*
* Dequeues the first TCB of the given priority or higher, from the queue
* pointed to by \c tcb_queue_ptr. Because the queue is ordered high priority
* first, we only ever dequeue the list head, if any. If the list head is
* lower priority than we wish to dequeue, then all following ones will also
* be lower priority and hence are not parsed.
*
* The TCB will be removed from the queue. Same priority TCBs will be dequeued
* in FIFO order.
*
* \c tcb_queue_ptr may be modified by the routine if the dequeued TCB was
* the list head. It is valid for tcb_queue_ptr to point to a NULL pointer,
* which is the case if the queue is currently empty. In this case the
* function returns NULL.
*
* \b NOTE: Assumes that the caller is already in a critical section.
*
* @param[in,out] tcb_queue_ptr Pointer to TCB queue head pointer
* @param[in] priority Minimum priority to qualify for dequeue
*
* @return Pointer to the dequeued TCB, or NULL if none found within priority
*/
ATOM_TCB *tcbDequeuePriority (ATOM_TCB **tcb_queue_ptr, uint8_t priority)
{
ATOM_TCB *ret_ptr;
/* Parameter check */
if (tcb_queue_ptr == NULL)
{
/* Return NULL */
ret_ptr = NULL;
}
/* Check for an empty queue */
else if (*tcb_queue_ptr == NULL)
{
/* Return NULL */
ret_ptr = NULL;
}
/* Check if the list head priority is within our range */
else if ((*tcb_queue_ptr)->priority <= priority)
{
/* Remove the list head */
ret_ptr = *tcb_queue_ptr;
*tcb_queue_ptr = (*tcb_queue_ptr)->next_tcb;
if (*tcb_queue_ptr)
{
(*tcb_queue_ptr)->prev_tcb = NULL;
ret_ptr->next_tcb = NULL;
}
}
else
{
/* No higher priority ready threads found */
ret_ptr = NULL;
}
return (ret_ptr);
}

645
kernel/atommutex.c Executable file
View File

@@ -0,0 +1,645 @@
/*
* Copyright (c) 2010, Kelvin Lawson. All rights reserved.
*
* 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. No personal names or organizations' names associated with the
* Atomthreads project may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT 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 PROJECT 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.
*/
#include <stdio.h>
#include "atom.h"
#include "atommutex.h"
#include "atomtimer.h"
#include "atomuser.h"
/* Local data types */
typedef struct mutex_timer
{
ATOM_TCB *tcb_ptr; /* Thread which is suspended with timeout */
ATOM_MUTEX *mutex_ptr; /* Mutex the thread is suspended on */
} MUTEX_TIMER;
/* Forward declarations */
static void atomMutexTimerCallback (POINTER cb_data);
/**
* \b atomMutexCreate
*
* Initialises a mutex object.
*
* Must be called before calling any other mutex library routines on a
* mutex. Objects can be deleted later using atomMutexDelete().
*
* Does not set the owner of a mutex. atomMutexGet() must be called after
* creation in order to actually take ownership.
*
* Does not allocate storage, the caller provides the mutex object.
*
* This function can be called from interrupt context.
*
* @param[in] mutex Pointer to mutex object
*
* @retval ATOM_OK Success
* @retval ATOM_ERR_PARAM Bad parameters
*/
uint8_t atomMutexCreate (ATOM_MUTEX *mutex)
{
uint8_t status;
/* Parameter check */
if (mutex == NULL)
{
/* Bad mutex pointer */
status = ATOM_ERR_PARAM;
}
else
{
/* Start with no owner (unlocked) */
mutex->owner = NULL;
/* Reset the initial lock count */
mutex->count = 0;
/* Initialise the suspended threads queue */
mutex->suspQ = NULL;
/* Successful */
status = ATOM_OK;
}
return (status);
}
/**
* \b atomMutexDelete
*
* Deletes a mutex object.
*
* Any threads currently suspended on the mutex will be woken up with
* return status ATOM_ERR_DELETED. If called at thread context then the
* scheduler will be called during this function which may schedule in one
* of the woken threads depending on relative priorities.
*
* This function can be called from interrupt context, but loops internally
* waking up all threads blocking on the mutex, so the potential
* execution cycles cannot be determined in advance.
*
* @param[in] mutex Pointer to mutex object
*
* @retval ATOM_OK Success
* @retval ATOM_ERR_QUEUE Problem putting a woken thread on the ready queue
* @retval ATOM_ERR_TIMER Problem cancelling a timeout on a woken thread
*/
uint8_t atomMutexDelete (ATOM_MUTEX *mutex)
{
uint8_t status;
CRITICAL_STORE;
ATOM_TCB *tcb_ptr;
uint8_t woken_threads = FALSE;
/* Parameter check */
if (mutex == NULL)
{
/* Bad mutex pointer */
status = ATOM_ERR_PARAM;
}
else
{
/* Default to success status unless errors occur during wakeup */
status = ATOM_OK;
/* Wake up all suspended tasks */
while (1)
{
/* Enter critical region */
CRITICAL_START ();
/* Check if any threads are suspended */
tcb_ptr = tcbDequeueHead (&mutex->suspQ);
/* A thread is suspended on the mutex */
if (tcb_ptr)
{
/* Return error status to the waiting thread */
tcb_ptr->suspend_wake_status = ATOM_ERR_DELETED;
/* Put the thread on the ready queue */
if (tcbEnqueuePriority (&tcbReadyQ, tcb_ptr) != ATOM_OK)
{
/* Exit critical region */
CRITICAL_END ();
/* Quit the loop, returning error */
status = ATOM_ERR_QUEUE;
break;
}
/* If there's a timeout on this suspension, cancel it */
if (tcb_ptr->suspend_timo_cb)
{
/* Cancel the callback */
if (atomTimerCancel (tcb_ptr->suspend_timo_cb) != ATOM_OK)
{
/* Exit critical region */
CRITICAL_END ();
/* Quit the loop, returning error */
status = ATOM_ERR_TIMER;
break;
}
/* Flag as no timeout registered */
tcb_ptr->suspend_timo_cb = NULL;
}
/* Exit critical region */
CRITICAL_END ();
/* Request a reschedule */
woken_threads = TRUE;
}
/* No more suspended threads */
else
{
/* Exit critical region and quit the loop */
CRITICAL_END ();
break;
}
}
/* Call scheduler if any threads were woken up */
if (woken_threads == TRUE)
{
/**
* Only call the scheduler if we are in thread context, otherwise
* it will be called on exiting the ISR by atomIntExit().
*/
if (atomCurrentContext())
atomSched (FALSE);
}
}
return (status);
}
/**
* \b atomMutexGet
*
* Take the lock on a mutex.
*
* This takes ownership of a mutex if it is not currently owned. Ownership
* is held by this thread until a corresponding call to atomMutexPut() by
* the same thread.
*
* Can be called recursively by the original locking thread (owner).
* Recursive calls are counted, and ownership is not relinquished until
* the number of unlock (atomMutexPut()) calls by the owner matches the
* number of lock (atomMutexGet()) calls.
*
* No thread other than the owner can lock or unlock the mutex while it is
* locked by another thread.
*
* Depending on the \c timeout value specified the call will do one of
* the following if the mutex is already locked by another thread:
*
* \c timeout == 0 : Call will block until the mutex is available
* \c timeout > 0 : Call will block until available up to the specified timeout
* \c timeout == -1 : Return immediately if mutex is locked by another thread
*
* If the call needs to block and \c timeout is zero, it will block
* indefinitely until the owning thread calls atomMutexPut() or
* atomMutexDelete() is called on the mutex.
*
* If the call needs to block and \c timeout is non-zero, the call will only
* block for the specified number of system ticks after which time, if the
* thread was not already woken, the call will return with \c ATOM_TIMEOUT.
*
* If the call would normally block and \c timeout is -1, the call will
* return immediately with \c ATOM_WOULDBLOCK.
*
* This function can only be called from thread context. A mutex has the
* concept of an owner thread, so it is never valid to make a mutex call
* from interrupt context when there is no thread to associate with.
*
* @param[in] mutex Pointer to mutex object
* @param[in] timeout Max system ticks to block (0 = forever)
*
* @retval ATOM_OK Success
* @retval ATOM_TIMEOUT Mutex timed out before being woken
* @retval ATOM_WOULDBLOCK Called with timeout == -1 but count is zero
* @retval ATOM_ERR_DELETED Mutex was deleted while suspended
* @retval ATOM_ERR_CONTEXT Not called in thread context and attempted to block
* @retval ATOM_ERR_PARAM Bad parameter
* @retval ATOM_ERR_QUEUE Problem putting the thread on the suspend queue
* @retval ATOM_ERR_TIMER Problem registering the timeout
* @retval ATOM_ERR_OVF The recursive lock count would have overflowed (>255)
*/
uint8_t atomMutexGet (ATOM_MUTEX *mutex, int32_t timeout)
{
CRITICAL_STORE;
uint8_t status;
MUTEX_TIMER timer_data;
ATOM_TIMER timer_cb;
ATOM_TCB *curr_tcb_ptr;
/* Check parameters */
if (mutex == NULL)
{
/* Bad mutex pointer */
status = ATOM_ERR_PARAM;
}
else
{
/* Get the current TCB */
curr_tcb_ptr = atomCurrentContext();
/* Protect access to the mutex object and OS queues */
CRITICAL_START ();
/**
* Check we are at thread context. Because mutexes have the concept of
* owner threads, it is never valid to call here from an ISR,
* regardless of whether we will block.
*/
if (curr_tcb_ptr == NULL)
{
/* Exit critical region */
CRITICAL_END ();
/* Not currently in thread context, can't suspend */
status = ATOM_ERR_CONTEXT;
}
/* Otherwise if mutex is owned by another thread, block the calling thread */
else if ((mutex->owner != NULL) && (mutex->owner != curr_tcb_ptr))
{
/* If called with timeout >= 0, we should block */
if (timeout >= 0)
{
/* Add current thread to the suspend list on this mutex */
if (tcbEnqueuePriority (&mutex->suspQ, curr_tcb_ptr) != ATOM_OK)
{
/* Exit critical region */
CRITICAL_END ();
/* There was an error putting this thread on the suspend list */
status = ATOM_ERR_QUEUE;
}
else
{
/* Set suspended status for the current thread */
curr_tcb_ptr->suspended = TRUE;
/* Track errors */
status = ATOM_OK;
/* Register a timer callback if requested */
if (timeout)
{
/* Fill out the data needed by the callback to wake us up */
timer_data.tcb_ptr = curr_tcb_ptr;
timer_data.mutex_ptr = mutex;
/* Fill out the timer callback request structure */
timer_cb.cb_func = atomMutexTimerCallback;
timer_cb.cb_data = (POINTER)&timer_data;
timer_cb.cb_ticks = timeout;
/**
* Store the timer details in the TCB so that we can
* cancel the timer callback if the mutex is put
* before the timeout occurs.
*/
curr_tcb_ptr->suspend_timo_cb = &timer_cb;
/* Register a callback on timeout */
if (atomTimerRegister (&timer_cb) != ATOM_OK)
{
/* Timer registration failed */
status = ATOM_ERR_TIMER;
/* Clean up and return to the caller */
(void)tcbDequeueEntry (&mutex->suspQ, curr_tcb_ptr);
curr_tcb_ptr->suspended = FALSE;
curr_tcb_ptr->suspend_timo_cb = NULL;
}
}
/* Set no timeout requested */
else
{
/* No need to cancel timeouts on this one */
curr_tcb_ptr->suspend_timo_cb = NULL;
}
/* Exit critical region */
CRITICAL_END ();
/* Check no errors have occurred */
if (status == ATOM_OK)
{
/**
* Current thread now blocking, schedule in a new
* one. We already know we are in thread context
* so can call the scheduler from here.
*/
atomSched (FALSE);
/**
* Normal atomMutexPut() wakeups will set ATOM_OK status,
* while timeouts will set ATOM_TIMEOUT and mutex
* deletions will set ATOM_ERR_DELETED. */
status = curr_tcb_ptr->suspend_wake_status;
/**
* If we were woken up by another thread relinquishing
* the mutex and handing this thread ownership, then
* the relinquishing thread will set status to ATOM_OK
* and will make this thread the owner. Setting the
* owner before waking the thread ensures that no other
* thread can preempt and take ownership of the mutex
* between this thread being made ready to run, and
* actually being scheduled back in here.
*/
if (status == ATOM_OK)
{
/**
* Since this thread has just gained ownership, the
* lock count is zero and should be incremented
* once for this call.
*/
mutex->count++;
}
}
}
}
else
{
/* timeout == -1, requested not to block and mutex is owned by another thread */
CRITICAL_END();
status = ATOM_WOULDBLOCK;
}
}
else
{
/* Thread is not owned or is owned by us, we can claim ownership */
/* Increment the lock count, checking for count overflow */
if (mutex->count == 255)
{
/* Don't increment, just return error status */
status = ATOM_ERR_OVF;
}
else
{
/* Increment the count and return to the calling thread */
mutex->count++;
/* If the mutex is not locked, mark the calling thread as the new owner */
if (mutex->owner == NULL)
{
mutex->owner = curr_tcb_ptr;
}
/* Successful */
status = ATOM_OK;
}
/* Exit critical region */
CRITICAL_END ();
}
}
return (status);
}
/**
* \b atomMutexPut
*
* Give back the lock on a mutex.
*
* This checks that the mutex is owned by the calling thread, and decrements
* the recursive lock count. Once the lock count reaches zero, the lock is
* considered relinquished and no longer owned by this thread.
*
* If the lock is relinquished and there are threads blocking on the mutex, the
* call will wake up the highest priority thread suspended. Only one thread is
* woken per call to atomMutexPut(). If multiple threads of the same priority
* are suspended, they are woken in order of suspension (FIFO).
*
* This function can only be called from thread context. A mutex has the
* concept of an owner thread, so it is never valid to make a mutex call
* from interrupt context when there is no thread to associate with.
*
* @param[in] mutex Pointer to mutex object
*
* @retval ATOM_OK Success
* @retval ATOM_ERR_PARAM Bad parameter
* @retval ATOM_ERR_QUEUE Problem putting a woken thread on the ready queue
* @retval ATOM_ERR_TIMER Problem cancelling a timeout for a woken thread
* @retval ATOM_ERR_OWNERSHIP Attempt to unlock mutex not owned by this thread
*/
uint8_t atomMutexPut (ATOM_MUTEX * mutex)
{
uint8_t status;
CRITICAL_STORE;
ATOM_TCB *tcb_ptr, *curr_tcb_ptr;
/* Check parameters */
if (mutex == NULL)
{
/* Bad mutex pointer */
status = ATOM_ERR_PARAM;
}
else
{
/* Get the current TCB */
curr_tcb_ptr = atomCurrentContext();
/* Protect access to the mutex object and OS queues */
CRITICAL_START ();
/* Check if the calling thread owns this mutex */
if (mutex->owner != curr_tcb_ptr)
{
/* Exit critical region */
CRITICAL_END ();
/* Attempt to unlock by non-owning thread */
status = ATOM_ERR_OWNERSHIP;
}
else
{
/* Lock is owned by this thread, decrement the recursive lock count */
mutex->count--;
/* Once recursive lock count reaches zero, we relinquish ownership */
if (mutex->count == 0)
{
/* Relinquish ownership */
mutex->owner = NULL;
/* If any threads are blocking on this mutex, wake them now */
if (mutex->suspQ)
{
/**
* Threads are woken up in priority order, with a FIFO system
* used on same priority threads. We always take the head,
* ordering is taken care of by an ordered list enqueue.
*/
tcb_ptr = tcbDequeueHead (&mutex->suspQ);
if (tcbEnqueuePriority (&tcbReadyQ, tcb_ptr) != ATOM_OK)
{
/* Exit critical region */
CRITICAL_END ();
/* There was a problem putting the thread on the ready queue */
status = ATOM_ERR_QUEUE;
}
else
{
/* Set OK status to be returned to the waiting thread */
tcb_ptr->suspend_wake_status = ATOM_OK;
/* Set this thread as the new owner of the mutex */
mutex->owner = tcb_ptr;
/* If there's a timeout on this suspension, cancel it */
if ((tcb_ptr->suspend_timo_cb != NULL)
&& (atomTimerCancel (tcb_ptr->suspend_timo_cb) != ATOM_OK))
{
/* There was a problem cancelling a timeout on this mutex */
status = ATOM_ERR_TIMER;
}
else
{
/* Flag as no timeout registered */
tcb_ptr->suspend_timo_cb = NULL;
/* Successful */
status = ATOM_OK;
}
/* Exit critical region */
CRITICAL_END ();
/**
* The scheduler may now make a policy decision to
* thread switch. We already know we are in thread
* context so can call the scheduler from here.
*/
atomSched (FALSE);
}
}
else
{
/**
* Relinquished ownership and no threads waiting.
* Nothing to do.
*/
/* Exit critical region */
CRITICAL_END ();
/* Successful */
status = ATOM_OK;
}
}
else
{
/**
* Decremented lock but still retain ownership due to
* recursion. Nothing to do.
*/
/* Exit critical region */
CRITICAL_END ();
/* Successful */
status = ATOM_OK;
}
}
}
return (status);
}
/**
* \b atomMutexTimerCallback
*
* This is an internal function not for use by application code.
*
* Timeouts on suspended threads are notified by the timer system through
* this generic callback. The timer system calls us back with a pointer to
* the relevant \c MUTEX_TIMER object which is used to retrieve the
* mutex details.
*
* @param[in] cb_data Pointer to a MUTEX_TIMER object
*/
static void atomMutexTimerCallback (POINTER cb_data)
{
MUTEX_TIMER *timer_data_ptr;
CRITICAL_STORE;
/* Get the MUTEX_TIMER structure pointer */
timer_data_ptr = (MUTEX_TIMER *)cb_data;
/* Check parameter is valid */
if (timer_data_ptr)
{
/* Enter critical region */
CRITICAL_START ();
/* Set status to indicate to the waiting thread that it timed out */
timer_data_ptr->tcb_ptr->suspend_wake_status = ATOM_TIMEOUT;
/* Flag as no timeout registered */
timer_data_ptr->tcb_ptr->suspend_timo_cb = NULL;
/* Remove this thread from the mutex's suspend list */
(void)tcbDequeueEntry (&timer_data_ptr->mutex_ptr->suspQ, timer_data_ptr->tcb_ptr);
/* Put the thread on the ready queue */
(void)tcbEnqueuePriority (&tcbReadyQ, timer_data_ptr->tcb_ptr);
/* Exit critical region */
CRITICAL_END ();
/**
* Note that we don't call the scheduler now as it will be called
* when we exit the ISR by atomIntExit().
*/
}
}

44
kernel/atommutex.h Executable file
View File

@@ -0,0 +1,44 @@
/*
* Copyright (c) 2010, Kelvin Lawson. All rights reserved.
*
* 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. No personal names or organizations' names associated with the
* Atomthreads project may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT 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 PROJECT 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.
*/
#ifndef __ATOM_MUTEX_H
#define __ATOM_MUTEX_H
typedef struct atom_mutex
{
ATOM_TCB * suspQ; /* Queue of threads suspended on this mutex */
ATOM_TCB * owner; /* Thread which currently owns the lock */
uint8_t count; /* Recursive count of locks by the owner */
} ATOM_MUTEX;
extern uint8_t atomMutexCreate (ATOM_MUTEX *mutex);
extern uint8_t atomMutexDelete (ATOM_MUTEX *mutex);
extern uint8_t atomMutexGet (ATOM_MUTEX *mutex, int32_t timeout);
extern uint8_t atomMutexPut (ATOM_MUTEX *mutex);
#endif /* __ATOM_MUTEX_H */

879
kernel/atomqueue.c Executable file
View File

@@ -0,0 +1,879 @@
/*
* Copyright (c) 2010, Kelvin Lawson. All rights reserved.
*
* 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. No personal names or organizations' names associated with the
* Atomthreads project may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT 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 PROJECT 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.
*/
#include <stdio.h>
#include <string.h>
#include "atom.h"
#include "atomqueue.h"
#include "atomtimer.h"
#include "atomuser.h"
/* Local data types */
typedef struct queue_timer
{
ATOM_TCB *tcb_ptr; /* Thread which is suspended with timeout */
ATOM_QUEUE *queue_ptr; /* Queue the thread is interested in */
ATOM_TCB **suspQ; /* TCB queue which thread is suspended on */
} QUEUE_TIMER;
/* Forward declarations */
static uint8_t queue_remove (ATOM_QUEUE *qptr, uint8_t* msgptr);
static uint8_t queue_insert (ATOM_QUEUE *qptr, uint8_t* msgptr);
static void atomQueueTimerCallback (POINTER cb_data);
/**
* \b atomQueueCreate
*
* Initialises a queue object.
*
* Must be called before calling any other queue library routines on a
* queue. Objects can be deleted later using atomQueueDelete().
*
* Does not allocate storage, the caller provides the queue object.
*
* Callers pass in their own buffer area for storing the queue messages while
* in transit between threads. The provided storage must be large enough to
* store (\c unit_size * \c max_num_mgs) bytes. i.e. the storage area will be
* used for up to \c max_num_msgs messages each of size \c unit_size.
*
* Queues use a fixed-size message.
*
* This function can be called from interrupt context.
*
* @param[in] qptr Pointer to queue object
* @param[in] buff_ptr Pointer to buffer storage area
* @param[in] unit_size Size in bytes of each queue message
* @param[in] max_num_msgs Maximum number of messages in the queue
*
* @retval ATOM_OK Success
* @retval ATOM_ERR_PARAM Bad parameters
*/
uint8_t atomQueueCreate (ATOM_QUEUE *qptr, uint8_t *buff_ptr, uint32_t unit_size, uint32_t max_num_msgs)
{
uint8_t status;
/* Parameter check */
if ((qptr == NULL) || (buff_ptr == NULL))
{
/* Bad pointers */
status = ATOM_ERR_PARAM;
}
else if ((unit_size == 0) || (max_num_msgs == 0))
{
/* Bad values */
status = ATOM_ERR_PARAM;
}
else
{
/* Store the queue details */
qptr->buff_ptr = buff_ptr;
qptr->unit_size = unit_size;
qptr->max_num_msgs = max_num_msgs;
/* Initialise the suspended threads queues */
qptr->putSuspQ = NULL;
qptr->getSuspQ = NULL;
/* Initialise the insert/remove pointers */
qptr->insert_index = 0;
qptr->remove_index = 0;
qptr->num_msgs_stored = 0;
/* Successful */
status = ATOM_OK;
}
return (status);
}
/**
* \b atomQueueDelete
*
* Deletes a queue object.
*
* Any threads currently suspended on the queue will be woken up with
* return status ATOM_ERR_DELETED. If called at thread context then the
* scheduler will be called during this function which may schedule in one
* of the woken threads depending on relative priorities.
*
* This function can be called from interrupt context, but loops internally
* waking up all threads blocking on the queue, so the potential
* execution cycles cannot be determined in advance.
*
* @param[in] qptr Pointer to queue object
*
* @retval ATOM_OK Success
* @retval ATOM_ERR_QUEUE Problem putting a woken thread on the ready queue
* @retval ATOM_ERR_TIMER Problem cancelling a timeout on a woken thread
*/
uint8_t atomQueueDelete (ATOM_QUEUE *qptr)
{
uint8_t status;
CRITICAL_STORE;
ATOM_TCB *tcb_ptr;
uint8_t woken_threads = FALSE;
/* Parameter check */
if (qptr == NULL)
{
/* Bad pointer */
status = ATOM_ERR_PARAM;
}
else
{
/* Default to success status unless errors occur during wakeup */
status = ATOM_OK;
/* Wake up all suspended tasks */
while (1)
{
/* Enter critical region */
CRITICAL_START ();
/* Check if any threads are suspended */
if (((tcb_ptr = tcbDequeueHead (&qptr->getSuspQ)) != NULL)
|| ((tcb_ptr = tcbDequeueHead (&qptr->putSuspQ)) != NULL))
{
/* A thread is waiting on a suspend queue */
/* Return error status to the waiting thread */
tcb_ptr->suspend_wake_status = ATOM_ERR_DELETED;
/* Put the thread on the ready queue */
if (tcbEnqueuePriority (&tcbReadyQ, tcb_ptr) != ATOM_OK)
{
/* Exit critical region */
CRITICAL_END ();
/* Quit the loop, returning error */
status = ATOM_ERR_QUEUE;
break;
}
/* If there's a timeout on this suspension, cancel it */
if (tcb_ptr->suspend_timo_cb)
{
/* Cancel the callback */
if (atomTimerCancel (tcb_ptr->suspend_timo_cb) != ATOM_OK)
{
/* Exit critical region */
CRITICAL_END ();
/* Quit the loop, returning error */
status = ATOM_ERR_TIMER;
break;
}
/* Flag as no timeout registered */
tcb_ptr->suspend_timo_cb = NULL;
}
/* Exit critical region */
CRITICAL_END ();
/* Request a reschedule */
woken_threads = TRUE;
}
/* No more suspended threads */
else
{
/* Exit critical region and quit the loop */
CRITICAL_END ();
break;
}
}
/* Call scheduler if any threads were woken up */
if (woken_threads == TRUE)
{
/**
* Only call the scheduler if we are in thread context, otherwise
* it will be called on exiting the ISR by atomIntExit().
*/
if (atomCurrentContext())
atomSched (FALSE);
}
}
return (status);
}
/**
* \b atomQueueGet
*
* Attempt to retrieve a message from a queue.
*
* If the queue is currently empty, the call will do one of the following
* depending on the \c timeout value specified:
*
* \c timeout == 0 : Call will block until a message is available
* \c timeout > 0 : Call will block until a message or the specified timeout
* \c timeout == -1 : Return immediately if no message is on the queue
*
* If a maximum timeout value is specified (\c timeout > 0), and no message
* is present on the queue for the specified number of system ticks, the
* call will return with \c ATOM_TIMEOUT.
*
* This function can only be called from interrupt context if the \c timeout
* parameter is -1 (in which case it does not block).
*
* @param[in] qptr Pointer to queue object
* @param[in] timeout Max system ticks to block (0 = forever, -1 = no block)
* @param[out] msgptr Pointer to which the received message will be copied
*
* @retval ATOM_OK Success
* @retval ATOM_TIMEOUT Queue wait timed out before being woken
* @retval ATOM_WOULDBLOCK Called with timeout == -1 but queue was empty
* @retval ATOM_ERR_DELETED Queue was deleted while suspended
* @retval ATOM_ERR_CONTEXT Not called in thread context and attempted to block
* @retval ATOM_ERR_PARAM Bad parameter
* @retval ATOM_ERR_QUEUE Problem putting the thread on the suspend queue
* @retval ATOM_ERR_TIMER Problem registering the timeout
*/
uint8_t atomQueueGet (ATOM_QUEUE *qptr, int32_t timeout, uint8_t *msgptr)
{
CRITICAL_STORE;
uint8_t status;
QUEUE_TIMER timer_data;
ATOM_TIMER timer_cb;
ATOM_TCB *curr_tcb_ptr;
/* Check parameters */
if ((qptr == NULL) || (msgptr == NULL))
{
/* Bad pointer */
status = ATOM_ERR_PARAM;
}
else
{
/* Protect access to the queue object and OS queues */
CRITICAL_START ();
/* If no messages on the queue, block the calling thread */
if (qptr->num_msgs_stored == 0)
{
/* If called with timeout >= 0, we should block */
if (timeout >= 0)
{
/* Queue is empty, block the calling thread */
/* Get the current TCB */
curr_tcb_ptr = atomCurrentContext();
/* Check we are actually in thread context */
if (curr_tcb_ptr)
{
/* Add current thread to the list suspended on receives */
if (tcbEnqueuePriority (&qptr->getSuspQ, curr_tcb_ptr) == ATOM_OK)
{
/* Set suspended status for the current thread */
curr_tcb_ptr->suspended = TRUE;
/* Track errors */
status = ATOM_OK;
/* Register a timer callback if requested */
if (timeout)
{
/**
* Fill out the data needed by the callback to
* wake us up.
*/
timer_data.tcb_ptr = curr_tcb_ptr;
timer_data.queue_ptr = qptr;
timer_data.suspQ = &qptr->getSuspQ;
/* Fill out the timer callback request structure */
timer_cb.cb_func = atomQueueTimerCallback;
timer_cb.cb_data = (POINTER)&timer_data;
timer_cb.cb_ticks = timeout;
/**
* Store the timer details in the TCB so that we
* can cancel the timer callback if the queue is
* put before the timeout occurs.
*/
curr_tcb_ptr->suspend_timo_cb = &timer_cb;
/* Register a callback on timeout */
if (atomTimerRegister (&timer_cb) != ATOM_OK)
{
/* Timer registration failed */
status = ATOM_ERR_TIMER;
/* Clean up and return to the caller */
(void)tcbDequeueEntry (&qptr->getSuspQ, curr_tcb_ptr);
curr_tcb_ptr->suspended = FALSE;
curr_tcb_ptr->suspend_timo_cb = NULL;
}
}
/* Set no timeout requested */
else
{
/* No need to cancel timeouts on this one */
curr_tcb_ptr->suspend_timo_cb = NULL;
}
/* Exit critical region */
CRITICAL_END ();
/* Check no errors occurred */
if (status == ATOM_OK)
{
/**
* Current thread now blocking, schedule in a new
* one. We already know we are in thread context
* so can call the scheduler from here.
*/
atomSched (FALSE);
/**
* Normal atomQueuePut() wakeups will set ATOM_OK
* status, while timeouts will set ATOM_TIMEOUT
* and queue deletions will set ATOM_ERR_DELETED.
*/
status = curr_tcb_ptr->suspend_wake_status;
/**
* Check suspend_wake_status. If it is ATOM_OK
* then we were woken because a message has been
* put on the queue and we can now copy it out.
* Otherwise we were woken because we timed out
* waiting for a message, or the queue was
* deleted, so we should just quit.
*/
if (status == ATOM_OK)
{
/* Enter critical region */
CRITICAL_START();
/* Copy the message out of the queue */
status = queue_remove (qptr, msgptr);
/* Exit critical region */
CRITICAL_END();
}
}
}
else
{
/* There was an error putting this thread on the suspend list */
CRITICAL_END ();
status = ATOM_ERR_QUEUE;
}
}
else
{
/* Not currently in thread context, can't suspend */
CRITICAL_END ();
status = ATOM_ERR_CONTEXT;
}
}
else
{
/* timeout == -1, requested not to block and queue is empty */
CRITICAL_END();
status = ATOM_WOULDBLOCK;
}
}
else
{
/* No need to block, there is a message to copy out of the queue */
status = queue_remove (qptr, msgptr);
/* Exit critical region */
CRITICAL_END ();
/**
* The scheduler may now make a policy decision to thread
* switch if we are currently in thread context. If we are
* in interrupt context it will be handled by atomIntExit().
*/
if (atomCurrentContext())
atomSched (FALSE);
}
}
return (status);
}
/**
* \b atomQueuePut
*
* Attempt to put a message onto a queue.
*
* If the queue is currently full, the call will do one of the following
* depending on the \c timeout value specified:
*
* \c timeout == 0 : Call will block until space is available
* \c timeout > 0 : Call will block until space or the specified timeout
* \c timeout == -1 : Return immediately if the queue is full
*
* If a maximum timeout value is specified (\c timeout > 0), and no space
* is available on the queue for the specified number of system ticks, the
* call will return with \c ATOM_TIMEOUT.
*
* This function can only be called from interrupt context if the \c timeout
* parameter is -1 (in which case it does not block and may fail to post a
* message if the queue is full).
*
* @param[in] qptr Pointer to queue object
* @param[in] timeout Max system ticks to block (0 = forever, -1 = no block)
* @param[out] msgptr Pointer from which the message should be copied out
*
* @retval ATOM_OK Success
* @retval ATOM_WOULDBLOCK Called with timeout == -1 but queue was full
* @retval ATOM_TIMEOUT Queue wait timed out before being woken
* @retval ATOM_ERR_DELETED Queue was deleted while suspended
* @retval ATOM_ERR_CONTEXT Not called in thread context and attempted to block
* @retval ATOM_ERR_PARAM Bad parameter
* @retval ATOM_ERR_QUEUE Problem putting the thread on the suspend queue
* @retval ATOM_ERR_TIMER Problem registering the timeout
*/
uint8_t atomQueuePut (ATOM_QUEUE *qptr, int32_t timeout, uint8_t *msgptr)
{
CRITICAL_STORE;
uint8_t status;
QUEUE_TIMER timer_data;
ATOM_TIMER timer_cb;
ATOM_TCB *curr_tcb_ptr;
/* Check parameters */
if ((qptr == NULL) || (msgptr == NULL))
{
/* Bad pointer */
status = ATOM_ERR_PARAM;
}
else
{
/* Protect access to the queue object and OS queues */
CRITICAL_START ();
/* If queue is full, block the calling thread */
if (qptr->num_msgs_stored == qptr->max_num_msgs)
{
/* If called with timeout >= 0, we should block */
if (timeout >= 0)
{
/* Queue is full, block the calling thread */
/* Get the current TCB */
curr_tcb_ptr = atomCurrentContext();
/* Check we are actually in thread context */
if (curr_tcb_ptr)
{
/* Add current thread to the suspend list on sends */
if (tcbEnqueuePriority (&qptr->putSuspQ, curr_tcb_ptr) == ATOM_OK)
{
/* Set suspended status for the current thread */
curr_tcb_ptr->suspended = TRUE;
/* Track errors */
status = ATOM_OK;
/* Register a timer callback if requested */
if (timeout)
{
/**
* Fill out the data needed by the callback to
* wake us up.
*/
timer_data.tcb_ptr = curr_tcb_ptr;
timer_data.queue_ptr = qptr;
timer_data.suspQ = &qptr->putSuspQ;
/* Fill out the timer callback request structure */
timer_cb.cb_func = atomQueueTimerCallback;
timer_cb.cb_data = (POINTER)&timer_data;
timer_cb.cb_ticks = timeout;
/**
* Store the timer details in the TCB so that we
* can cancel the timer callback if a message is
* removed from the queue before the timeout
* occurs.
*/
curr_tcb_ptr->suspend_timo_cb = &timer_cb;
/* Register a callback on timeout */
if (atomTimerRegister (&timer_cb) != ATOM_OK)
{
/* Timer registration failed */
status = ATOM_ERR_TIMER;
/* Clean up and return to the caller */
(void)tcbDequeueEntry (&qptr->putSuspQ, curr_tcb_ptr);
curr_tcb_ptr->suspended = FALSE;
curr_tcb_ptr->suspend_timo_cb = NULL;
}
}
/* Set no timeout requested */
else
{
/* No need to cancel timeouts on this one */
curr_tcb_ptr->suspend_timo_cb = NULL;
}
/* Exit critical region */
CRITICAL_END ();
/* Check timer registration was successful */
if (status == ATOM_OK)
{
/**
* Current thread now blocking, schedule in a new
* one. We already know we are in thread context
* so can call the scheduler from here.
*/
atomSched (FALSE);
/**
* Normal atomQueueGet() wakeups will set ATOM_OK
* status, while timeouts will set ATOM_TIMEOUT
* and queue deletions will set ATOM_ERR_DELETED.
*/
status = curr_tcb_ptr->suspend_wake_status;
/**
* Check suspend_wake_status. If it is ATOM_OK
* then we were woken because a message has been
* removed from the queue and we can now add ours.
* Otherwise we were woken because we timed out
* waiting for a message, or the queue was
* deleted, so we should just quit.
*/
if (status == ATOM_OK)
{
/* Enter critical region */
CRITICAL_START();
/* Copy the message into the queue */
status = queue_insert (qptr, msgptr);
/* Exit critical region */
CRITICAL_END();
}
}
}
else
{
/* There was an error putting this thread on the suspend list */
CRITICAL_END ();
status = ATOM_ERR_QUEUE;
}
}
else
{
/* Not currently in thread context, can't suspend */
CRITICAL_END ();
status = ATOM_ERR_CONTEXT;
}
}
else
{
/* timeout == -1, cannot block. Just return queue is full */
CRITICAL_END();
status = ATOM_WOULDBLOCK;
}
}
else
{
/* No need to block, there is space to copy into the queue */
status = queue_insert (qptr, msgptr);
/* Exit critical region */
CRITICAL_END ();
/**
* The scheduler may now make a policy decision to thread
* switch if we are currently in thread context. If we are
* in interrupt context it will be handled by atomIntExit().
*/
if (atomCurrentContext())
atomSched (FALSE);
}
}
return (status);
}
/**
* \b atomQueueTimerCallback
*
* This is an internal function not for use by application code.
*
* Timeouts on suspended threads are notified by the timer system through
* this generic callback. The timer system calls us back with a pointer to
* the relevant \c QUEUE_TIMER object which is used to retrieve the
* queue details.
*
* @param[in] cb_data Pointer to a QUEUE_TIMER object
*/
static void atomQueueTimerCallback (POINTER cb_data)
{
QUEUE_TIMER *timer_data_ptr;
CRITICAL_STORE;
/* Get the QUEUE_TIMER structure pointer */
timer_data_ptr = (QUEUE_TIMER *)cb_data;
/* Check parameter is valid */
if (timer_data_ptr)
{
/* Enter critical region */
CRITICAL_START ();
/* Set status to indicate to the waiting thread that it timed out */
timer_data_ptr->tcb_ptr->suspend_wake_status = ATOM_TIMEOUT;
/* Flag as no timeout registered */
timer_data_ptr->tcb_ptr->suspend_timo_cb = NULL;
/**
* Remove this thread from the queue's suspend list. Handles threads
* suspended on the receive list as well as the send list.
*/
(void)tcbDequeueEntry (timer_data_ptr->suspQ, timer_data_ptr->tcb_ptr);
/* Put the thread on the ready queue */
(void)tcbEnqueuePriority (&tcbReadyQ, timer_data_ptr->tcb_ptr);
/* Exit critical region */
CRITICAL_END ();
/**
* Note that we don't call the scheduler now as it will be called
* when we exit the ISR by atomIntExit().
*/
}
}
/**
* \b queue_remove
*
* This is an internal function not for use by application code.
*
* Removes a message from a queue. Assumes that there is a message present,
* which is already checked by the calling functions with interrupts locked
* out.
*
* Also wakes up a suspended thread if there are any waiting to send on the
* queue.
*
* Assumes interrupts are already locked out.
*
* @param[in] qptr Pointer to an ATOM_QUEUE object
* @param[in] msgptr Destination pointer for the message to be copied into
*
* @retval ATOM_OK Success
* @retval ATOM_ERR_PARAM Bad parameter
* @retval ATOM_ERR_QUEUE Problem putting a thread on the ready queue
* @retval ATOM_ERR_TIMER Problem cancelling a timeout
*/
static uint8_t queue_remove (ATOM_QUEUE *qptr, uint8_t* msgptr)
{
uint8_t status;
ATOM_TCB *tcb_ptr;
/* Check parameters */
if ((qptr == NULL) || (msgptr == NULL))
{
/* Bad pointer */
status = ATOM_ERR_PARAM;
}
else
{
/* There is a message on the queue, copy it out */
memcpy (msgptr, (qptr->buff_ptr + qptr->remove_index), qptr->unit_size);
qptr->remove_index += qptr->unit_size;
qptr->num_msgs_stored--;
/* Check if the remove index should now wrap to the beginning */
if (qptr->remove_index >= (qptr->unit_size * qptr->max_num_msgs))
qptr->remove_index = 0;
/**
* If there are threads waiting to send, wake one up now. Waiting
* threads are woken up in priority order, with same-priority
* threads woken up in FIFO order.
*/
tcb_ptr = tcbDequeueHead (&qptr->putSuspQ);
if (tcb_ptr)
{
/* Move the waiting thread to the ready queue */
if (tcbEnqueuePriority (&tcbReadyQ, tcb_ptr) == ATOM_OK)
{
/* Set OK status to be returned to the waiting thread */
tcb_ptr->suspend_wake_status = ATOM_OK;
/* If there's a timeout on this suspension, cancel it */
if ((tcb_ptr->suspend_timo_cb != NULL)
&& (atomTimerCancel (tcb_ptr->suspend_timo_cb) != ATOM_OK))
{
/* There was a problem cancelling a timeout */
status = ATOM_ERR_TIMER;
}
else
{
/* Flag as no timeout registered */
tcb_ptr->suspend_timo_cb = NULL;
/* Successful */
status = ATOM_OK;
}
}
else
{
/**
* There was a problem putting the thread on the ready
* queue.
*/
status = ATOM_ERR_QUEUE;
}
}
else
{
/* There were no threads waiting to send */
status = ATOM_OK;
}
}
return (status);
}
/**
* \b queue_insert
*
* This is an internal function not for use by application code.
*
* Inserts a message onto a queue. Assumes that the queue has space for one
* message, which has already been checked by the calling function with
* interrupts locked out.
*
* Also wakes up a suspended thread if there are any waiting to receive on the
* queue.
*
* Assumes interrupts are already locked out.
*
* @param[in] qptr Pointer to an ATOM_QUEUE object
* @param[in] msgptr Source pointer for the message to be copied out of
*
* @retval ATOM_OK Success
* @retval ATOM_ERR_PARAM Bad parameter
* @retval ATOM_ERR_QUEUE Problem putting a thread on the ready queue
* @retval ATOM_ERR_TIMER Problem cancelling a timeout
*/
static uint8_t queue_insert (ATOM_QUEUE *qptr, uint8_t* msgptr)
{
uint8_t status;
ATOM_TCB *tcb_ptr;
/* Check parameters */
if ((qptr == NULL) || (msgptr == NULL))
{
/* Bad pointer */
status = ATOM_ERR_PARAM;
}
else
{
/* There is space in the queue, copy it in */
memcpy ((qptr->buff_ptr + qptr->insert_index), msgptr, qptr->unit_size);
qptr->insert_index += qptr->unit_size;
qptr->num_msgs_stored++;
/* Check if the insert index should now wrap to the beginning */
if (qptr->insert_index >= (qptr->unit_size * qptr->max_num_msgs))
qptr->insert_index = 0;
/**
* If there are threads waiting to receive, wake one up now. Waiting
* threads are woken up in priority order, with same-priority
* threads woken up in FIFO order.
*/
tcb_ptr = tcbDequeueHead (&qptr->getSuspQ);
if (tcb_ptr)
{
/* Move the waiting thread to the ready queue */
if (tcbEnqueuePriority (&tcbReadyQ, tcb_ptr) == ATOM_OK)
{
/* Set OK status to be returned to the waiting thread */
tcb_ptr->suspend_wake_status = ATOM_OK;
/* If there's a timeout on this suspension, cancel it */
if ((tcb_ptr->suspend_timo_cb != NULL)
&& (atomTimerCancel (tcb_ptr->suspend_timo_cb) != ATOM_OK))
{
/* There was a problem cancelling a timeout */
status = ATOM_ERR_TIMER;
}
else
{
/* Flag as no timeout registered */
tcb_ptr->suspend_timo_cb = NULL;
/* Successful */
status = ATOM_OK;
}
}
else
{
/**
* There was a problem putting the thread on the ready
* queue.
*/
status = ATOM_ERR_QUEUE;
}
}
else
{
/* There were no threads waiting to send */
status = ATOM_OK;
}
}
return (status);
}

49
kernel/atomqueue.h Executable file
View File

@@ -0,0 +1,49 @@
/*
* Copyright (c) 2010, Kelvin Lawson. All rights reserved.
*
* 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. No personal names or organizations' names associated with the
* Atomthreads project may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT 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 PROJECT 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.
*/
#ifndef __ATOM_QUEUE_H
#define __ATOM_QUEUE_H
typedef struct atom_queue
{
ATOM_TCB * putSuspQ; /* Queue of threads waiting to send */
ATOM_TCB * getSuspQ; /* Queue of threads waiting to receive */
uint8_t * buff_ptr; /* Pointer to queue data area */
uint32_t unit_size; /* Size of each message */
uint32_t max_num_msgs; /* Max number of storable messages */
uint32_t insert_index; /* Next byte index to insert into */
uint32_t remove_index; /* Next byte index to remove from */
uint32_t num_msgs_stored;/* Number of messages stored */
} ATOM_QUEUE;
extern uint8_t atomQueueCreate (ATOM_QUEUE *qptr, uint8_t *buff_ptr, uint32_t unit_size, uint32_t max_num_msgs);
extern uint8_t atomQueueDelete (ATOM_QUEUE *qptr);
extern uint8_t atomQueueGet (ATOM_QUEUE *qptr, int32_t timeout, uint8_t *msgptr);
extern uint8_t atomQueuePut (ATOM_QUEUE *qptr, int32_t timeout, uint8_t *msgptr);
#endif /* __ATOM_QUEUE_H */

618
kernel/atomsem.c Executable file
View File

@@ -0,0 +1,618 @@
/*
* Copyright (c) 2010, Kelvin Lawson. All rights reserved.
*
* 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. No personal names or organizations' names associated with the
* Atomthreads project may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT 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 PROJECT 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.
*/
#include <stdio.h>
#include "atom.h"
#include "atomsem.h"
#include "atomtimer.h"
#include "atomuser.h"
/* Local data types */
typedef struct sem_timer
{
ATOM_TCB *tcb_ptr; /* Thread which is suspended with timeout */
ATOM_SEM *sem_ptr; /* Semaphore the thread is suspended on */
} SEM_TIMER;
/* Forward declarations */
static void atomSemTimerCallback (POINTER cb_data);
/**
* \b atomSemCreate
*
* Initialises a semaphore object.
*
* Must be called before calling any other semaphore library routines on a
* semaphore. Objects can be deleted later using atomSemDelete().
*
* Does not allocate storage, the caller provides the semaphore object.
*
* This function can be called from interrupt context.
*
* @param[in] sem Pointer to semaphore object
* @param[in] initial_count Initial count value
*
* @retval ATOM_OK Success
* @retval ATOM_ERR_PARAM Bad parameters
*/
uint8_t atomSemCreate (ATOM_SEM *sem, uint8_t initial_count)
{
uint8_t status;
/* Parameter check */
if (sem == NULL)
{
/* Bad semaphore pointer */
status = ATOM_ERR_PARAM;
}
else
{
/* Set the initial count */
sem->count = initial_count;
/* Initialise the suspended threads queue */
sem->suspQ = NULL;
/* Successful */
status = ATOM_OK;
}
return (status);
}
/**
* \b atomSemDelete
*
* Deletes a semaphore object.
*
* Any threads currently suspended on the semaphore will be woken up with
* return status ATOM_ERR_DELETED. If called at thread context then the
* scheduler will be called during this function which may schedule in one
* of the woken threads depending on relative priorities.
*
* This function can be called from interrupt context, but loops internally
* waking up all threads blocking on the semaphore, so the potential
* execution cycles cannot be determined in advance.
*
* @param[in] sem Pointer to semaphore object
*
* @retval ATOM_OK Success
* @retval ATOM_ERR_QUEUE Problem putting a woken thread on the ready queue
* @retval ATOM_ERR_TIMER Problem cancelling a timeout on a woken thread
*/
uint8_t atomSemDelete (ATOM_SEM *sem)
{
uint8_t status;
CRITICAL_STORE;
ATOM_TCB *tcb_ptr;
uint8_t woken_threads = FALSE;
/* Parameter check */
if (sem == NULL)
{
/* Bad semaphore pointer */
status = ATOM_ERR_PARAM;
}
else
{
/* Default to success status unless errors occur during wakeup */
status = ATOM_OK;
/* Wake up all suspended tasks */
while (1)
{
/* Enter critical region */
CRITICAL_START ();
/* Check if any threads are suspended */
tcb_ptr = tcbDequeueHead (&sem->suspQ);
/* A thread is suspended on the semaphore */
if (tcb_ptr)
{
/* Return error status to the waiting thread */
tcb_ptr->suspend_wake_status = ATOM_ERR_DELETED;
/* Put the thread on the ready queue */
if (tcbEnqueuePriority (&tcbReadyQ, tcb_ptr) != ATOM_OK)
{
/* Exit critical region */
CRITICAL_END ();
/* Quit the loop, returning error */
status = ATOM_ERR_QUEUE;
break;
}
/* If there's a timeout on this suspension, cancel it */
if (tcb_ptr->suspend_timo_cb)
{
/* Cancel the callback */
if (atomTimerCancel (tcb_ptr->suspend_timo_cb) != ATOM_OK)
{
/* Exit critical region */
CRITICAL_END ();
/* Quit the loop, returning error */
status = ATOM_ERR_TIMER;
break;
}
/* Flag as no timeout registered */
tcb_ptr->suspend_timo_cb = NULL;
}
/* Exit critical region */
CRITICAL_END ();
/* Request a reschedule */
woken_threads = TRUE;
}
/* No more suspended threads */
else
{
/* Exit critical region and quit the loop */
CRITICAL_END ();
break;
}
}
/* Call scheduler if any threads were woken up */
if (woken_threads == TRUE)
{
/**
* Only call the scheduler if we are in thread context, otherwise
* it will be called on exiting the ISR by atomIntExit().
*/
if (atomCurrentContext())
atomSched (FALSE);
}
}
return (status);
}
/**
* \b atomSemGet
*
* Perform a get operation on a semaphore.
*
* This decrements the current count value for the semaphore and returns.
* If the count value is already zero then the call will block until the
* count is incremented by another thread, or until the specified \c timeout
* is reached. Blocking threads will also be woken if the semaphore is
* deleted by another thread while blocking.
*
* Depending on the \c timeout value specified the call will do one of
* the following if the count value is zero:
*
* \c timeout == 0 : Call will block until the count is non-zero
* \c timeout > 0 : Call will block until non-zero up to the specified timeout
* \c timeout == -1 : Return immediately if the count is zero
*
* If the call needs to block and \c timeout is zero, it will block
* indefinitely until atomSemPut() or atomSemDelete() is called on the
* semaphore.
*
* If the call needs to block and \c timeout is non-zero, the call will only
* block for the specified number of system ticks after which time, if the
* thread was not already woken, the call will return with \c ATOM_TIMEOUT.
*
* If the call would normally block and \c timeout is -1, the call will
* return immediately with \c ATOM_WOULDBLOCK.
*
* This function can only be called from interrupt context if the \c timeout
* parameter is -1 (in which case it does not block).
*
* @param[in] sem Pointer to semaphore object
* @param[in] timeout Max system ticks to block (0 = forever)
*
* @retval ATOM_OK Success
* @retval ATOM_TIMEOUT Semaphore timed out before being woken
* @retval ATOM_WOULDBLOCK Called with timeout == -1 but count is zero
* @retval ATOM_ERR_DELETED Semaphore was deleted while suspended
* @retval ATOM_ERR_CONTEXT Not called in thread context and attempted to block
* @retval ATOM_ERR_PARAM Bad parameter
* @retval ATOM_ERR_QUEUE Problem putting the thread on the suspend queue
* @retval ATOM_ERR_TIMER Problem registering the timeout
*/
uint8_t atomSemGet (ATOM_SEM *sem, int32_t timeout)
{
CRITICAL_STORE;
uint8_t status;
SEM_TIMER timer_data;
ATOM_TIMER timer_cb;
ATOM_TCB *curr_tcb_ptr;
/* Check parameters */
if (sem == NULL)
{
/* Bad semaphore pointer */
status = ATOM_ERR_PARAM;
}
else
{
/* Protect access to the semaphore object and OS queues */
CRITICAL_START ();
/* If count is zero, block the calling thread */
if (sem->count == 0)
{
/* If called with timeout >= 0, we should block */
if (timeout >= 0)
{
/* Count is zero, block the calling thread */
/* Get the current TCB */
curr_tcb_ptr = atomCurrentContext();
/* Check we are actually in thread context */
if (curr_tcb_ptr)
{
/* Add current thread to the suspend list on this semaphore */
if (tcbEnqueuePriority (&sem->suspQ, curr_tcb_ptr) != ATOM_OK)
{
/* Exit critical region */
CRITICAL_END ();
/* There was an error putting this thread on the suspend list */
status = ATOM_ERR_QUEUE;
}
else
{
/* Set suspended status for the current thread */
curr_tcb_ptr->suspended = TRUE;
/* Track errors */
status = ATOM_OK;
/* Register a timer callback if requested */
if (timeout)
{
/* Fill out the data needed by the callback to wake us up */
timer_data.tcb_ptr = curr_tcb_ptr;
timer_data.sem_ptr = sem;
/* Fill out the timer callback request structure */
timer_cb.cb_func = atomSemTimerCallback;
timer_cb.cb_data = (POINTER)&timer_data;
timer_cb.cb_ticks = timeout;
/**
* Store the timer details in the TCB so that we can
* cancel the timer callback if the semaphore is put
* before the timeout occurs.
*/
curr_tcb_ptr->suspend_timo_cb = &timer_cb;
/* Register a callback on timeout */
if (atomTimerRegister (&timer_cb) != ATOM_OK)
{
/* Timer registration failed */
status = ATOM_ERR_TIMER;
/* Clean up and return to the caller */
(void)tcbDequeueEntry (&sem->suspQ, curr_tcb_ptr);
curr_tcb_ptr->suspended = FALSE;
curr_tcb_ptr->suspend_timo_cb = NULL;
}
}
/* Set no timeout requested */
else
{
/* No need to cancel timeouts on this one */
curr_tcb_ptr->suspend_timo_cb = NULL;
}
/* Exit critical region */
CRITICAL_END ();
/* Check no errors have occurred */
if (status == ATOM_OK)
{
/**
* Current thread now blocking, schedule in a new
* one. We already know we are in thread context
* so can call the scheduler from here.
*/
atomSched (FALSE);
/**
* Normal atomSemPut() wakeups will set ATOM_OK status,
* while timeouts will set ATOM_TIMEOUT and semaphore
* deletions will set ATOM_ERR_DELETED.
*/
status = curr_tcb_ptr->suspend_wake_status;
/**
* If we have been woken up with ATOM_OK then
* another thread incremented the semaphore and
* handed control to this thread. In theory the
* the posting thread increments the counter and
* as soon as this thread wakes up we decrement
* the counter here, but to prevent another
* thread preempting this thread and decrementing
* the semaphore before this section was
* scheduled back in, we emulate the increment
* and decrement by not incrementing in the
* atomSemPut() and not decrementing here. The
* count remains zero throughout preventing other
* threads preempting before we decrement the
* count again.
*/
}
}
}
else
{
/* Exit critical region */
CRITICAL_END ();
/* Not currently in thread context, can't suspend */
status = ATOM_ERR_CONTEXT;
}
}
else
{
/* timeout == -1, requested not to block and count is zero */
CRITICAL_END();
status = ATOM_WOULDBLOCK;
}
}
else
{
/* Count is non-zero, just decrement it and return to calling thread */
sem->count--;
/* Exit critical region */
CRITICAL_END ();
/* Successful */
status = ATOM_OK;
}
}
return (status);
}
/**
* \b atomSemPut
*
* Perform a put operation on a semaphore.
*
* This increments the current count value for the semaphore and returns.
*
* If the count value was previously zero and there are threads blocking on the
* semaphore, the call will wake up the highest priority thread suspended. Only
* one thread is woken per call to atomSemPut(). If multiple threads of the
* same priority are suspended, they are woken in order of suspension (FIFO).
*
* This function can be called from interrupt context.
*
* @param[in] sem Pointer to semaphore object
*
* @retval ATOM_OK Success
* @retval ATOM_ERR_OVF The semaphore count would have overflowed (>255)
* @retval ATOM_ERR_PARAM Bad parameter
* @retval ATOM_ERR_QUEUE Problem putting a woken thread on the ready queue
* @retval ATOM_ERR_TIMER Problem cancelling a timeout for a woken thread
*/
uint8_t atomSemPut (ATOM_SEM * sem)
{
uint8_t status;
CRITICAL_STORE;
ATOM_TCB *tcb_ptr;
/* Check parameters */
if (sem == NULL)
{
/* Bad semaphore pointer */
status = ATOM_ERR_PARAM;
}
else
{
/* Protect access to the semaphore object and OS queues */
CRITICAL_START ();
/* If any threads are blocking on the semaphore, wake up one */
if (sem->suspQ)
{
/**
* Threads are woken up in priority order, with a FIFO system
* used on same priority threads. We always take the head,
* ordering is taken care of by an ordered list enqueue.
*/
tcb_ptr = tcbDequeueHead (&sem->suspQ);
if (tcbEnqueuePriority (&tcbReadyQ, tcb_ptr) != ATOM_OK)
{
/* Exit critical region */
CRITICAL_END ();
/* There was a problem putting the thread on the ready queue */
status = ATOM_ERR_QUEUE;
}
else
{
/* Set OK status to be returned to the waiting thread */
tcb_ptr->suspend_wake_status = ATOM_OK;
/* If there's a timeout on this suspension, cancel it */
if ((tcb_ptr->suspend_timo_cb != NULL)
&& (atomTimerCancel (tcb_ptr->suspend_timo_cb) != ATOM_OK))
{
/* There was a problem cancelling a timeout on this semaphore */
status = ATOM_ERR_TIMER;
}
else
{
/* Flag as no timeout registered */
tcb_ptr->suspend_timo_cb = NULL;
/* Successful */
status = ATOM_OK;
}
/* Exit critical region */
CRITICAL_END ();
/**
* The scheduler may now make a policy decision to thread
* switch if we are currently in thread context. If we are
* in interrupt context it will be handled by atomIntExit().
*/
if (atomCurrentContext())
atomSched (FALSE);
}
}
/* If no threads waiting, just increment the count and return */
else
{
/* Check for count overflow */
if (sem->count == 255)
{
/* Don't increment, just return error status */
status = ATOM_ERR_OVF;
}
else
{
/* Increment the count and return success */
sem->count++;
status = ATOM_OK;
}
/* Exit critical region */
CRITICAL_END ();
}
}
return (status);
}
/**
* \b atomSemResetCount
*
* Set a new count value on a semaphore.
*
* Care must be taken when using this function, as there may be threads
* suspended on the semaphore. In general it should only be used once a
* semaphore is out of use.
*
* This function can be called from interrupt context.
*
* @param[in] sem Pointer to semaphore object
* @param[in] count New count value
*
* @retval ATOM_OK Success
* @retval ATOM_ERR_PARAM Bad parameter
*/
uint8_t atomSemResetCount (ATOM_SEM *sem, uint8_t count)
{
uint8_t status;
/* Parameter check */
if (sem == NULL)
{
/* Bad semaphore pointer */
status = ATOM_ERR_PARAM;
}
else
{
/* Set the count */
sem->count = count;
/* Successful */
status = ATOM_OK;
}
return (status);
}
/**
* \b atomSemTimerCallback
*
* This is an internal function not for use by application code.
*
* Timeouts on suspended threads are notified by the timer system through
* this generic callback. The timer system calls us back with a pointer to
* the relevant \c SEM_TIMER object which is used to retrieve the
* semaphore details.
*
* @param[in] cb_data Pointer to a SEM_TIMER object
*/
static void atomSemTimerCallback (POINTER cb_data)
{
SEM_TIMER *timer_data_ptr;
CRITICAL_STORE;
/* Get the SEM_TIMER structure pointer */
timer_data_ptr = (SEM_TIMER *)cb_data;
/* Check parameter is valid */
if (timer_data_ptr)
{
/* Enter critical region */
CRITICAL_START ();
/* Set status to indicate to the waiting thread that it timed out */
timer_data_ptr->tcb_ptr->suspend_wake_status = ATOM_TIMEOUT;
/* Flag as no timeout registered */
timer_data_ptr->tcb_ptr->suspend_timo_cb = NULL;
/* Remove this thread from the semaphore's suspend list */
(void)tcbDequeueEntry (&timer_data_ptr->sem_ptr->suspQ, timer_data_ptr->tcb_ptr);
/* Put the thread on the ready queue */
(void)tcbEnqueuePriority (&tcbReadyQ, timer_data_ptr->tcb_ptr);
/* Exit critical region */
CRITICAL_END ();
/**
* Note that we don't call the scheduler now as it will be called
* when we exit the ISR by atomIntExit().
*/
}
}

45
kernel/atomsem.h Executable file
View File

@@ -0,0 +1,45 @@
/*
* Copyright (c) 2010, Kelvin Lawson. All rights reserved.
*
* 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. No personal names or organizations' names associated with the
* Atomthreads project may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT 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 PROJECT 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.
*/
#ifndef __ATOM_SEM_H
#define __ATOM_SEM_H
typedef struct atom_sem
{
ATOM_TCB * suspQ; /* Queue of threads suspended on this semaphore */
uint8_t count; /* Semaphore count */
} ATOM_SEM;
extern uint8_t atomSemCreate (ATOM_SEM *sem, uint8_t initial_count);
extern uint8_t atomSemDelete (ATOM_SEM *sem);
extern uint8_t atomSemGet (ATOM_SEM *sem, int32_t timeout);
extern uint8_t atomSemPut (ATOM_SEM *sem);
extern uint8_t atomSemResetCount (ATOM_SEM *sem, uint8_t count);
#endif /* __ATOM_SEM_H */

463
kernel/atomtimer.c Executable file
View File

@@ -0,0 +1,463 @@
/*
* Copyright (c) 2010, Kelvin Lawson. All rights reserved.
*
* 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. No personal names or organizations' names associated with the
* Atomthreads project may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT 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 PROJECT 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.
*/
#include <stdio.h>
#include "atom.h"
#include "atomuser.h"
/* Data types */
/* Delay callbacks data structure */
typedef struct delay_timer
{
ATOM_TCB *tcb_ptr; /* Thread which is suspended with timeout */
} DELAY_TIMER;
/* Global data */
/* Local data */
/** Pointer to the head of the outstanding timers queue */
static ATOM_TIMER *timer_queue = NULL;
/** Current system tick count */
static uint32_t system_ticks = 0;
/* Forward declarations */
static void atomTimerCallbacks (void);
static void atomTimerDelayCallback (POINTER cb_data);
/**
* \b atomTimerRegister
*
* Register a timer callback.
*
* Callers should fill out and pass in a timer descriptor, containing
* the number of system ticks until they would like a callback, together
* with a callback function and optional parameter. The number of ticks
* must be greater than zero.
*
* On the relevant system tick count, the callback function will be
* called.
*
* These timers are used by some of the OS library routines, but they
* can also be used by application code requiring timer facilities at
* system tick resolution.
*
* This function can be called from interrupt context, but loops internally
* through the time list, so the potential execution cycles cannot be
* determined in advance.
*
* @param[in] timer_ptr Pointer to timer descriptor
*
* @retval ATOM_OK Success
* @retval ATOM_ERR_PARAM Bad parameters
*/
uint8_t atomTimerRegister (ATOM_TIMER *timer_ptr)
{
uint8_t status;
CRITICAL_STORE;
/* Parameter check */
if ((timer_ptr == NULL) || (timer_ptr->cb_func == NULL)
|| (timer_ptr->cb_ticks == 0))
{
/* Return error */
status = ATOM_ERR_PARAM;
}
else
{
/* Protect the list */
CRITICAL_START ();
/*
* Enqueue in the list of timers.
*
* The list is not ordered, all timers are inserted at the start
* of the list. On each system tick increment the list is walked
* and the remaining ticks count for that timer is decremented.
* Once the remaining ticks reaches zero, the timer callback is
* made.
*/
if (timer_queue == NULL)
{
/* List is empty, insert new head */
timer_ptr->next_timer = NULL;
timer_queue = timer_ptr;
}
else
{
/* List has at least one entry, enqueue new timer before */
timer_ptr->next_timer = timer_queue;
timer_queue = timer_ptr;
}
/* End of list protection */
CRITICAL_END ();
/* Successful */
status = ATOM_OK;
}
return (status);
}
/**
* \b atomTimerCancel
*
* Cancel a timer callback previously registered using atomTimerRegister().
*
* This function can be called from interrupt context, but loops internally
* through the time list, so the potential execution cycles cannot be
* determined in advance.
*
* @param[in] timer_ptr Pointer to timer to cancel
*
* @retval ATOM_OK Success
* @retval ATOM_ERR_PARAM Bad parameters
* @retval ATOM_ERR_NOT_FOUND Timer registration was not found
*/
uint8_t atomTimerCancel (ATOM_TIMER *timer_ptr)
{
uint8_t status = ATOM_ERR_NOT_FOUND;
ATOM_TIMER *prev_ptr, *next_ptr;
CRITICAL_STORE;
/* Parameter check */
if (timer_ptr == NULL)
{
/* Return error */
status = ATOM_ERR_PARAM;
}
else
{
/* Protect the list */
CRITICAL_START ();
/* Walk the list to find the relevant timer */
prev_ptr = next_ptr = timer_queue;
while (next_ptr)
{
/* Is this entry the one we're looking for? */
if (next_ptr == timer_ptr)
{
if (next_ptr == timer_queue)
{
/* We're removing the list head */
timer_queue = next_ptr->next_timer;
}
else
{
/* We're removing a mid or tail TCB */
prev_ptr->next_timer = next_ptr->next_timer;
}
/* Successful */
status = ATOM_OK;
break;
}
/* Move on to the next in the list */
prev_ptr = next_ptr;
next_ptr = next_ptr->next_timer;
}
/* End of list protection */
CRITICAL_END ();
}
return (status);
}
/**
* \b atomTimeGet
*
* Returns the current system tick time.
*
* This function can be called from interrupt context.
*
* @retval Current system tick count
*/
uint32_t atomTimeGet(void)
{
return (system_ticks);
}
/**
* \b atomTimeSet
*
* This is an internal function not for use by application code.
*
* Sets the current system tick time.
*
* Currently only required for automated test suite to test
* clock behaviour.
*
* This function can be called from interrupt context.
*
* @param[in] new_time New system tick time value
*
* @return None
*/
void atomTimeSet(uint32_t new_time)
{
system_ticks = new_time;
}
/**
* \b atomTimerTick
*
* System tick handler.
*
* User ports are responsible for calling this routine once per system tick.
*
* On each system tick this routine is called to do the following:
* 1. Increase the system tick count
* 2. Call back to any registered timer callbacks
*
* @return None
*/
void atomTimerTick (void)
{
/* Only do anything if the OS is started */
if (atomOSStarted)
{
/* Increment the system tick count */
system_ticks++;
/* Check for any callbacks that are due */
atomTimerCallbacks ();
}
}
/**
* \b atomTimerDelay
*
* Suspend a thread for the given number of system ticks.
*
* Note that the wakeup time is the number of ticks from the current system
* tick, therefore, for a one tick delay, the thread may be woken up at any
* time between the atomTimerDelay() call and the next system tick. For
* a minimum number of ticks, you should specify minimum number of ticks + 1.
*
* This function can only be called from thread context.
*
* @param[in] ticks Number of system ticks to delay (must be > 0)
*
* @retval ATOM_OK Successful delay
* @retval ATOM_ERR_PARAM Bad parameter (ticks must be non-zero)
* @retval ATOM_ERR_CONTEXT Not called from thread context
*/
uint8_t atomTimerDelay (uint32_t ticks)
{
ATOM_TCB *curr_tcb_ptr;
ATOM_TIMER timer_cb;
DELAY_TIMER timer_data;
CRITICAL_STORE;
uint8_t status;
/* Get the current TCB */
curr_tcb_ptr = atomCurrentContext();
/* Parameter check */
if (ticks == 0)
{
/* Return error */
status = ATOM_ERR_PARAM;
}
/* Check we are actually in thread context */
else if (curr_tcb_ptr == NULL)
{
/* Not currently in thread context, can't suspend */
status = ATOM_ERR_CONTEXT;
}
/* Otherwise safe to proceed */
else
{
/* Protect the system queues */
CRITICAL_START ();
/* Set suspended status for the current thread */
curr_tcb_ptr->suspended = TRUE;
/* Register the timer callback */
/* Fill out the data needed by the callback to wake us up */
timer_data.tcb_ptr = curr_tcb_ptr;
/* Fill out the timer callback request structure */
timer_cb.cb_func = atomTimerDelayCallback;
timer_cb.cb_data = (POINTER)&timer_data;
timer_cb.cb_ticks = ticks;
/* Store the timeout callback details, though we don't use it */
curr_tcb_ptr->suspend_timo_cb = &timer_cb;
/* Register the callback */
if (atomTimerRegister (&timer_cb) != ATOM_OK)
{
/* Exit critical region */
CRITICAL_END ();
/* Timer registration didn't work, won't get a callback */
status = ATOM_ERR_TIMER;
}
else
{
/* Exit critical region */
CRITICAL_END ();
/* Successful timer registration */
status = ATOM_OK;
/* Current thread should now block, schedule in another */
atomSched (FALSE);
}
}
return (status);
}
/**
* \b atomTimerCallbacks
*
* This is an internal function not for use by application code.
*
* Find any callbacks that are due and call them up.
*
* @return None
*/
static void atomTimerCallbacks (void)
{
ATOM_TIMER *prev_ptr, *next_ptr;
/*
* Walk the list decrementing each timer's remaining ticks count and
* looking for due callbacks.
*/
prev_ptr = next_ptr = timer_queue;
while (next_ptr)
{
/* Is this entry due? */
if (--(next_ptr->cb_ticks) == 0)
{
/* Remove the entry from the timer list */
if (next_ptr == timer_queue)
{
/* We're removing the list head */
timer_queue = next_ptr->next_timer;
}
else
{
/* We're removing a mid or tail timer */
prev_ptr->next_timer = next_ptr->next_timer;
}
/* Call the registered callback */
if (next_ptr->cb_func)
{
next_ptr->cb_func (next_ptr->cb_data);
}
/* Do not update prev_ptr, we have just removed this one */
}
/* Entry is not due, leave it in there with its count decremented */
else
{
/*
* Update prev_ptr to this entry. We will need it if we want
* to remove a mid or tail timer.
*/
prev_ptr = next_ptr;
}
/* Move on to the next in the list */
next_ptr = next_ptr->next_timer;
}
}
/**
* \b atomTimerDelayCallback
*
* This is an internal function not for use by application code.
*
* Callback for atomTimerDelay() calls. Wakes up the sleeping threads.
*
* @param[in] cb_data Callback parameter (DELAY_TIMER ptr for sleeping thread)
*
* @return None
*/
static void atomTimerDelayCallback (POINTER cb_data)
{
DELAY_TIMER *timer_data_ptr;
CRITICAL_STORE;
/* Get the DELAY_TIMER structure pointer */
timer_data_ptr = (DELAY_TIMER *)cb_data;
/* Check parameter is valid */
if (timer_data_ptr)
{
/* Enter critical region */
CRITICAL_START ();
/* Put the thread on the ready queue */
(void)tcbEnqueuePriority (&tcbReadyQ, timer_data_ptr->tcb_ptr);
/* Exit critical region */
CRITICAL_END ();
/**
* Don't call the scheduler yet. The ISR exit routine will do this
* in case there are other callbacks to be made, which may also make
* threads ready.
*/
}
}

60
kernel/atomtimer.h Executable file
View File

@@ -0,0 +1,60 @@
/*
* Copyright (c) 2010, Kelvin Lawson. All rights reserved.
*
* 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. No personal names or organizations' names associated with the
* Atomthreads project may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT 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 PROJECT 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.
*/
#ifndef __ATOM_TIMER_H
#define __ATOM_TIMER_H
#include "atomuser.h"
/* Callback function prototype */
typedef void ( * TIMER_CB_FUNC ) ( POINTER cb_data ) ;
/* Data structures */
/* Timer descriptor */
typedef struct atom_timer
{
TIMER_CB_FUNC cb_func; /* Callback function */
POINTER cb_data; /* Pointer to callback parameter/data */
uint32_t cb_ticks; /* Ticks until callback */
/* Internal data */
struct atom_timer *next_timer; /* Next timer in doubly-linked list */
} ATOM_TIMER;
/* Function prototypes */
extern uint8_t atomTimerRegister (ATOM_TIMER *timer_ptr);
extern uint8_t atomTimerCancel (ATOM_TIMER *timer_ptr);
extern uint8_t atomTimerDelay (uint32_t ticks);
extern uint32_t atomTimeGet (void);
extern void atomTimeSet (uint32_t new_time);
#endif /* __ATOM_TIMER_H */

60
kernel/atomuser-template.h Executable file
View File

@@ -0,0 +1,60 @@
/*
* Copyright (c) 2010, Kelvin Lawson. All rights reserved.
*
* 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. No personal names or organizations' names associated with the
* Atomthreads project may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT 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 PROJECT 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.
*/
#ifndef __ATOM_USER_H
#define __ATOM_USER_H
/* Required number of system ticks per second (normally 100 for 10ms tick) */
#define SYSTEM_TICKS_PER_SEC 100
/**
* Architecture-specific types.
* Uses the stdint.h naming convention, so if stdint.h is available on the
* platform it is simplest to include it from this header.
*/
#define uint8_t unsigned char
#define uint16_t unsigned short
#define uint32_t unsigned long
#define uint64_t unsigned long long
#define int8_t char
#define int16_t short
#define int32_t long
#define int64_t long long
#define POINTER void *
/* Critical region protection */
#define CRITICAL_STORE uint8_t sreg
#define CRITICAL_START() sreg = SREG; cli();
#define CRITICAL_END() SREG = sreg
#endif /* __ATOM_USER_H */

1161
ports/avr/Doxyfile Normal file

File diff suppressed because it is too large Load Diff

103
ports/avr/Makefile Normal file
View File

@@ -0,0 +1,103 @@
############
# Settings #
############
# Build all test applications:
# make
#
# Program a test application using UISP (appname => test app e.g. sems1):
# make program app=appname
# Location of build tools and atomthreads sources
KERNEL_DIR=../../kernel
TESTS_DIR=../../tests
CC=/usr/bin/avr-gcc
OBJCOPY=/usr/bin/avr-objcopy
UISP=/usr/bin/uisp
UISP_DEV=/dev/ttyUSB0
PART=atmega16
# Directory for built objects
BUILD_DIR=build
# Port/application object files
APP_OBJECTS = atomport.o uart.o tests-main.o
APP_ASM_OBJECTS = atomport-asm.o
# Kernel object files
KERNEL_OBJECTS = atomkernel.o atomsem.o atommutex.o atomtimer.o atomqueue.o
# Collection of built objects (excluding test applications)
ALL_OBJECTS = $(APP_OBJECTS) $(APP_ASM_OBJECTS) $(KERNEL_OBJECTS)
BUILT_OBJECTS = $(patsubst %,$(BUILD_DIR)/%,$(ALL_OBJECTS))
# Test object files (dealt with separately as only one per application build)
TEST_OBJECTS = $(notdir $(patsubst %.c,%.o,$(wildcard $(TESTS_DIR)/*.c)))
# Target application filenames (.elf and .hex) for each test object
TEST_ELFS = $(patsubst %.o,%.elf,$(TEST_OBJECTS))
TEST_HEXS = $(patsubst %.o,%.hex,$(TEST_OBJECTS))
# Search build/output directory for dependencies
vpath %.o ./$(BUILD_DIR)
vpath %.elf ./$(BUILD_DIR)
vpath %.hex ./$(BUILD_DIR)
# GCC flags
CFLAGS=-g -mmcu=$(PART) -Wall -Werror
#################
# Build targets #
#################
# All tests
all: $(BUILD_DIR) $(TEST_HEXS) Makefile
# Make build/output directory
$(BUILD_DIR):
mkdir $(BUILD_DIR)
# Test HEX files (one application build for each test)
$(TEST_HEXS): %.hex: %.elf
@echo Building $@
$(OBJCOPY) -j .text -j .data -O ihex $(BUILD_DIR)/$< $(BUILD_DIR)/$@
# Test ELF files (one application build for each test)
$(TEST_ELFS): %.elf: %.o $(KERNEL_OBJECTS) $(APP_OBJECTS) $(APP_ASM_OBJECTS)
$(CC) $(CFLAGS) $(BUILD_DIR)/$(notdir $<) $(BUILT_OBJECTS) --output $(BUILD_DIR)/$@ -Wl,-Map,$(BUILD_DIR)/$(basename $@).map
# Kernel objects builder
$(KERNEL_OBJECTS): %.o: $(KERNEL_DIR)/%.c
$(CC) -c $(CFLAGS) -I. $< -o $(BUILD_DIR)/$(notdir $@)
# Test objects builder
$(TEST_OBJECTS): %.o: $(TESTS_DIR)/%.c
$(CC) -c $(CFLAGS) -I. -I$(KERNEL_DIR) $< -o $(BUILD_DIR)/$(notdir $@)
# Application C objects builder
$(APP_OBJECTS): %.o: ./%.c
$(CC) -c $(CFLAGS) -I. -I$(KERNEL_DIR) -I$(TESTS_DIR) $< -o $(BUILD_DIR)/$(notdir $@)
# Application asm objects builder
$(APP_ASM_OBJECTS): %.o: ./%.s
$(CC) -c $(CFLAGS) -x assembler-with-cpp -I. -I$(KERNEL_DIR) $< -o $(BUILD_DIR)/$(notdir $@)
# .lst file builder
%.lst: %.c
$(CC) $(CFLAGS) -I. -I$(KERNEL_DIR) -I$(TESTS_DIR) -Wa,-al $< > $@
# Clean
clean:
rm -f *.o *.elf *.map *.hex *.bin *.lst
rm -rf doxygen-kernel
rm -rf doxygen-avr
rm -rf build
# Send to STK500
program : $(BUILD_DIR)/$(app).hex
$(UISP) -dprog=stk500 -dserial=$(UISP_DEV) -dpart=$(PART) --erase --upload --verify if=$(BUILD_DIR)/$(app).hex
doxygen:
doxygen $(KERNEL_DIR)/Doxyfile
doxygen ./Doxyfile

362
ports/avr/atomport-asm.s Normal file
View File

@@ -0,0 +1,362 @@
/*
* Copyright (c) 2005, Atomthreads Project. All rights reserved.
*
* 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. No personal names or organizations' names associated with the
* Atomthreads project may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT 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 PROJECT 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.
*/
#include <avr/io.h>
.section .text
/*
* \b archContextSwitch
*
* Architecture-specific context switch routine.
*
* Note that interrupts are always locked out when this routine is
* called. For cooperative switches, the scheduler will have entered
* a critical region. For preemptions (called from an ISR), the
* ISR will have disabled interrupts on entry.
*
* Note that this function might have been coded in C, but gcc
* was generating prologue and epilogue code to handle the parameters.
* Worse, with the naked attribute set it generated half of the
* prologue/epilogue. Rather than work around the gcc code generation,
* which may change from compiler version to compiler version, we
* just write this function in asm, where we have absolute control
* over the code generation. Important during register saves/restores.
*
* @param[in] old_tcb_ptr Pointer to the thread being scheduled out
* @param[in] new_tcb_ptr Pointer to the thread being scheduled in
*
* @return None
*
* void archContextSwitch (ATOM_TCB *old_tcb_ptr, ATOM_TCB *new_tcb_ptr)
*/
.global archContextSwitch
archContextSwitch:
/**
* Parameter locations:
* old_tcb_ptr = R25-R24
* new_tcb_ptr = R23-R22
*/
/**
* If this is a cooperative context switch (a thread has called us
* to schedule itself out), gcc will have saved any of the
* registers R18-R27 and R30-R31 which it does not want us to clobber.
* Any registers of that set which it did not need to save are safe
* not to be saved by us anyway. Hence for cooperative context
* switches we only need to save those registers which gcc expects
* us _not_ to modify, that is R2-R17 and R28-R29.
*
* If we were called from an interrupt routine (because a thread
* is being preemptively scheduled out), the situation is exactly
* the same. Any ISR which calls out to a subroutine will have
* similarly saved those registers which it needs us not to
* clobber. In the case of an interrupt, that is every single
* register of the set R18-R27 and R30-R31. (gcc cannot establish
* which of those registers actually need to be saved because
* the information is not available to an ISR). Again, we only
* need to save the registers R2-R17 and R28-29, because these
* are expected to be unclobbered by a subroutine.
*
* Note that in addition to saving R18-R27 and R30-R31, gcc also
* saves R0, R1 and SREG when entering ISRs. In the case of a
* cooperative context switch, it is not necessary to save these.
*/
/**
* Save registers R2-R17, R28-R29.
*/
push r2
push r3
push r4
push r5
push r6
push r7
push r8
push r9
push r10
push r11
push r12
push r13
push r14
push r15
push r16
push r17
push r28
push r29
/**
* Save the final stack pointer to the TCB. The parameter pointing to
* the old TCB is still untouched in R25-R24. We have saved R16/R17
* and R28/R29 so we can use them for our own purposes now. We must be
* careful not to use R23-R22, however, as these still contain the
* other parameter, old_tcb_ptr.
*/
in r16,_SFR_IO_ADDR(SPL) /* Get the current SP into general regs */
in r17,_SFR_IO_ADDR(SPH) /* R16/R17 which are now free to use. */
mov r28,r24 /* Move old_tcb_ptr param into the Y-regs so we */
mov r29,r25 /* can access the TCB via a pointer. */
st Y,r16 /* Store SPH/SPL to new_tcb_ptr->tcb_save_ptr which */
std Y+1,r17 /* is conveniently the first member of the TCB. */
/**
* At this point, all of the current thread's context has been saved
* so we no longer care about keeping the contents of any registers.
*
* The stack frame if this is a cooperative switch looks as follows:
*
* <Any of R18-R27 and R30-R31 that the calling function saves>
* <Return address to calling function>
* <R2>
* <R3>
* ||
* <R16>
* <R17>
* <R28>
* <R29>
*
* The stack frame if this was a preemptive switch looks as follows:
*
* <R1> // saved by ISR
* <R0> //
* <SREG> //
* <R18> //
* <R19> //
* || //
* <R26> //
* <R27> //
* <R30> //
* <R31> //
* <Return address to ISR>
* <Any stacking and return addresses between ISR and this call>
* <R2>
* <R3>
* ||
* <R16>
* <R17>
* <R28>
* <R29>
*
*
* In addition, the thread's stack pointer (after context-save) is
* stored in the thread's TCB.
*/
/**
* We are now ready to restore the new thread's context. We switch
* our stack pointer to the new thread's stack pointer, and pop
* all of its context off the stack. When we have finished popping
* all registers (R2-R17 and R28-R29), we are ready to return.
*
* Note that any further registers that needed to be saved for the
* thread will be restored on exiting this function. If the new
* thread previously scheduled itself out cooperatively, the
* original calling function will restore any registers it chose
* to save. If the new thread was preempted, we will return to the
* ISR which will restore all other system registers, before
* returning to the interrupted thread.
*/
/**
* Get the new thread's stack pointer off the TCB (new_tcb_ptr).
* new_tcb_ptr is still stored in the parameter registers, R23-R22.
* We are free to use any other registers, however, as we haven't
* yet popped any of the new thread's context off its stack.
*/
mov r28,r22 /* Move new_tcb_ptr into the Y-regs so we */
mov r29,r23 /* can access the TCB via a pointer. */
ld r16,Y /* Load new_tcb_ptr->sp_save_ptr into R16/R17. */
ldd r17,Y+1 /* It is conveniently the first member of the TCB. */
out _SFR_IO_ADDR(SPL),r16 /* Set our stack pointer to the new thread's */
out _SFR_IO_ADDR(SPH),r17 /* stack pointer, from its TCB. */
/**
* Restore registers R2-R17, R28-R29.
*/
pop r29
pop r28
pop r17
pop r16
pop r15
pop r14
pop r13
pop r12
pop r11
pop r10
pop r9
pop r8
pop r7
pop r6
pop r5
pop r4
pop r3
pop r2
/**
* The return address on the stack will now be the new thread's return
* address - i.e. although we just entered this function from a
* function called by the old thread, now that we have restored the new
* thread's context, we actually return from this function to wherever
* the new thread was when it was scheduled out. This could be either a
* regular C routine if the new thread previously scheduled itself out
* cooperatively, or it could be an ISR if this new thread was
* previously preempted (on exiting the ISR, execution will return to
* wherever the new thread was originally interrupted).
*/
/**
* Note that we always just perform a RET here. Although we may
* come in from an ISR and leave through a regular C routine for
* another thread (and visa versa) this is OK, because we don't
* actually need to perform a RETI to tell the processor the
* interrupt is finished. The only extra thing that RETI does is to
* set the I bit (interrupts enabled). If we enter from a regular
* thread context, but leave through an ISR return address and a RETI,
* that just returns and handily enables interrupts for us. Similarly
* if we enter from an ISR and leave back into some thread context
* calls, interrupts will remain disabled through the regular RET
* calls, and we will reenable interrupts in the CRITICAL_END() call
* when we unlock interrupts.
*/
ret
/**
* \b archFirstThreadRestore
*
* Architecture-specific function to restore and start the first thread.
* This is called by atomOSStart() when the OS is starting.
*
* This function will be largely similar to the latter half of
* archContextSwitch(). Its job is to restore the context for the
* first thread, and finally enable interrupts.
*
* It expects to see the context saved in the same way as if the
* thread has been previously scheduled out, and had its context
* saved. That is, archThreadContextInit() will have been called
* first (via atomThreadCreate()) to create a "fake" context save
* area, containing the relevant register-save values for a thread
* restore.
*
* Note that you can create more than one thread before starting
* the OS - only one thread is restored using this function, so
* all other threads are actually restored by archContextSwitch().
* This is another reminder that the initial context set up by
* archThreadContextInit() must look the same whether restored by
* archFirstThreadRestore() or archContextSwitch().
*
* @param[in] new_tcb_ptr Pointer to the thread being scheduled in
*
* @return None
*
* void archFirstThreadRestore (ATOM_TCB *new_tcb_ptr)
*/
.global archFirstThreadRestore
archFirstThreadRestore:
/**
* Parameter locations:
* new_tcb_ptr = R25-R24
*/
/**
* First thread restores in the AVR port expect to see R2-R17 and
* R28-R29 stored as context. The context will look exactly like it
* would had a thread cooperatively scheduled itself out. That is,
* these registers will be stored on the stack, and above those will
* be the return address of the calling function. In this case we
* will have set up this "fake" context in archThreadContextInit(),
* and above these registers will be the return address of the thread
* entry point. A "ret" or "reti" instruction will therefore direct
* the processor to the thread entry point, by popping this "return
* address" off the stack.
*/
/**
* Get the new thread's stack pointer off the TCB (new_tcb_ptr).
* new_tcb_ptr is stored in the parameter registers, R25-R24.
* We are free to use any other registers, however, as we haven't
* yet popped any of the new thread's context off its stack.
*/
mov r28,r24 /* Move new_tcb_ptr into the Y-regs so we */
mov r29,r25 /* can access the TCB via a pointer. */
ld r16,Y /* Load new_tcb_ptr->sp_save_ptr into R16/R17. */
ldd r17,Y+1 /* It is conveniently the first member of the TCB. */
out _SFR_IO_ADDR(SPL),r16 /* Set our stack pointer to the new thread's */
out _SFR_IO_ADDR(SPH),r17 /* stack pointer, from its TCB. */
/**
* Restore registers R2-R17, R28-R29.
*/
pop r29
pop r28
pop r17
pop r16
pop r15
pop r14
pop r13
pop r12
pop r11
pop r10
pop r9
pop r8
pop r7
pop r6
pop r5
pop r4
pop r3
pop r2
/**
* The "return address" left on the stack now will be the new
* thread's entry point. RETI will take us there as if we had
* actually been there before calling this subroutine, whereas
* the return address was actually set up by archThreadContextInit().
*
* As discussed above, this function is responsible for enabling
* interrupts once all context has been restored. We can do this
* using a single RETI instruction (return and enable interrupts),
* but it is also safe at this point to have two separate
* instructions:
* sei // enable interrupts
* ret // return to new thread entry point
*/
reti

329
ports/avr/atomport.c Normal file
View File

@@ -0,0 +1,329 @@
/*
* Copyright (c) 2010, Kelvin Lawson. All rights reserved.
*
* 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. No personal names or organizations' names associated with the
* Atomthreads project may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT 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 PROJECT 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.
*/
#include <avr/interrupt.h>
#include "atom.h"
#include "atomport.h"
/** Forward declarations */
static void thread_shell (void);
/**
* \b thread_shell
*
* Shell routine which is used to call all thread entry points.
*
* This routine is called whenever a new thread is starting, and is
* responsible for taking the entry point parameter off the TCB
* and passing this into the thread entry point, as well as enabling
* interrupts. This is an optional function for a port, as it is
* also possible to do this with a regular context restore if the
* appropriate registers are saved for context switches (parameter
* and interrupt enable). Alternatively a flag could be used to
* notify archFirstThreadRestore() and archContextSwitch()
* that they should this time restore the contents of the parameter
* registers and enable interrupts. After restoring a thread first
* time the context restore routines need not perform those
* operations again. This is discussed in more detail below.
*
* When starting new threads, ports must pass in the entry point
* function parameter, and enable interrupts. This can be handled by
* the first context restore for new threads if they are saved as
* part of the thread's context. However for the AVR port we have
* chosen to save only the minimum registers required for context
* switches. This reduces the cycle time required for all context
* switches during operation. This means, however, that we don't save
* the parameter registers R25/R24 or the SREG. These would otherwise
* be used when restoring a thread for the first time to pass the
* parameter to the entry point, and enabling interrupts in the SREG.
*
* A few of the possible ways round this are to:
*
* a) Save R25/R24 and SREG with the normal context - thus increasing
* the processor cycles required on each context switch.
* b) Use a thread shell routine which is used to start all threads.
* This routine can then pass the parameter and enable interrupts,
* without incurring any overhead on all context switches.
* c) Store a flag in a new thread's TCB to notify the normal context
* switch routine that it must (the first time a thread is restored
* only) pass the parameter, and enable interrupts.
*
* We have chosen to implement (b) in this case, as it does not affect
* normal context switch times just for the benefit of the first restore,
* and it does not incur extra complication to the thread restore
* routines through handling special cases. A thread shell is also handy
* for providing port users with a place to do any initialisation that
* must be done for each thread (e.g. opening stdio files etc).
*
* Other ports are free to implement whatever scheme they wish. In
* particular if you save all necessary registers on a context switch
* then you need not worry about any special requirements for
* starting threads for the first time.
*
* @return None
*/
static void thread_shell (void)
{
ATOM_TCB *curr_tcb;
/* Get the TCB of the thread being started */
curr_tcb = atomCurrentContext();
/**
* Enable interrupts - these will not be enabled when a thread
* is first restored.
*/
sei();
/* Call the thread entry point */
if (curr_tcb && curr_tcb->entry_point)
{
curr_tcb->entry_point(curr_tcb->entry_param);
}
/* Not reached - threads should never return from the entry point */
}
/**
* \b archThreadContextInit
*
* Architecture-specific thread context initialisation routine.
*
* This function must set up a thread's context ready for restoring
* and running the thread via archFirstThreadRestore() or
* archContextSwitch().
*
* The layout required to fill the correct register values is
* described in archContextSwitch(). Note that not all registers
* are restored by archContextSwitch() and archFirstThreadRestore()
* as this port takes advantage of the fact that not all registers
* must be stored by gcc-avr C subroutines. This means that we don't
* provide start values for those registers, as they are "don't cares".
*
* Because we don't actually save the parameter registers (R25-R24)
* for this particular architecture, we use a separate thread shell.
* The thread shell is always used as the thread entry point stored
* in the thread context, and it does the actual calling of the
* proper thread entry point, passing the thread entry parameter.
* This allows us to pass the entry parameter without actually
* storing it on the stack (the thread shell routine takes the
* entry point and parameter from the thread's TCB). On other ports
* you may instead choose to store the entry point and parameter
* in the thread context and use no thread shell routine.
*
* Similarly we use the thread shell in this case to enable interrupts.
* When a thread is restored and started for the first time, it must
* also enable interrupts. This might be done by setting up the
* appropriate value in the SREG register for enabled interrupts, which
* would then be restored when the thread is first started. But to
* reduce register-saves we do not save SREG on the AVR port, and
* instead we use the thread shell routine to enable interrupts the
* first time a thread is started.
*
* @param[in] tcb_ptr Pointer to the TCB of the thread being created
* @param[in] stack_top Pointer to the top of the new thread's stack
* @param[in] entry_point Pointer to the thread entry point function
* @param[in] entry_param Parameter to be passed to the thread entry point
*
* @return None
*/
void archThreadContextInit (ATOM_TCB *tcb_ptr, void *stack_top, void (*entry_point)(uint32_t), uint32_t entry_param)
{
uint8_t *stack_ptr;
/** Start at stack top */
stack_ptr = (uint8_t *)stack_top;
/**
* After restoring all of the context registers, the thread restore
* routines will perform a RET or RETI which expect to find the
* address of the calling routine on the stack. In this case (the
* first time a thread is run) we "return" to the entry point for
* the thread. That is, we store the thread entry point in the
* place that RET and RETI will look for the return address: the
* stack.
*
* Note that we are using the thread_shell() routine to start all
* threads, so we actually store the address of thread_shell()
* here. Other ports may store the real thread entry point here
* and call it directly from the thread restore routines.
*
* Because we are filling the stack from top to bottom, this goes
* on the stack first (at the top).
*/
*stack_ptr-- = (uint8_t)((uint16_t)thread_shell & 0xFF);
*stack_ptr-- = (uint8_t)(((uint16_t)thread_shell >> 8) & 0xFF);
/**
* For the AVR port the parameter registers (R25-R24) are not
* saved and restored by the context switch routines. This means
* that they cannot be used to pass a parameter to the entry
* point the first time a thread is restored. Rather than incur
* the overhead of saving them (just for the benefit of starting
* threads) we can either use a flag here to notify the context
* restore routines (archThreadFirstRestore() and
* archContextSwitch()) that they should restore R25-R24 this
* one time, or we can use a thread shell routine which replaces
* that actual thread entry point. In this case we use a thread
* shell which is responsible for passing the parameters to the
* actual thread entry point, without adding extra processing
* to the context switch routines.
*
* Other ports may wish to store entry_param in the appropriate
* parameter registers when creating a thread's context,
* particularly if that port saves those registers anyway.
*
* Similarly, although interrupts must be enabled when starting
* new threads, we also defer this to the thread shell because
* we don't save the SREG contents for normal context switches.
* Other ports may choose to context switch the relevant
* interrupt enable register, so that the first context switch
* is able to enable interrupts during its normal context
* restore.
*/
/**
* Store starting register values for R2-R17, R28-R29
*/
*stack_ptr-- = 0x00; /* R2 */
*stack_ptr-- = 0x00; /* R3 */
*stack_ptr-- = 0x00; /* R4 */
*stack_ptr-- = 0x00; /* R5 */
*stack_ptr-- = 0x00; /* R6 */
*stack_ptr-- = 0x00; /* R7 */
*stack_ptr-- = 0x00; /* R8 */
*stack_ptr-- = 0x00; /* R9 */
*stack_ptr-- = 0x00; /* R10 */
*stack_ptr-- = 0x00; /* R11 */
*stack_ptr-- = 0x00; /* R12 */
*stack_ptr-- = 0x00; /* R13 */
*stack_ptr-- = 0x00; /* R14 */
*stack_ptr-- = 0x00; /* R15 */
*stack_ptr-- = 0x00; /* R16 */
*stack_ptr-- = 0x00; /* R17 */
*stack_ptr-- = 0x00; /* R28 */
*stack_ptr-- = 0x00; /* R29 */
/**
* All thread context has now been initialised. Save the current
* stack pointer to the thread's TCB so it knows where to start
* looking when the thread is started.
*/
tcb_ptr->sp_save_ptr = stack_ptr;
}
/**
* \b avrInitSystemTickTimer
*
* Initialise the system tick timer. Uses the AVR's timer1 facility.
*
* @return None
*/
void avrInitSystemTickTimer ( void )
{
/* Set timer 1 compare match value for configured system tick,
* with a prescaler of 256. We will get a compare match 1A
* interrupt on every system tick, in which we must call the
* OS's system tick handler. */
OCR1A = (AVR_CPU_HZ / 256 / SYSTEM_TICKS_PER_SEC);
/* Enable compare match 1A interrupt */
TIMSK = _BV(OCIE1A);
/* Set prescaler 256 */
TCCR1B = _BV(CS12) | _BV(WGM12);
}
/**
*
* System tick ISR.
*
* This is responsible for regularly calling the OS system tick handler.
* The system tick handler checks if any timer callbacks are necessary,
* and runs the scheduler.
*
* The compiler automatically saves all registers necessary before calling
* out to a C routine. This will be (at least) R0, R1, SREG, R18-R27 and
* R30/R31.
*
* The system may decide to schedule in a new thread during the call to
* atomTimerTick(), in which case around half of the thread's context will
* already have been saved here, ready for when we return here when the
* interrupted thread is scheduled back in. The remaining context will be
* saved by the context switch routine.
*
* As with all interrupts, the ISR should call atomIntEnter() and
* atomIntExit() on entry and exit. This serves two purposes:
*
* a) To notify the OS that it is running in interrupt context
* b) To defer the scheduler until after the ISR is completed
*
* We defer all scheduling decisions until after the ISR has completed
* in case the interrupt handler makes more than one thread ready.
*
* @return None
*/
ISR (TIMER1_COMPA_vect)
{
/* Call the interrupt entry routine */
atomIntEnter();
/* Call the OS system tick handler */
atomTimerTick();
/* Call the interrupt exit routine */
atomIntExit(TRUE);
}
/**
*
* Default (no handler installed) ISR.
*
* Installs a default handler to be called if any interrupts occur for
* which we have not registered an ISR. This is empty and has only been
* included to handle user-created code which may enable interrupts. The
* core OS does not enable any interrupts other than the system timer
* tick interrupt.
*
* @return None
*/
ISR (BADISR_vect)
{
/* Empty */
}

39
ports/avr/atomport.h Normal file
View File

@@ -0,0 +1,39 @@
/*
* Copyright (c) 2010, Kelvin Lawson. All rights reserved.
*
* 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. No personal names or organizations' names associated with the
* Atomthreads project may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT 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 PROJECT 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.
*/
#ifndef __ATOM_PORT_H
#define __ATOM_PORT_H
/* CPU Frequency */
#define AVR_CPU_HZ 1000000
/* Function prototypes */
void avrInitSystemTickTimer ( void );
#endif /* __ATOM_PORT_H */

59
ports/avr/atomuser.h Normal file
View File

@@ -0,0 +1,59 @@
/*
* Copyright (c) 2010, Kelvin Lawson. All rights reserved.
*
* 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. No personal names or organizations' names associated with the
* Atomthreads project may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT 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 PROJECT 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.
*/
#ifndef __ATOM_USER_H
#define __ATOM_USER_H
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
/* Portable uint8_t and friends available from stdint.h on this platform */
#include <stdint.h>
/* Required number of system ticks per second (normally 100 for 10ms tick) */
#define SYSTEM_TICKS_PER_SEC 100
/**
* Architecture-specific types.
* Most of these are available from stdint.h on this platform, which is
* included above.
*/
#define POINTER void *
/* Critical region protection */
#define CRITICAL_STORE uint8_t sreg
#define CRITICAL_START() sreg = SREG; cli();
#define CRITICAL_END() SREG = sreg
#endif /* __ATOM_USER_H */

6
ports/avr/gdb.txt Normal file
View File

@@ -0,0 +1,6 @@
delete
file atomapp.elf
target remote localhost:1212
load
break main_thread_func
cont

238
ports/avr/tests-main.c Normal file
View File

@@ -0,0 +1,238 @@
/*
* Copyright (c) 2010, Kelvin Lawson. All rights reserved.
*
* 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. No personal names or organizations' names associated with the
* Atomthreads project may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT 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 PROJECT 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.
*/
#include <stdio.h>
#include <avr/pgmspace.h>
#include "atom.h"
#include "atomport.h"
#include "atomtests.h"
#include "atomtimer.h"
#include "uart.h"
/* Constants */
/*
* Idle thread stack size
*
* This needs to be large enough to handle any interrupt handlers
* and callbacks called by interrupt handlers (e.g. user-created
* timer callbacks) as well as the saving of all context when
* switching away from this thread.
*
* In this case, the idle stack is allocated on the BSS via the
* idle_thread_stack[] byte array.
*/
#define IDLE_STACK_SIZE_BYTES 128
/*
* Startup code stack size
*
* This defines the size of stack allowed for the main() startup
* code before the OS is actually started. This needs to be large
* enough to manage the atomOSInit(), atomOSStart() and
* atomThreadCreate() calls which occur before the OS is started.
*
* In this case we use the default startup stack location used by
* avr-gcc of the top of RAM (defined as RAMEND). After the OS
* is started this allocation is no longer required,therefore
* you could alternatively use some location which you know that
* your application will not use until the OS is started. Note
* that you cannot use the idle thread or main thread stack here
* because the stack contexts of these threads are initialised
* during OS creation.
*
* Instead of reusing some application area, here we set aside
* 64 bytes of RAM for this purpose, because we call out to
* several different test applications, and do not know of any
* particular application locations which will be free to use.
*/
#define STARTUP_STACK_SIZE_BYTES 64
/*
* Main thread stack size
*
* Here we utilise the space starting at 64 bytes below the startup
* stack for the Main application thread. Note that this is not a
* required OS kernel thread - you will replace this with your own
* application thread.
*
* In this case the Main thread is responsible for calling out to the
* test routines. Once a test routine has finished, the thread remains
* running and loops printing out an error message (if the test failed)
* or flashes a LED once per second (if the test passed).
*
* The Main thread stack generally needs to be larger than the idle
* thread stack, as not only does it need to store interrupt handler
* stack saves and context switch saves, but the application main thread
* will generally be carrying out more nested function calls and require
* stack for application code local variables etc.
*
* With all OS tests implemented to date on the AVR, the Main thread
* stack has not exceeded 147 bytes. Care must be taken to ensure that
* the data section, BSS section, and 64 byte startup section leave
* enough free space for the main thread. You can use the avr-size
* command to view the size of the BSS and data sections in your
* application ELF files. For example if you require a 196 byte main
* thread stack, then the data, BSS and startup stack combined must
* not exceed RAMSIZE-196 bytes.
*/
/* Local data */
/* Application threads' TCBs */
static ATOM_TCB main_tcb;
/* Idle thread's stack area */
static uint8_t idle_thread_stack[IDLE_STACK_SIZE_BYTES];
/* STDIO stream */
static FILE uart_stdout = FDEV_SETUP_STREAM(uart_putchar, NULL, _FDEV_SETUP_WRITE);
/* Forward declarations */
static void main_thread_func (uint32_t data);
/**
* \b main
*
* Program entry point.
*
* Sets up the AVR hardware resources (system tick timer interrupt) necessary
* for the OS to be started. Creates an application thread and starts the OS.
*/
int main ( void )
{
int8_t status;
/**
* Note: to protect OS structures and data during initialisation,
* interrupts must remain disabled until the first thread
* has been restored. They are reenabled at the very end of
* the first thread restore, at which point it is safe for a
* reschedule to take place.
*/
/* Initialise the OS before creating our threads */
status = atomOSInit(&idle_thread_stack[IDLE_STACK_SIZE_BYTES - 1]);
if (status == ATOM_OK)
{
/* Enable the system tick timer */
avrInitSystemTickTimer();
/* Create an application thread */
status = atomThreadCreate(&main_tcb,
TEST_THREAD_PRIO, main_thread_func, 0,
(POINTER)(RAMEND-STARTUP_STACK_SIZE_BYTES));
if (status == ATOM_OK)
{
/**
* First application thread successfully created. It is
* now possible to start the OS. Execution will not return
* from atomOSStart(), which will restore the context of
* our application thread and start executing it.
*
* Note that interrupts are still disabled at this point.
* They will be enabled as we restore and execute our first
* thread in archFirstThreadRestore().
*/
atomOSStart();
}
}
while (1)
;
/* There was an error starting the OS if we reach here */
return (0);
}
/**
* \b main_thread_func
*
* Entry point for main application thread.
*
* This is the first thread that will be executed when the OS is started.
*
* @param[in] data Unused (optional thread entry parameter)
*
* @return None
*/
static void main_thread_func (uint32_t data)
{
uint32_t test_status;
/* Enable all LEDs (STK500-specific) */
DDRB = 0xFF;
PORTB = 0xFF;
/* Initialise UART (9600bps) */
if (uart_init(9600) != 0)
{
/* Error initialising UART */
}
/**
* Redirect stdout via the UART. Note that the UART write routine
* is protected via a semaphore, so the OS must be started before
* use of the UART.
*/
stdout = &uart_stdout;
/* Put a message out on the UART */
printf_P(PSTR("Go\n"));
/* Start test. All tests use the same start API. */
test_status = test_start();
/* Test finished, sleep forever */
while (1)
{
/* Log test status */
if (test_status == 0)
{
/* Toggle a LED (STK500-specific) */
PORTB ^= (1 << 7);
}
else
{
printf_P (PSTR("Fail%d\n"), atomTimeGet());
}
/* Sleep for one second and log status again */
atomTimerDelay(SYSTEM_TICKS_PER_SEC);
}
}

85
ports/avr/uart.c Normal file
View File

@@ -0,0 +1,85 @@
/*
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* <joerg@FreeBSD.ORG> wrote this file. As long as you retain this notice you
* can do whatever you want with this stuff. If we meet some day, and you think
* this stuff is worth it, you can buy me a beer in return. Joerg Wunsch
* ----------------------------------------------------------------------------
*
* Stdio demo, UART implementation
*
* $Id: uart.c,v 1.1 2005/12/28 21:38:59 joerg_wunsch Exp $
*/
#include <stdint.h>
#include <stdio.h>
#include <avr/io.h>
#include "atom.h"
#include "atommutex.h"
#include "atomport.h"
#include "uart.h"
/*
* Semaphore for single-threaded access to UART device
*/
static ATOM_MUTEX uart_mutex;
/*
* Initialize the UART to 9600 Bd, tx/rx, 8N1.
*/
int
uart_init(uint32_t baudrate)
{
int status;
/* Set up the UART device with the selected baudrate */
#if AVR_CPU_HZ < 2000000UL && defined(U2X)
UCSRA = _BV(U2X); /* improve baud rate error by using 2x clk */
UBRRL = (AVR_CPU_HZ / (8UL * baudrate)) - 1;
#else
UBRRL = (AVR_CPU_HZ / (16UL * baudrate)) - 1;
#endif
UCSRB = _BV(TXEN) | _BV(RXEN); /* tx/rx enable */
/* Create a mutex for single-threaded putchar() access */
if (atomMutexCreate (&uart_mutex) != ATOM_OK)
{
status = -1;
}
else
{
status = 0;
}
/* Finished */
return (status);
}
/*
* Send character c down the UART Tx, wait until tx holding register
* is empty.
*/
int
uart_putchar(char c, FILE *stream)
{
/* Block on private access to the UART */
if (atomMutexGet(&uart_mutex, 0) == ATOM_OK)
{
/* Convert \n to \r\n */
if (c == '\n')
uart_putchar('\r', stream);
/* Wait until the UART is ready then send the character out */
loop_until_bit_is_set(UCSRA, UDRE);
UDR = c;
/* Return mutex access */
atomMutexPut(&uart_mutex);
}
return 0;
}

25
ports/avr/uart.h Normal file
View File

@@ -0,0 +1,25 @@
/*
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* <joerg@FreeBSD.ORG> wrote this file. As long as you retain this notice you
* can do whatever you want with this stuff. If we meet some day, and you think
* this stuff is worth it, you can buy me a beer in return. Joerg Wunsch
* ----------------------------------------------------------------------------
*
* Stdio demo, UART declarations
*
* $Id: uart.h,v 1.1 2005/12/28 21:38:59 joerg_wunsch Exp $
*/
#include "atom.h"
/*
* Perform UART startup initialization.
*/
int uart_init(uint32_t baudrate);
/*
* Send one character to the UART.
*/
int uart_putchar(char c, FILE *stream);

60
tests/atomtests.h Normal file
View File

@@ -0,0 +1,60 @@
/*
* Copyright (c) 2010, Kelvin Lawson. All rights reserved.
*
* 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. No personal names or organizations' names associated with the
* Atomthreads project may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT 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 PROJECT 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.
*/
#ifndef __ATOM_TESTS_H
#define __ATOM_TESTS_H
/* Include Atomthreads kernel API */
#include "atom.h"
/* Prerequisite include for ATOMLOG() macro (via printf) */
#include <stdio.h>
/* Logger macro for viewing test results */
#define ATOMLOG printf_P
/*
* String location macro: for platforms which need to place strings in
* alternative locations, e.g. on avr-gcc strings can be placed in
* program space, saving SRAM. On most platforms this can expand to
* empty.
*/
#define _STR(x) PSTR(x)
/* Default thread stack size (in bytes) */
#define TEST_THREAD_STACK_SIZE 128
/* Default thread priority */
#define TEST_THREAD_PRIO 16
/* API for starting each test */
extern uint32_t test_start (void);
#endif /* __ATOM_TESTS_H */

120
tests/kern1.c Normal file
View File

@@ -0,0 +1,120 @@
/*
* Copyright (c) 2010, Kelvin Lawson. All rights reserved.
*
* 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. No personal names or organizations' names associated with the
* Atomthreads project may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT 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 PROJECT 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.
*/
#include "atom.h"
#include "atomtests.h"
/* Test OS objects */
static ATOM_TCB tcb1;
static uint8_t test_thread_stack[TEST_THREAD_STACK_SIZE];
/* Forward declarations */
static void test_thread_func (uint32_t data);
/**
* \b test_start
*
* Start kernel test.
*
* This tests the handling of bad parameters within the public kernel APIs.
*
* Other than during initialisation, the only API that takes parameters
* which require checking (and that application code might call) is the
* thread create API.
*
* @retval Number of failures
*/
uint32_t test_start (void)
{
int failures;
/* Default to zero failures */
failures = 0;
/* atomThreadCreate: Pass a bad TCB pointer */
if (atomThreadCreate (NULL, TEST_THREAD_PRIO, test_thread_func, 0,
&test_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_ERR_PARAM)
{
ATOMLOG (_STR("Bad TCB check\n"));
failures++;
}
/* atomThreadCreate: Pass a bad entry point */
if (atomThreadCreate (&tcb1, TEST_THREAD_PRIO, NULL, 0,
&test_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_ERR_PARAM)
{
ATOMLOG (_STR("Bad entry check\n"));
failures++;
}
/* atomThreadCreate: Pass a bad stack pointer */
if (atomThreadCreate (&tcb1, TEST_THREAD_PRIO, test_thread_func, 0,
NULL) != ATOM_ERR_PARAM)
{
ATOMLOG (_STR("Bad stack ptr check\n"));
failures++;
}
/* Log final status */
if (failures == 0)
{
ATOMLOG (_STR("Pass\n"));
}
else
{
ATOMLOG (_STR("Fail(%d)\n"), failures);
}
/* Quit */
return failures;
}
/**
* \b test_thread_func
*
* Entry point for test thread.
*
* @param[in] data Unused (optional thread entry parameter)
*
* @return None
*/
static void test_thread_func (uint32_t data)
{
/* Wait forever */
while (1)
{
atomTimerDelay (SYSTEM_TICKS_PER_SEC);
}
}

198
tests/kern2.c Normal file
View File

@@ -0,0 +1,198 @@
/*
* Copyright (c) 2010, Kelvin Lawson. All rights reserved.
*
* 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. No personal names or organizations' names associated with the
* Atomthreads project may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT 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 PROJECT 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.
*/
#include "atom.h"
#include "atomtests.h"
/**
* \b test_start
*
* Start kernel test.
*
* This is a basic test of the thread context-switch functionality. It
* creates twenty local (byte) variables and schedules the thread out
* for one second. If context-switch save/restore is not implemented
* correctly, you might expect one or more of these local variables to
* be corrupted by the time the thread is scheduled back in.
*
* Note that this is a fairly unsophisticated test, and a lot depends on
* how the compiler deals with the variables, as well as what code is
* executed while the thread is scheduled out. It should flag up any
* major problems with the context-switch, however.
*
* @retval Number of failures
*/
uint32_t test_start (void)
{
int failures;
uint8_t one = 1;
uint8_t two = 2;
uint8_t three = 3;
uint8_t four = 4;
uint8_t five = 5;
uint8_t six = 6;
uint8_t seven = 7;
uint8_t eight = 8;
uint8_t nine = 9;
uint8_t ten = 10;
uint8_t eleven = 11;
uint8_t twelve = 12;
uint8_t thirteen = 13;
uint8_t fourteen = 14;
uint8_t fifteen = 15;
uint8_t sixteen = 16;
uint8_t seventeen = 17;
uint8_t eighteen = 18;
uint8_t nineteen = 19;
uint8_t twenty = 20;
/* Default to zero failures */
failures = 0;
/* Sleep for one second */
atomTimerDelay(SYSTEM_TICKS_PER_SEC);
/* Check all variables contain expected values */
if (one != 1)
{
ATOMLOG (_STR("1(%d)\n"), one);
failures++;
}
if (two != 2)
{
ATOMLOG (_STR("2(%d)\n"), two);
failures++;
}
if (three != 3)
{
ATOMLOG (_STR("3(%d)\n"), three);
failures++;
}
if (four != 4)
{
ATOMLOG (_STR("4(%d)\n"), four);
failures++;
}
if (five != 5)
{
ATOMLOG (_STR("5(%d)\n"), five);
failures++;
}
if (six != 6)
{
ATOMLOG (_STR("6(%d)\n"), six);
failures++;
}
if (seven != 7)
{
ATOMLOG (_STR("7(%d)\n"), seven);
failures++;
}
if (eight != 8)
{
ATOMLOG (_STR("8(%d)\n"), eight);
failures++;
}
if (nine != 9)
{
ATOMLOG (_STR("9(%d)\n"), nine);
failures++;
}
if (ten != 10)
{
ATOMLOG (_STR("10(%d)\n"), ten);
failures++;
}
if (eleven != 11)
{
ATOMLOG (_STR("11(%d)\n"), eleven);
failures++;
}
if (twelve != 12)
{
ATOMLOG (_STR("12(%d)\n"), twelve);
failures++;
}
if (thirteen != 13)
{
ATOMLOG (_STR("13(%d)\n"), thirteen);
failures++;
}
if (fourteen != 14)
{
ATOMLOG (_STR("14(%d)\n"), fourteen);
failures++;
}
if (fifteen != 15)
{
ATOMLOG (_STR("15(%d)\n"), fifteen);
failures++;
}
if (sixteen != 16)
{
ATOMLOG (_STR("16(%d)\n"), sixteen);
failures++;
}
if (seventeen != 17)
{
ATOMLOG (_STR("17(%d)\n"), seventeen);
failures++;
}
if (eighteen != 18)
{
ATOMLOG (_STR("18(%d)\n"), eighteen);
failures++;
}
if (nineteen != 19)
{
ATOMLOG (_STR("19(%d)\n"), nineteen);
failures++;
}
if (twenty != 20)
{
ATOMLOG (_STR("20(%d)\n"), twenty);
failures++;
}
/* Log final status */
if (failures == 0)
{
ATOMLOG (_STR("Pass\n"));
}
else
{
ATOMLOG (_STR("Fail(%d)\n"), failures);
}
/* Quit */
return failures;
}

213
tests/kern3.c Normal file
View File

@@ -0,0 +1,213 @@
/*
* Copyright (c) 2010, Kelvin Lawson. All rights reserved.
*
* 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. No personal names or organizations' names associated with the
* Atomthreads project may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT 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 PROJECT 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.
*/
#include "atom.h"
#include "atomtests.h"
/* Test OS objects */
static ATOM_TCB tcb1, tcb2;
static uint8_t test1_thread_stack[TEST_THREAD_STACK_SIZE];
static uint8_t test2_thread_stack[TEST_THREAD_STACK_SIZE];
/* Test global data (one per thread) */
static volatile int running_flag[2];
static volatile int sleep_request[2];
/* Forward declarations */
static void test_thread_func (uint32_t data);
/**
* \b test_start
*
* Start kernel test.
*
* This tests the scheduling of threads at different priorities, and
* preemption of lower priority threads by higher priority threads.
*
* Much of this functionality is already tested implicitly by the
* semaphore, mutex tests etc but we repeat it here within the kernel
* tests for completeness.
*
* Two threads are created at different priorities, with each thread
* setting a running flag whenever it runs. We check that when the
* higher priority thread is ready to run, only the higher priority
* thread's running flag is set (even though the lower priority
* thread should also be setting it at this time). This checks that
* the scheduler is correctly prioritising thread execution.
*
* The test also exercises preemption, by disabling setting of the
* running flag in the higher priority thread for a period. During
* this time the higher priority thread repeatedly sleeps for one
* system tick then wakes up to check the sleep-request flag again.
* Every time the higher priority thread wakes up, it has preempted
* the lower priority thread (which is always running). By ensuring
* that the higher priority thread is able to start running again
* after one of these periods (through checking the running flag)
* we prove that the preemption has worked.
*
* @retval Number of failures
*/
uint32_t test_start (void)
{
int failures;
int i;
/* Default to zero failures */
failures = 0;
/* Initialise global data */
running_flag[0] = running_flag[1] = FALSE;
sleep_request[0] = sleep_request[1] = FALSE;
/* Create low priority thread */
if (atomThreadCreate (&tcb1, 253, test_thread_func, 0,
&test1_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK)
{
ATOMLOG (_STR("Bad thread create\n"));
failures++;
}
/* Create high priority thread */
else if (atomThreadCreate (&tcb2, 252, test_thread_func, 1,
&test2_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK)
{
ATOMLOG (_STR("Bad thread create\n"));
failures++;
}
/* Repeat test a few times */
for (i = 0; i < 8; i++)
{
/* Make the higher priority thread sleep */
sleep_request[1] = TRUE;
/* Sleep a little to make sure the thread sees the sleep request */
atomTimerDelay (SYSTEM_TICKS_PER_SEC/4);
/* Reset the running flag for both threads */
running_flag[0] = running_flag[1] = FALSE;
/* Sleep a little to give any running threads time to set their flag */
atomTimerDelay (SYSTEM_TICKS_PER_SEC/4);
/* Check only the low priority thread has run since we reset the flags */
if ((running_flag[0] != TRUE) || (running_flag[1] != FALSE))
{
ATOMLOG (_STR("Lo%d %d/%d\n"), i, running_flag[0], running_flag[1]);
failures++;
break;
}
else
{
/*
* We have confirmed that only the ready thread has been running.
* Now check that if we wake up the high priority thread, the
* low priority one stops running and only the high priority one
* does.
*/
/* Tell the higher priority thread to stop sleeping */
sleep_request[1] = FALSE;
/* Reset the running flag for both threads */
running_flag[0] = running_flag[1] = FALSE;
/* Sleep a little to give any running threads time to set their flag */
atomTimerDelay (SYSTEM_TICKS_PER_SEC/4);
/* Check only the high priority thread has run since we reset the flags */
if ((running_flag[1] != TRUE) || (running_flag[0] != FALSE))
{
ATOMLOG (_STR("Hi%d/%d\n"), running_flag[0], running_flag[1]);
failures++;
break;
}
else
{
/*
* We have confirmed that the high priority thread has preempted the
* low priority thread, and remain running while never scheduling
* the lower one back in.
*/
}
}
}
/* Log final status */
if (failures == 0)
{
ATOMLOG (_STR("Pass\n"));
}
else
{
ATOMLOG (_STR("Fail(%d)\n"), failures);
}
/* Quit */
return failures;
}
/**
* \b test_thread_func
*
* Entry point for test thread.
*
* @param[in] data Thread ID (0 = low prio, 1 = high prio)
*
* @return None
*/
static void test_thread_func (uint32_t data)
{
int thread_id;
/* Pull out thread ID */
thread_id = (int)data;
/* Run forever */
while (1)
{
/* If this thread is requested to sleep, sleep until told to stop */
if (sleep_request[thread_id])
{
atomTimerDelay (1);
}
else
{
/* Otherwise set running flag for this thread */
running_flag[thread_id] = TRUE;
}
}
}

241
tests/kern4.c Normal file
View File

@@ -0,0 +1,241 @@
/*
* Copyright (c) 2010, Kelvin Lawson. All rights reserved.
*
* 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. No personal names or organizations' names associated with the
* Atomthreads project may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT 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 PROJECT 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.
*/
#include "atom.h"
#include "atomtests.h"
/* Test OS objects */
static ATOM_TCB tcb1, tcb2, tcb3, tcb4;
static uint8_t test1_thread_stack[TEST_THREAD_STACK_SIZE];
static uint8_t test2_thread_stack[TEST_THREAD_STACK_SIZE];
static uint8_t test3_thread_stack[TEST_THREAD_STACK_SIZE];
static uint8_t test4_thread_stack[TEST_THREAD_STACK_SIZE];
/* Test global data (one per thread) */
static uint32_t volatile last_time;
static int volatile last_thread_id;
static volatile int switch_cnt;
static uint32_t volatile failure_cnt[4];
static int volatile test_started;
/* Forward declarations */
static void test_thread_func (uint32_t data);
/**
* \b test_start
*
* Start kernel test.
*
* This tests the round-robin timeslicing of same priority threads.
*
* Four threads are created (over and above the main test thread).
* The main test thread sleeps for the duration of the entire test.
* While the test is ongoing, all four threads are running
* continuously at the same priority. They each check that whenever
* the system tick (atomTimeGet()) changes, it has moved on 1 tick
* since the last time the tick was checked, and that the previous
* thread is the one created before itself. In the case of the first
* thread created, the previous thread should be the last thread
* created. This proves that on every tick the four threads get a
* schedule timeslice equally, and in the same order throughout the
* duration of the test.
*
* @retval Number of failures
*/
uint32_t test_start (void)
{
int failures;
/* Default to zero failures */
failures = 0;
/* Initialise global data */
last_time = 0;
last_thread_id = -1;
failure_cnt[0] = failure_cnt[1] = failure_cnt[2] = failure_cnt[3] = 0;
/* Set test as not started until all threads are ready to go */
test_started = FALSE;
/*
* Create all four threads at the same priority as each other.
* They are given a lower priority than this thread, however,
* to ensure that once this thread wakes up to stop the test it
* can do so without confusing the scheduling tests by having
* a spell in which this thread was run.
*/
if (atomThreadCreate (&tcb1, TEST_THREAD_PRIO + 1, test_thread_func, 0,
&test1_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK)
{
ATOMLOG (_STR("Bad thread create\n"));
failures++;
}
else if (atomThreadCreate (&tcb2, TEST_THREAD_PRIO + 1, test_thread_func, 1,
&test2_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK)
{
ATOMLOG (_STR("Bad thread create\n"));
failures++;
}
else if (atomThreadCreate (&tcb3, TEST_THREAD_PRIO + 1, test_thread_func, 2,
&test3_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK)
{
ATOMLOG (_STR("Bad thread create\n"));
failures++;
}
else if (atomThreadCreate (&tcb4, TEST_THREAD_PRIO + 1, test_thread_func, 3,
&test4_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK)
{
ATOMLOG (_STR("Bad thread create\n"));
failures++;
}
/* Start the test */
test_started = TRUE;
/* Sleep for 5 seconds during test */
atomTimerDelay (5 * SYSTEM_TICKS_PER_SEC);
/* Stop the test */
test_started = FALSE;
/* Sleep for tests to complete */
atomTimerDelay (SYSTEM_TICKS_PER_SEC/4);
/* Count any failures from test threads */
failures += failure_cnt[0] + failure_cnt[1] + failure_cnt[2] + failure_cnt[3];
/* Log final status */
if (failures == 0)
{
ATOMLOG (_STR("Pass\n"));
}
else
{
ATOMLOG (_STR("Fail(%d)\n"), failures);
}
/* Quit */
return failures;
}
/**
* \b test_thread_func
*
* Entry point for test thread.
*
* @param[in] data Thread ID (0 to 3)
*
* @return None
*/
static void test_thread_func (uint32_t data)
{
int thread_id, expected_thread;
int time_error, thread_error;
uint32_t new_time;
CRITICAL_STORE;
/* Pull out thread ID */
thread_id = (int)data;
/* Run forever */
while (1)
{
/* Check if test is currently in operation */
if (test_started)
{
/*
* If the system time has ticked over, check that the currently
* running thread is not the one that was running last tick.
*/
/* Default to no error this time */
time_error = thread_error = FALSE;
/* Do the whole operation with interrupts locked out */
CRITICAL_START();
/* Check if time has ticked over */
new_time = atomTimeGet();
/* Only perform the check if this is not the first thread to run */
if ((last_time != 0) && (last_thread_id != -1))
{
/* Check if the time has ticked over */
if (new_time != last_time)
{
/* Check time only ticked over by 1 */
if ((new_time - last_time) != 1)
{
time_error = 1;
}
/*
* We are expecting the previous thread to be our thread_id
* minus one.
*/
expected_thread = thread_id - 1;
if (expected_thread == -1)
{
expected_thread = 3;
}
/* Check that the last thread was the expected one */
if (last_thread_id != expected_thread)
{
thread_error = TRUE;
}
/* Increment the switch count */
switch_cnt++;
}
}
/* Store the currently-running thread as the last-running */
last_thread_id = thread_id;
last_time = new_time;
/* Finished with the interrupt lockout */
CRITICAL_END();
/* If we got an error above, increment the total failure count */
if (test_started && (thread_error || time_error))
{
failure_cnt[thread_id]++;
ATOMLOG(_STR("T%d\n"), thread_id);
}
}
}
}

331
tests/mutex1.c Normal file
View File

@@ -0,0 +1,331 @@
/*
* Copyright (c) 2010, Kelvin Lawson. All rights reserved.
*
* 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. No personal names or organizations' names associated with the
* Atomthreads project may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT 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 PROJECT 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.
*/
#include "atom.h"
#include "atommutex.h"
#include "atomtests.h"
#include "atomuser.h"
/* Test OS objects */
static ATOM_MUTEX mutex1;
static ATOM_TCB tcb1, tcb2;
static uint8_t test1_thread_stack[TEST_THREAD_STACK_SIZE];
static uint8_t test2_thread_stack[TEST_THREAD_STACK_SIZE];
/* Test result tracking */
static volatile int g_result;
/* Forward declarations */
static void test1_thread_func (uint32_t data);
static void test2_thread_func (uint32_t data);
/**
* \b test_start
*
* Start mutex test.
*
* This test exercises the mutex creation and deletion APIs, including
* waking threads blocking on a mutex if the mutex is deleted.
* Deletion wakeups are tested twice: once for a thread which is blocking
* with a timeout and once for a thread which is blocking with no timeout.
*
* @retval Number of failures
*/
uint32_t test_start (void)
{
int failures;
uint32_t i;
/* Default to zero failures */
failures = 0;
/* Test creation and deletion of mutexes: good values */
for (i = 0; i < 1000; i++)
{
if (atomMutexCreate (&mutex1) == ATOM_OK)
{
if (atomMutexDelete (&mutex1) == ATOM_OK)
{
/* Success */
}
else
{
/* Fail */
ATOMLOG (_STR("Error deleting mutex\n"));
failures++;
break;
}
}
else
{
/* Fail */
ATOMLOG (_STR("Error creating mutex\n"));
failures++;
break;
}
}
/* Test creation and deletion of mutexes: creation checks */
if (atomMutexCreate (NULL) != ATOM_OK)
{
/* Success */
}
else
{
/* Fail */
ATOMLOG (_STR("Bad mutex creation checks\n"));
failures++;
}
/* Test creation and deletion of mutexes: deletion checks */
if (atomMutexDelete (NULL) != ATOM_OK)
{
/* Success */
}
else
{
/* Fail */
ATOMLOG (_STR("Bad mutex deletion checks\n"));
failures++;
}
/* Test wakeup of threads on mutex deletion (thread blocking with no timeout) */
g_result = 0;
if (atomMutexCreate (&mutex1) != ATOM_OK)
{
ATOMLOG (_STR("Error creating test mutex\n"));
failures++;
}
/* Take the mutex so that the test thread will block */
else if (atomMutexGet (&mutex1, 0) != ATOM_OK)
{
ATOMLOG (_STR("Error taking mutex\n"));
failures++;
}
else if (atomThreadCreate(&tcb1, TEST_THREAD_PRIO, test1_thread_func, 0,
&test1_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK)
{
/* Fail */
ATOMLOG (_STR("Error creating test thread 1\n"));
failures++;
}
else
{
/*
* We have created a mutex and taken ownership ourselves. We
* want to see that the other thread is woken up if its mutex
* is deleted. This is indicated through g_result being set.
*/
/* Wait for the other thread to start blocking on mutex1 */
if (atomTimerDelay(SYSTEM_TICKS_PER_SEC) != ATOM_OK)
{
ATOMLOG (_STR("Failed timer delay\n"));
failures++;
}
else
{
/* The other thread will be blocking on mutex1 now, delete mutex1 */
if (atomMutexDelete(&mutex1) != ATOM_OK)
{
ATOMLOG (_STR("Failed mutex1 delete\n"));
failures++;
}
else
{
/* Mutex1 deleted. The thread should now wake up and set g_result. */
atomTimerDelay (SYSTEM_TICKS_PER_SEC);
if (g_result == 0)
{
ATOMLOG (_STR("Notify fail\n"));
failures++;
}
else
{
/* Success */
}
}
}
}
/* Test wakeup of threads on semaphore deletion (thread blocking with timeout) */
g_result = 0;
if (atomMutexCreate (&mutex1) != ATOM_OK)
{
ATOMLOG (_STR("Error creating test mutex\n"));
failures++;
}
/* Take the mutex so that the test thread will block */
else if (atomMutexGet (&mutex1, 0) != ATOM_OK)
{
ATOMLOG (_STR("Error taking mutex\n"));
failures++;
}
else if (atomThreadCreate(&tcb2, TEST_THREAD_PRIO, test2_thread_func, 0,
&test2_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK)
{
/* Fail */
ATOMLOG (_STR("Error creating test thread 2\n"));
failures++;
}
else
{
/*
* We have created a mutex and taken ownership ourselves. We
* want to see that the other thread is woken up if its mutex
* is deleted. This is indicated through g_result being set.
*/
/* Wait for the other thread to start blocking on mutex1 */
if (atomTimerDelay(SYSTEM_TICKS_PER_SEC) != ATOM_OK)
{
ATOMLOG (_STR("Failed timer delay\n"));
failures++;
}
else
{
/* The other thread will be blocking on mutex1 now, delete mutex1 */
if (atomMutexDelete(&mutex1) != ATOM_OK)
{
ATOMLOG (_STR("Failed mutex1 delete\n"));
failures++;
}
else
{
/* Mutex1 deleted. The thread should now wake up and set g_result. */
atomTimerDelay (SYSTEM_TICKS_PER_SEC);
if (g_result == 0)
{
ATOMLOG (_STR("Notify fail\n"));
failures++;
}
else
{
/* Success */
}
}
}
}
/* Log final status */
if (failures == 0)
{
ATOMLOG (_STR("Pass\n"));
}
else
{
ATOMLOG (_STR("Fail(%d)\n"), failures);
}
/* Quit */
return failures;
}
/**
* \b test1_thread_func
*
* Entry point for test thread 1.
*
* @param[in] data Unused (optional thread entry parameter)
*
* @return None
*/
static void test1_thread_func (uint32_t data)
{
uint8_t status;
/*
* Wait on mutex1 with no timeout. We are expecting to be woken up
* by the main thread while blocking.
*/
status = atomMutexGet(&mutex1, 0);
if (status != ATOM_ERR_DELETED)
{
ATOMLOG (_STR("Test1 thread woke without deletion (%d)\n"), status);
}
else
{
/* We were woken due to deletion as expected, set g_result to notify success */
g_result = 1;
}
/* Wait forever */
while (1)
{
atomTimerDelay (SYSTEM_TICKS_PER_SEC);
}
}
/**
* \b test2_thread_func
*
* Entry point for test thread 2.
*
* @param[in] data Unused (optional thread entry parameter)
*
* @return None
*/
static void test2_thread_func (uint32_t data)
{
uint8_t status;
/*
* Wait on mutex1 with timeout. We are expecting to be woken up
* by the main thread while blocking.
*/
status = atomMutexGet(&mutex1, (5 * SYSTEM_TICKS_PER_SEC));
if (status != ATOM_ERR_DELETED)
{
ATOMLOG (_STR("Test2 thread woke without deletion (%d)\n"), status);
}
else
{
/* We were woken due to deletion as expected, set g_result to notify success */
g_result = 1;
}
/* Wait forever */
while (1)
{
atomTimerDelay (SYSTEM_TICKS_PER_SEC);
}
}

299
tests/mutex2.c Normal file
View File

@@ -0,0 +1,299 @@
/*
* Copyright (c) 2010, Kelvin Lawson. All rights reserved.
*
* 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. No personal names or organizations' names associated with the
* Atomthreads project may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT 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 PROJECT 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.
*/
#include "atom.h"
#include "atommutex.h"
#include "atomtests.h"
#include "atomuser.h"
/* Test OS objects */
static ATOM_MUTEX mutex1, mutex2;
static ATOM_TCB tcb1;
static uint8_t test1_thread_stack[TEST_THREAD_STACK_SIZE];
/* Test result tracking */
static volatile int g_result, g_owned;
/* Forward declarations */
static void testCallback (POINTER cb_data);
static void test_thread_func (uint32_t data);
/**
* \b test_start
*
* Start mutex test.
*
* This test exercises the atomMutexGet() and atomMutexPut() APIs including
* forcing the various error indications which can be returned from the
* APIs to ensure that handling for these corner cases have been correctly
* implemented.
*
* @retval Number of failures
*/
uint32_t test_start (void)
{
int failures;
uint8_t status;
ATOM_TIMER timer_cb;
int count;
/* Default to zero failures */
failures = 0;
/* Test parameter checks */
if (atomMutexGet (NULL, 0) != ATOM_ERR_PARAM)
{
ATOMLOG (_STR("Get param failed\n"));
failures++;
}
if (atomMutexPut (NULL) != ATOM_ERR_PARAM)
{
ATOMLOG (_STR("Put param failed\n"));
failures++;
}
/* Test atomMutexGet() can not be called from interrupt context */
g_result = 0;
if (atomMutexCreate (&mutex1) != ATOM_OK)
{
ATOMLOG (_STR("Error creating test mutex1\n"));
failures++;
}
else
{
/* Fill out the timer callback request structure */
timer_cb.cb_func = testCallback;
timer_cb.cb_data = NULL;
timer_cb.cb_ticks = SYSTEM_TICKS_PER_SEC;
/* Request the timer callback to run in one second */
if (atomTimerRegister (&timer_cb) != ATOM_OK)
{
ATOMLOG (_STR("Error registering timer\n"));
failures++;
}
/* Wait two seconds for g_result to be set indicating success */
else
{
atomTimerDelay (2 * SYSTEM_TICKS_PER_SEC);
if (g_result != 1)
{
ATOMLOG (_STR("Context check failed\n"));
failures++;
}
}
/* Delete the test mutex */
if (atomMutexDelete (&mutex1) != ATOM_OK)
{
ATOMLOG (_STR("Mutex1 delete failed\n"));
failures++;
}
}
/* Create mutex1 which will be owned by us */
if (atomMutexCreate (&mutex1) != ATOM_OK)
{
ATOMLOG (_STR("Error creating test mutex 1\n"));
failures++;
}
/* Create mutex2 which will be owned by another thread */
else if (atomMutexCreate (&mutex2) != ATOM_OK)
{
ATOMLOG (_STR("Error creating test mutex 2\n"));
failures++;
}
/* Create a test thread, the sole purpose of which is to own mutex2 */
g_owned = 0;
if (atomThreadCreate(&tcb1, TEST_THREAD_PRIO, test_thread_func, 0,
&test1_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK)
{
/* Fail */
ATOMLOG (_STR("Error creating test thread 1\n"));
failures++;
}
/* Sleep until the test thread owns mutex2 */
atomTimerDelay (SYSTEM_TICKS_PER_SEC);
if (g_owned == 0)
{
ATOMLOG (_STR("Thread own fail\n"));
failures++;
}
/* Test wait on mutex with timeout - should timeout while owned by another thread */
if ((status = atomMutexGet (&mutex2, SYSTEM_TICKS_PER_SEC)) != ATOM_TIMEOUT)
{
ATOMLOG (_STR("Get %d\n"), status);
failures++;
}
else
{
/* Success */
}
/* Test wait on mutex with no blocking - should return that owned by another thread */
if ((status = atomMutexGet (&mutex2, -1)) != ATOM_WOULDBLOCK)
{
ATOMLOG (_STR("Wouldblock err %d\n"), status);
failures++;
}
/* Test wait on mutex with no blocking when mutex is available */
if (atomMutexGet (&mutex1, -1) != ATOM_OK)
{
ATOMLOG (_STR("Error taking mutex1\n"));
failures++;
}
else
{
/* Relinquish ownership of mutex1 */
if (atomMutexPut (&mutex1) != ATOM_OK)
{
ATOMLOG (_STR("Error posting mutex\n"));
failures++;
}
}
/* Test for lock count overflows with too many gets */
count = 255;
while (count--)
{
if (atomMutexGet (&mutex1, 0) != ATOM_OK)
{
ATOMLOG (_STR("Error getting mutex1\n"));
failures++;
break;
}
}
/* The lock count should overflow this time */
if (atomMutexGet (&mutex1, 0) != ATOM_ERR_OVF)
{
ATOMLOG (_STR("Error tracking overflow\n"));
failures++;
}
else
{
/* Success */
}
/* Delete the test mutexes */
if (atomMutexDelete (&mutex1) != ATOM_OK)
{
ATOMLOG (_STR("Error deleting mutex1\n"));
failures++;
}
if (atomMutexDelete (&mutex2) != ATOM_OK)
{
ATOMLOG (_STR("Error deleting mutex2\n"));
failures++;
}
/* Log final status */
if (failures == 0)
{
ATOMLOG (_STR("Pass\n"));
}
else
{
ATOMLOG (_STR("Fail(%d)\n"), failures);
}
/* Quit */
return failures;
}
/**
* \b testCallback
*
* Attempt an atomMutexGet() on mutex1 from interrupt context.
* Should receive an ATOM_ERR_CONTEXT error. Sets g_result if passes.
*
* @param[in] cb_data Not used
*/
static void testCallback (POINTER cb_data)
{
/* Check the return value from atomMutexGet() */
if (atomMutexGet(&mutex1, 0) == ATOM_ERR_CONTEXT)
{
/* Received the error we expected, set g_result to notify success */
g_result = 1;
}
else
{
/* Did not get expected error, don't set g_result signifying fail */
}
}
/**
* \b test_thread_func
*
* Entry point for test thread.
*
* @param[in] data Unused (optional thread entry parameter)
*
* @return None
*/
static void test_thread_func (uint32_t data)
{
uint8_t status;
/*
* Take mutex2 so that main thread can test mutex APIs on a mutex
* which it does not own.
*/
status = atomMutexGet(&mutex2, 0);
if (status != ATOM_OK)
{
ATOMLOG (_STR("Mutex get (%d)\n"), status);
}
else
{
/* We took ownership of mutex2, set g_owned to notify success */
g_owned = 1;
}
/* Wait forever */
while (1)
{
atomTimerDelay (SYSTEM_TICKS_PER_SEC);
}
}

263
tests/mutex3.c Normal file
View File

@@ -0,0 +1,263 @@
/*
* Copyright (c) 2010, Kelvin Lawson. All rights reserved.
*
* 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. No personal names or organizations' names associated with the
* Atomthreads project may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT 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 PROJECT 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.
*/
#include "atom.h"
#include "atomtests.h"
#include "atommutex.h"
/* Test OS objects */
static ATOM_MUTEX mutex1;
static ATOM_TCB tcb1, tcb2, tcb3, tcb4;
static uint8_t test1_thread_stack[TEST_THREAD_STACK_SIZE];
static uint8_t test2_thread_stack[TEST_THREAD_STACK_SIZE];
static uint8_t test3_thread_stack[TEST_THREAD_STACK_SIZE];
static uint8_t test4_thread_stack[TEST_THREAD_STACK_SIZE];
/* Data updated by threads */
static volatile uint8_t wake_cnt;
static volatile uint8_t wake_order[4];
/* Forward declarations */
static void test_thread_func (uint32_t data);
/**
* \b test_start
*
* Start mutex test.
*
* With multiple threads blocking on a single mutex, this test confirms that
* they are woken in order when the mutex is released. The correct order for
* waking is that the higher priority threads are woken first, followed by the
* lower priority threads. Where multiple threads of the same priority are
* waiting, the threads are woken in FIFO order (the order in which they started
* waiting on the mutex).
*
* To test this we create four threads which all wait on a single mutex.
* One pair of threads are running at high priority, with the other pair at a
* lower priority:
*
* Thread 1: low prio thread A
* Thread 2: low prio thread B
* Thread 3: high prio thread A
* Thread 4: high prio thread B
*
* The threads are forced to start blocking on the same mutex in the
* above order.
*
* We expect to see them woken up in the following order:
* 3, 4, 1, 2
*
* This proves the multiple blocking thread ordering in terms of both
* the priority-queueing and same-priority-FIFO-queueing.
*
* @retval Number of failures
*/
uint32_t test_start (void)
{
int failures;
/* Default to zero failures */
failures = 0;
/* Create mutex */
if (atomMutexCreate (&mutex1) != ATOM_OK)
{
ATOMLOG (_STR("Error creating test mutex 1\n"));
failures++;
}
/* Take ownership of the mutex so all threads will block to begin with */
else if (atomMutexGet (&mutex1, 0) != ATOM_OK)
{
ATOMLOG (_STR("Get error\n"));
failures++;
}
/* Start the threads */
else
{
/* Create Thread 1 (lower priority thread A) */
if (atomThreadCreate(&tcb1, TEST_THREAD_PRIO+1, test_thread_func, 1,
&test1_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK)
{
/* Fail */
ATOMLOG (_STR("Error creating test thread\n"));
failures++;
}
/* Delay to ensure the thread will start blocking on the mutex */
atomTimerDelay (SYSTEM_TICKS_PER_SEC/4);
/* Create Thread 2 (lower priority thread B) */
if (atomThreadCreate(&tcb2, TEST_THREAD_PRIO+1, test_thread_func, 2,
&test2_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK)
{
/* Fail */
ATOMLOG (_STR("Error creating test thread\n"));
failures++;
}
/* Delay to ensure the thread will start blocking on the mutex */
atomTimerDelay (SYSTEM_TICKS_PER_SEC/4);
/* Create Thread 3 (higher priority thread A) */
if (atomThreadCreate(&tcb3, TEST_THREAD_PRIO, test_thread_func, 3,
&test3_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK)
{
/* Fail */
ATOMLOG (_STR("Error creating test thread\n"));
failures++;
}
/* Delay to ensure the thread will start blocking on the mutex */
atomTimerDelay (SYSTEM_TICKS_PER_SEC/4);
/* Create Thread 4 (higher priority thread B) */
if (atomThreadCreate(&tcb4, TEST_THREAD_PRIO, test_thread_func, 4,
&test4_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK)
{
/* Fail */
ATOMLOG (_STR("Error creating test thread\n"));
failures++;
}
/* Delay to ensure the thread will start blocking on the mutex */
atomTimerDelay (SYSTEM_TICKS_PER_SEC/4);
/* All four threads will now be blocking on mutex1 */
/*
* Initialise wake count, used by threads to determine
* what order they were woken in.
*/
wake_cnt = 0;
/*
* Release the mutex. This will wake up one of the threads blocking
* on it. That thread will take ownership of the mutex, and note the
* order at which it was woken, before releasing the mutex. This in
* turn will wake up the next thread blocking on the mutex until all
* four test threads have taken and released the mutex, noting their
* wake order.
*/
if (atomMutexPut (&mutex1) != ATOM_OK)
{
ATOMLOG (_STR("Post fail\n"));
failures++;
}
/* Sleep to give all four threads time to complete */
atomTimerDelay (SYSTEM_TICKS_PER_SEC / 4);
/* All four threads now woken up, check they woke in correct order */
if ((wake_order[0] != 3) && (wake_order[1] != 4)
&& (wake_order[2] != 1) && (wake_order[3] != 2))
{
ATOMLOG (_STR("Bad order %d,%d,%d,%d\n"),
wake_order[0], wake_order[1], wake_order[2], wake_order[3]);
failures++;
}
/* Delete mutex, test finished */
if (atomMutexDelete (&mutex1) != ATOM_OK)
{
ATOMLOG (_STR("Delete failed\n"));
failures++;
}
}
/* Log final status */
if (failures == 0)
{
ATOMLOG (_STR("Pass\n"));
}
else
{
ATOMLOG (_STR("Fail(%d)\n"), failures);
}
/* Quit */
return failures;
}
/**
* \b test_thread_func
*
* Entry point for test thread. The same thread entry point is used for all
* four test threads, with the thread number/ID (1-4) passed as the entry
* point parameter.
*
* @param[in] data Thread number (1,2,3,4)
*
* @return None
*/
static void test_thread_func (uint32_t data)
{
uint8_t thread_id;
/* Thread ID is passed through the function parameter */
thread_id = (uint8_t)data;
/*
* Wait for mutex1 to be posted. At creation of all test threads the mutex
* is owned by the parent thread, so all four threads will block here.
*/
if (atomMutexGet (&mutex1, 0) != ATOM_OK)
{
ATOMLOG (_STR("Get fail\n"));
}
else
{
/*
* Store our thread ID in the array using the current
* wake_cnt order. The threads are holding ownership
* of a mutex here, which provides protection for this
* global data.
*/
wake_order[wake_cnt++] = thread_id;
/* Release the mutex so that the next thread wakes up */
if (atomMutexPut (&mutex1) != ATOM_OK)
{
ATOMLOG (_STR("Put fail\n"));
}
}
/* Loop forever */
while (1)
{
atomTimerDelay (SYSTEM_TICKS_PER_SEC);
}
}

266
tests/mutex4.c Normal file
View File

@@ -0,0 +1,266 @@
/*
* Copyright (c) 2010, Kelvin Lawson. All rights reserved.
*
* 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. No personal names or organizations' names associated with the
* Atomthreads project may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT 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 PROJECT 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.
*/
#include "atom.h"
#include "atomtests.h"
#include "atommutex.h"
#include "atomsem.h"
/* Number of test loops for stress-test */
#define NUM_TEST_LOOPS 10000
/* Test OS objects */
static ATOM_MUTEX mutex1;
static ATOM_SEM sem1;
static ATOM_TCB tcb1, tcb2, tcb3, tcb4;
static uint8_t test1_thread_stack[TEST_THREAD_STACK_SIZE];
static uint8_t test2_thread_stack[TEST_THREAD_STACK_SIZE];
static uint8_t test3_thread_stack[TEST_THREAD_STACK_SIZE];
static uint8_t test4_thread_stack[TEST_THREAD_STACK_SIZE];
/*
* Global failure count (can be updated by test threads but is
* protected by an interrupt lockout).
*/
static volatile int g_failures;
/* Forward declarations */
static void test_thread_func (uint32_t data);
/**
* \b test_start
*
* Start mutex test.
*
* Stress-tests mutex Get and Put operations. Four threads are created which are
* continually Getting and Putting the same mutex, with no time delays between
* each Get/Put.
*
* @retval Number of g_failures
*/
uint32_t test_start (void)
{
CRITICAL_STORE;
int finish_cnt;
/* Default to zero g_failures */
g_failures = 0;
/* Create mutex to stress */
if (atomMutexCreate (&mutex1) != ATOM_OK)
{
ATOMLOG (_STR("Error creating mutex\n"));
g_failures++;
}
/* Create sem to receive thread-finished notification */
else if (atomSemCreate (&sem1, 0) != ATOM_OK)
{
ATOMLOG (_STR("Error creating sem\n"));
g_failures++;
}
else
{
/* Take ownership of the mutex to ensure all threads wait for now */
if (atomMutexGet (&mutex1, 0) != ATOM_OK)
{
ATOMLOG (_STR("Error taking mutex\n"));
g_failures++;
}
/* Create Thread 1 */
if (atomThreadCreate(&tcb1, TEST_THREAD_PRIO, test_thread_func, 1,
&test1_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK)
{
/* Fail */
ATOMLOG (_STR("Error creating test thread\n"));
CRITICAL_START ();
g_failures++;
CRITICAL_END ();
}
/* Create Thread 2 */
if (atomThreadCreate(&tcb2, TEST_THREAD_PRIO, test_thread_func, 2,
&test2_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK)
{
/* Fail */
ATOMLOG (_STR("Error creating test thread\n"));
CRITICAL_START ();
g_failures++;
CRITICAL_END ();
}
/* Create Thread 3 */
if (atomThreadCreate(&tcb3, TEST_THREAD_PRIO, test_thread_func, 3,
&test3_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK)
{
/* Fail */
ATOMLOG (_STR("Error creating test thread\n"));
CRITICAL_START ();
g_failures++;
CRITICAL_END ();
}
/* Create Thread 4 */
if (atomThreadCreate(&tcb4, TEST_THREAD_PRIO, test_thread_func, 4,
&test4_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK)
{
/* Fail */
ATOMLOG (_STR("Error creating test thread\n"));
CRITICAL_START ();
g_failures++;
CRITICAL_END ();
}
/* Release ownership of the mutex to kick the threads off */
if (atomMutexPut (&mutex1) != ATOM_OK)
{
ATOMLOG (_STR("Error putting mutex\n"));
g_failures++;
}
/*
* All four threads will now be performing Gets/Puts on mutex1.
* When they have finished they will post sem1, so we wait
* until sem1 is posted four times.
*/
finish_cnt = 0;
while (1)
{
/*
* Attempt to Get sem1. When we have managed to get
* the semaphore four times, it must have been posted
* by all four threads.
*/
if (atomSemGet (&sem1, 0) == ATOM_OK)
{
/* Increment our count of finished threads */
finish_cnt++;
/* Check if all four threads have now posted sem1 */
if (finish_cnt == 4)
{
break;
}
}
}
/* Delete OS objects, test finished */
if (atomMutexDelete (&mutex1) != ATOM_OK)
{
ATOMLOG (_STR("Delete failed\n"));
CRITICAL_START ();
g_failures++;
CRITICAL_END ();
}
if (atomSemDelete (&sem1) != ATOM_OK)
{
ATOMLOG (_STR("Delete failed\n"));
CRITICAL_START ();
g_failures++;
CRITICAL_END ();
}
}
/* Log final status */
if (g_failures == 0)
{
ATOMLOG (_STR("Pass\n"));
}
else
{
ATOMLOG (_STR("Fail(%d)\n"), g_failures);
}
/* Quit */
return g_failures;
}
/**
* \b test_thread_func
*
* Entry point for test thread. The same thread entry point is used for all
* four test threads.
*
* @param[in] data Unused (optional thread entry parameter)
*
* @return None
*/
static void test_thread_func (uint32_t data)
{
uint32_t loop_cnt;
uint8_t status;
CRITICAL_STORE;
/* Run a Get/Put pair many times */
loop_cnt = NUM_TEST_LOOPS;
while (loop_cnt--)
{
if ((status = atomMutexGet (&mutex1, 0)) != ATOM_OK)
{
/* Error getting mutex, notify the status code */
ATOMLOG (_STR("G%d\n"), status);
CRITICAL_START ();
g_failures++;
CRITICAL_END ();
break;
}
else if ((status = atomMutexPut (&mutex1)) != ATOM_OK)
{
/* Error putting mutex, notify the status code */
ATOMLOG (_STR("P%d\n"), status);
CRITICAL_START ();
g_failures++;
CRITICAL_END ();
break;
}
}
/* Post sem1 to notify the main thread we're finished */
if (atomSemPut (&sem1) != ATOM_OK)
{
ATOMLOG (_STR("Sem1 putfail\n"));
CRITICAL_START ();
g_failures++;
CRITICAL_END ();
}
/* Loop forever */
while (1)
{
atomTimerDelay (SYSTEM_TICKS_PER_SEC);
}
}

271
tests/mutex5.c Normal file
View File

@@ -0,0 +1,271 @@
/*
* Copyright (c) 2010, Kelvin Lawson. All rights reserved.
*
* 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. No personal names or organizations' names associated with the
* Atomthreads project may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT 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 PROJECT 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.
*/
#include "atom.h"
#include "atomtests.h"
#include "atommutex.h"
/* Test OS objects */
static ATOM_MUTEX mutex1;
static ATOM_TCB tcb1;
static uint8_t test1_thread_stack[TEST_THREAD_STACK_SIZE];
/* Global shared data protected by mutex */
static volatile int shared_data;
/* Forward declarations */
static void test_thread_func (uint32_t data);
/**
* \b test_start
*
* Start mutex test.
*
* This tests basic usage of a mutex. Whichever thread holds the
* mutex can modify the global variable "shared_data".
*
* The main thread first takes the mutex, then creates a second
* thread. The second thread should block on the mutex until the
* main thread releases it. The test checks that the global
* "shared_data" is not modified by the second thread until the
* main thread releases the mutex.
*
* @retval Number of failures
*/
uint32_t test_start (void)
{
int failures;
int i;
/* Default to zero failures */
failures = 0;
/* Create mutex */
if (atomMutexCreate (&mutex1) != ATOM_OK)
{
ATOMLOG (_STR("Error creating mutex\n"));
failures++;
}
else
{
/* Initialise the shared_data to zero */
shared_data = 0;
/* Take the mutex to ensure only this thread can modify shared_data */
if (atomMutexGet (&mutex1, 0) != ATOM_OK)
{
ATOMLOG (_STR("Error taking mutex\n"));
failures++;
}
/* Create second thread */
else if (atomThreadCreate(&tcb1, TEST_THREAD_PRIO, test_thread_func, 1,
&test1_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK)
{
/* Fail */
ATOMLOG (_STR("Error creating test thread\n"));
failures++;
}
/*
* The second thread has now been created and should block on
* the mutex until we release it. We wait a while and check that
* shared_data has not been modified.
*/
for (i = 0; i < 4; i++)
{
/*
* Sleep for a while to give the second thread a chance to
* modify shared_data, thought it shouldn't until we
* release the mutex.
*/
atomTimerDelay (SYSTEM_TICKS_PER_SEC/4);
/* Check shared data. The second thread always sets it to one. */
if (shared_data != 0)
{
ATOMLOG (_STR("Shared data modified\n"));
failures++;
break;
}
}
/* Check successful so far */
if (failures == 0)
{
/*
* Release the mutex, which will allow the second thread to
* wake and start modifying shared_data.
*/
if (atomMutexPut (&mutex1) != ATOM_OK)
{
ATOMLOG (_STR("Failed release\n"));
failures++;
}
/*
* Wait a little while then check that shared_data has
* been modified.
*/
atomTimerDelay (SYSTEM_TICKS_PER_SEC/4);
if (shared_data != 1)
{
ATOMLOG (_STR("Expected modify\n"));
failures++;
}
/*
* Release and take the mutex again a few times to ensure
* that the mutex continues to protect shared_data.
*/
for (i = 0; i < 4; i++)
{
/*
* Take the mutex again, to prevent second thread accessing
* shared_data.
*/
if (atomMutexGet (&mutex1, SYSTEM_TICKS_PER_SEC) != ATOM_OK)
{
ATOMLOG (_STR("Retake %d\n"), i);
failures++;
break;
}
else
{
/*
* Set shared_data to 0 and wait to ensure that the
* second thread doesn't modify it while we have the
* mutex again.
*/
shared_data = 0;
/* Wait a while to give second thread potential to run */
atomTimerDelay(SYSTEM_TICKS_PER_SEC/4);
/*
* Check that shared_data has not been modified while we
* own the mutex.
*/
if (shared_data != 0)
{
/* Thread is still modifying the data */
ATOMLOG (_STR("Still modifying\n"));
failures++;
break;
}
/*
* Release the mutex, which will allow the second thread to
* wake and start modifying shared_data again.
*/
if (atomMutexPut (&mutex1) != ATOM_OK)
{
ATOMLOG (_STR("Failed release\n"));
failures++;
}
}
}
}
/* Delete mutex, test finished */
if (atomMutexDelete (&mutex1) != ATOM_OK)
{
ATOMLOG (_STR("Delete failed\n"));
failures++;
}
}
/* Log final status */
if (failures == 0)
{
ATOMLOG (_STR("Pass\n"));
}
else
{
ATOMLOG (_STR("Fail(%d)\n"), failures);
}
/* Quit */
return failures;
}
/**
* \b test_thread_func
*
* Entry point for test thread.
*
* @param[in] data Unused (optional thread entry parameter)
*
* @return None
*/
static void test_thread_func (uint32_t data)
{
uint8_t status;
/* Repeatedly attempt to get the mutex and set shared_data to 1 */
while (1)
{
/* Block on the mutex */
if ((status = atomMutexGet (&mutex1, 0)) != ATOM_OK)
{
/* Error getting mutex, notify the status code */
ATOMLOG (_STR("G%d\n"), status);
break;
}
/* Got the mutex */
else
{
/* Set shared_data to signify that we think we have the mutex */
shared_data = 1;
/* Release the mutex allowing the main thread to take it again */
if ((status = atomMutexPut (&mutex1)) != ATOM_OK)
{
/* Error putting mutex, notify the status code */
ATOMLOG (_STR("P%d\n"), status);
break;
}
}
}
/* Loop forever - we only reach here on error */
while (1)
{
atomTimerDelay (SYSTEM_TICKS_PER_SEC);
}
}

262
tests/mutex6.c Normal file
View File

@@ -0,0 +1,262 @@
/*
* Copyright (c) 2010, Kelvin Lawson. All rights reserved.
*
* 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. No personal names or organizations' names associated with the
* Atomthreads project may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT 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 PROJECT 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.
*/
#include "atom.h"
#include "atomtests.h"
#include "atommutex.h"
/* Number of times to lock the mutex during test */
#define TEST_LOCK_CNT 250
/* Test OS objects */
static ATOM_MUTEX mutex1;
static ATOM_TCB tcb1;
static uint8_t test1_thread_stack[TEST_THREAD_STACK_SIZE];
/* Global shared data protected by mutex */
static volatile int shared_data;
/* Forward declarations */
static void test_thread_func (uint32_t data);
/**
* \b test_start
*
* Start mutex test.
*
* This tests the lock count of a mutex. The mutex object should
* count the number of times a thread has locked the mutex and
* not fully release it for use by another thread until it has
* been released the same number of times it was locked.
*
* @retval Number of failures
*/
uint32_t test_start (void)
{
int failures;
int i;
/* Default to zero failures */
failures = 0;
/* Create mutex */
if (atomMutexCreate (&mutex1) != ATOM_OK)
{
ATOMLOG (_STR("Error creating mutex\n"));
failures++;
}
else
{
/* Initialise the shared_data to zero */
shared_data = 0;
/* Take the mutex several times */
for (i = 0; i < TEST_LOCK_CNT; i++)
{
if (atomMutexGet (&mutex1, 0) != ATOM_OK)
{
ATOMLOG (_STR("Error taking mutex\n"));
failures++;
break;
}
}
/* Create second thread */
if (atomThreadCreate(&tcb1, TEST_THREAD_PRIO, test_thread_func, 1,
&test1_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK)
{
/* Fail */
ATOMLOG (_STR("Error creating test thread\n"));
failures++;
}
/*
* The second thread has now been created and should block on
* the mutex until we release it. We wait a while and check that
* shared_data has not been modified.
*/
for (i = 0; i < 4; i++)
{
/*
* Sleep for a while to give the second thread a chance to
* modify shared_data, thought it shouldn't until we
* release the mutex enough times.
*/
atomTimerDelay (SYSTEM_TICKS_PER_SEC/4);
/* Check shared data. The second thread always sets it to one. */
if (shared_data != 0)
{
ATOMLOG (_STR("Shared data modified\n"));
failures++;
break;
}
}
/* Check successful so far */
if (failures == 0)
{
/*
* Release the mutex TEST_LOCK_CNT-1 times, after which we
* should still own the mutex (until we release one more time).
*/
for (i = 0; i < TEST_LOCK_CNT-1; i++)
{
if (atomMutexPut (&mutex1) != ATOM_OK)
{
ATOMLOG (_STR("Failed release\n"));
failures++;
}
}
/*
* Wait a little while then check that shared_data has
* not been modified (we should still own the mutex).
*/
atomTimerDelay (SYSTEM_TICKS_PER_SEC/4);
if (shared_data != 0)
{
ATOMLOG (_STR("Expected unmodified\n"));
failures++;
}
/*
* Release the mutex one more time, after which we should no
* longer own the mutex (and wake up the second thread).
*/
if (atomMutexPut (&mutex1) != ATOM_OK)
{
ATOMLOG (_STR("Failed release\n"));
failures++;
}
/*
* Wait a little while then check that shared_data has
* been modified by the second thread.
*/
atomTimerDelay (SYSTEM_TICKS_PER_SEC/4);
if (shared_data != 1)
{
ATOMLOG (_STR("Expected modified\n"));
failures++;
}
}
/*
* Finally attempt to release the mutex one more time, while
* we no longer own the mutex. Either the second thread will
* have ownership of it, or no thread will have ownership.
* In both cases we expect to get an ownership error when we
* attempt to release it.
*/
if (atomMutexPut (&mutex1) != ATOM_ERR_OWNERSHIP)
{
ATOMLOG (_STR("Failed locked+1 release\n"));
failures++;
}
/* Delete mutex, test finished */
if (atomMutexDelete (&mutex1) != ATOM_OK)
{
ATOMLOG (_STR("Delete failed\n"));
failures++;
}
}
/* Log final status */
if (failures == 0)
{
ATOMLOG (_STR("Pass\n"));
}
else
{
ATOMLOG (_STR("Fail(%d)\n"), failures);
}
/* Quit */
return failures;
}
/**
* \b test_thread_func
*
* Entry point for test thread.
*
* @param[in] data Unused (optional thread entry parameter)
*
* @return None
*/
static void test_thread_func (uint32_t data)
{
uint8_t status;
/* Repeatedly attempt to get the mutex and set shared_data to 1 */
while (1)
{
/* Block on the mutex */
if ((status = atomMutexGet (&mutex1, 0)) != ATOM_OK)
{
/* Error getting mutex, notify the status code */
ATOMLOG (_STR("G%d\n"), status);
break;
}
/* Got the mutex */
else
{
/* Set shared_data to signify that we think we have the mutex */
shared_data = 1;
/* Release the mutex allowing the main thread to take it again */
if ((status = atomMutexPut (&mutex1)) != ATOM_OK)
{
/* Error putting mutex, notify the status code */
ATOMLOG (_STR("P%d\n"), status);
break;
}
}
}
/* Loop forever - we only reach here on error */
while (1)
{
atomTimerDelay (SYSTEM_TICKS_PER_SEC);
}
}

236
tests/mutex7.c Normal file
View File

@@ -0,0 +1,236 @@
/*
* Copyright (c) 2010, Kelvin Lawson. All rights reserved.
*
* 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. No personal names or organizations' names associated with the
* Atomthreads project may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT 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 PROJECT 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.
*/
#include "atom.h"
#include "atomtests.h"
#include "atommutex.h"
/* Test OS objects */
static ATOM_MUTEX mutex1;
static ATOM_TCB tcb1;
static uint8_t test1_thread_stack[TEST_THREAD_STACK_SIZE];
/* Global shared data */
static volatile int shared_data;
/* Forward declarations */
static void testCallback (POINTER cb_data);
static void test_thread_func (uint32_t data);
/**
* \b test_start
*
* Start mutex test.
*
* This tests the ownership checks of the mutex library. Only threads
* which own a mutex can release it. It should not be possible to
* release a mutex if it is not owned by any thread, is owned by a
* different thread, or at interrupt context. We test here that all
* three cases are trapped.
*
* @retval Number of failures
*/
uint32_t test_start (void)
{
int failures;
ATOM_TIMER timer_cb;
/* Default to zero failures */
failures = 0;
/* Create mutex */
if (atomMutexCreate (&mutex1) != ATOM_OK)
{
ATOMLOG (_STR("Error creating mutex\n"));
failures++;
}
else
{
/* Initialise the shared_data to zero */
shared_data = 0;
/* Attempt to release the mutex when not owned by any thread */
if (atomMutexPut (&mutex1) != ATOM_ERR_OWNERSHIP)
{
ATOMLOG (_STR("Release error\n"));
failures++;
}
/* Create second thread */
else if (atomThreadCreate(&tcb1, TEST_THREAD_PRIO, test_thread_func, 1,
&test1_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK)
{
/* Fail */
ATOMLOG (_STR("Error creating test thread\n"));
failures++;
}
/*
* The second thread has now been created and should take ownership
* of the mutex. We wait a while and check that shared_data has been
* modified, which proves to us that the thread has taken the mutex.
*/
atomTimerDelay (SYSTEM_TICKS_PER_SEC/4);
if (shared_data != 1)
{
ATOMLOG (_STR("Shared data unmodified\n"));
failures++;
}
/* Check successful so far */
if (failures == 0)
{
/*
* Attempt to release the mutex again now that it is owned
* by another thread.
*/
if (atomMutexPut (&mutex1) != ATOM_ERR_OWNERSHIP)
{
ATOMLOG (_STR("Release error 2\n"));
failures++;
}
/* Finally check that the mutex cannot be released from an ISR */
/* Fill out the timer callback request structure */
timer_cb.cb_func = testCallback;
timer_cb.cb_data = NULL;
timer_cb.cb_ticks = SYSTEM_TICKS_PER_SEC;
/* Request the timer callback to run in one second */
if (atomTimerRegister (&timer_cb) != ATOM_OK)
{
ATOMLOG (_STR("Error registering timer\n"));
failures++;
}
/*
* Wait two seconds for shared_date to be set to 2
* indicating success. This happens if the timer
* callback received the expected ownership error
* when attempting to release the mutex.
*/
else
{
atomTimerDelay (2 * SYSTEM_TICKS_PER_SEC);
if (shared_data != 2)
{
ATOMLOG (_STR("Context check failed\n"));
failures++;
}
}
}
/* Delete mutex, test finished */
if (atomMutexDelete (&mutex1) != ATOM_OK)
{
ATOMLOG (_STR("Delete failed\n"));
failures++;
}
}
/* Log final status */
if (failures == 0)
{
ATOMLOG (_STR("Pass\n"));
}
else
{
ATOMLOG (_STR("Fail(%d)\n"), failures);
}
/* Quit */
return failures;
}
/**
* \b test_thread_func
*
* Entry point for test thread.
*
* @param[in] data Unused (optional thread entry parameter)
*
* @return None
*/
static void test_thread_func (uint32_t data)
{
uint8_t status;
/* Block on the mutex */
if ((status = atomMutexGet (&mutex1, 0)) != ATOM_OK)
{
/* Error getting mutex, notify the status code */
ATOMLOG (_STR("G%d\n"), status);
}
/* Got the mutex */
else
{
/* Set shared_data to signify that we think we have the mutex */
shared_data = 1;
}
/* Loop forever */
while (1)
{
atomTimerDelay (SYSTEM_TICKS_PER_SEC);
}
}
/**
* \b testCallback
*
* Attempt an atomMutexPut() on mutex1 from interrupt context.
* Should receive an ATOM_ERR_OWNERSHIP error. Sets shared_data
* to 2 if passes.
*
* @param[in] cb_data Not used
*/
static void testCallback (POINTER cb_data)
{
/* Check the return value from atomMutexPut() */
if (atomMutexPut(&mutex1) == ATOM_ERR_OWNERSHIP)
{
/* Received the error we expected, set shared_data to notify success */
shared_data = 2;
}
else
{
/* Did not get expected error, don't set shared_data signifying fail */
}
}

200
tests/mutex8.c Normal file
View File

@@ -0,0 +1,200 @@
/*
* Copyright (c) 2010, Kelvin Lawson. All rights reserved.
*
* 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. No personal names or organizations' names associated with the
* Atomthreads project may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT 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 PROJECT 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.
*/
#include "atom.h"
#include "atommutex.h"
#include "atomtests.h"
#include "atomuser.h"
/* Test OS objects */
static ATOM_MUTEX mutex1;
static ATOM_TCB tcb1, tcb2, tcb3;
static uint8_t test1_thread_stack[TEST_THREAD_STACK_SIZE];
static uint8_t test2_thread_stack[TEST_THREAD_STACK_SIZE];
static uint8_t test3_thread_stack[TEST_THREAD_STACK_SIZE];
/* Test results */
static volatile int pass_flag[3];
/* Forward declarations */
static void test_thread_func (uint32_t data);
/**
* \b test_start
*
* Start mutex test.
*
* This test verifies the mutex deletion API, by deleting a mutex
* on which multiple threads are blocking, and checking that all three
* are woken up with an appropriate error code.
*
* @retval Number of failures
*/
uint32_t test_start (void)
{
int failures;
int i;
/* Default to zero failures */
failures = 0;
/* Initialise pass status for all three threads to FALSE */
for (i = 0; i < 3; i++)
{
pass_flag[i] = FALSE;
}
/* Test wakeup of three threads on mutex deletion */
if (atomMutexCreate (&mutex1) != ATOM_OK)
{
ATOMLOG (_STR("Error creating mutex\n"));
failures++;
}
else
{
/* Take the mutex to ensure that all three test threads will block */
if (atomMutexGet (&mutex1, 0) != ATOM_OK)
{
ATOMLOG (_STR("Error taking mutex\n"));
failures++;
}
/* Create test thread 1 */
if (atomThreadCreate(&tcb1, TEST_THREAD_PRIO, test_thread_func, 0,
&test1_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK)
{
/* Fail */
ATOMLOG (_STR("Error creating test thread 1\n"));
failures++;
}
/* Create test thread 2 */
else if (atomThreadCreate(&tcb2, TEST_THREAD_PRIO, test_thread_func, 1,
&test2_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK)
{
/* Fail */
ATOMLOG (_STR("Error creating test thread 2\n"));
failures++;
}
/* Create test thread 3 */
else if (atomThreadCreate(&tcb3, TEST_THREAD_PRIO, test_thread_func, 2,
&test3_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK)
{
/* Fail */
ATOMLOG (_STR("Error creating test thread 3\n"));
failures++;
}
/* Test threads now created */
else
{
/* Wait a while for threads to start blocking on mutex1 */
atomTimerDelay (SYSTEM_TICKS_PER_SEC/4);
/* Delete mutex1 now that all three threads should be blocking */
if (atomMutexDelete (&mutex1) != ATOM_OK)
{
ATOMLOG (_STR("Delete fail\n"));
failures++;
}
else
{
/* Wait a while for all three threads to wake up */
atomTimerDelay (SYSTEM_TICKS_PER_SEC/4);
/* Check that all three threads have passed */
if ((pass_flag[0] != TRUE) || (pass_flag[1] != TRUE) || (pass_flag[2] != TRUE))
{
ATOMLOG (_STR("Thread fail\n"));
failures++;
}
}
}
}
/* Log final status */
if (failures == 0)
{
ATOMLOG (_STR("Pass\n"));
}
else
{
ATOMLOG (_STR("Fail(%d)\n"), failures);
}
/* Quit */
return failures;
}
/**
* \b test_thread_func
*
* Entry point for test threads.
*
* @param[in] data Thread ID (0-2)
*
* @return None
*/
static void test_thread_func (uint32_t data)
{
uint8_t status;
int thread_id;
/* Pull out the passed thread ID */
thread_id = (int)data;
/*
* Wait on mutex1 with timeout. We are expecting to be woken up
* by the main thread while blocking.
*/
status = atomMutexGet(&mutex1, (5 * SYSTEM_TICKS_PER_SEC));
if (status != ATOM_ERR_DELETED)
{
ATOMLOG (_STR("Test1 thread woke without deletion (%d)\n"), status);
}
else
{
/* We were woken due to deletion as expected, set pass_flag to notify success */
pass_flag[thread_id] = TRUE;
}
/* Wait forever */
while (1)
{
atomTimerDelay (SYSTEM_TICKS_PER_SEC);
}
}

187
tests/mutex9.c Normal file
View File

@@ -0,0 +1,187 @@
/*
* Copyright (c) 2010, Kelvin Lawson. All rights reserved.
*
* 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. No personal names or organizations' names associated with the
* Atomthreads project may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT 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 PROJECT 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.
*/
#include "atom.h"
#include "atomtests.h"
#include "atommutex.h"
/* Test OS objects */
static ATOM_MUTEX mutex1;
static ATOM_TCB tcb1;
static uint8_t test1_thread_stack[TEST_THREAD_STACK_SIZE];
/* Global shared data protected by mutex */
static volatile int shared_data;
/* Forward declarations */
static void test_thread_func (uint32_t data);
/**
* \b test_start
*
* Start mutex test.
*
* This tests timeouts on a mutex. We make a thread block with timeout
* on a mutex, and test that sufficient time has actually passed as
* was requested by the timeout parameter.
*
* The main thread creates a second thread which will immediately take
* ownership of the mutex. The test checks that the correct timeout
* occurs when the first thread blocks on the mutex which is already
* owned (by the second thread).
*
* @retval Number of failures
*/
uint32_t test_start (void)
{
int failures;
uint32_t start_time, end_time;
/* Default to zero failures */
failures = 0;
/* Create mutex */
if (atomMutexCreate (&mutex1) != ATOM_OK)
{
ATOMLOG (_STR("Error creating mutex\n"));
failures++;
}
else
{
/* Initialise the shared_data to zero */
shared_data = 0;
/* Create second thread */
if (atomThreadCreate(&tcb1, TEST_THREAD_PRIO, test_thread_func, 1,
&test1_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK)
{
/* Fail */
ATOMLOG (_STR("Error creating test thread\n"));
failures++;
}
/*
* The second thread has now been created and should take ownership
* of the mutex. We wait a while and check that shared_data has been
* modified, which proves to us that the thread has taken the mutex.
*/
atomTimerDelay (SYSTEM_TICKS_PER_SEC/4);
if (shared_data != 1)
{
ATOMLOG (_STR("Shared data unmodified\n"));
failures++;
}
/* Check successful so far */
if (failures == 0)
{
/* Take note of the start time */
start_time = atomTimeGet();
/* Block on the mutex with two second timeout */
if (atomMutexGet (&mutex1, 2 * SYSTEM_TICKS_PER_SEC) != ATOM_TIMEOUT)
{
ATOMLOG (_STR("Failed get\n"));
failures++;
}
/* Take note of the end time */
end_time = atomTimeGet();
/* Now check that two seconds have passed */
if ((end_time < (start_time + (2 * SYSTEM_TICKS_PER_SEC)))
|| (end_time > (start_time + (2 * SYSTEM_TICKS_PER_SEC) + 1)))
{
ATOMLOG (_STR("Bad time\n"));
failures++;
}
}
/* Delete mutex, test finished */
if (atomMutexDelete (&mutex1) != ATOM_OK)
{
ATOMLOG (_STR("Delete failed\n"));
failures++;
}
}
/* Log final status */
if (failures == 0)
{
ATOMLOG (_STR("Pass\n"));
}
else
{
ATOMLOG (_STR("Fail(%d)\n"), failures);
}
/* Quit */
return failures;
}
/**
* \b test_thread_func
*
* Entry point for test thread.
*
* @param[in] data Unused (optional thread entry parameter)
*
* @return None
*/
static void test_thread_func (uint32_t data)
{
uint8_t status;
/* Block on the mutex */
if ((status = atomMutexGet (&mutex1, 0)) != ATOM_OK)
{
/* Error getting mutex, notify the status code */
ATOMLOG (_STR("G%d\n"), status);
}
/* Got the mutex */
else
{
/* Set shared_data to signify that we think we have the mutex */
shared_data = 1;
}
/* Loop forever */
while (1)
{
atomTimerDelay (SYSTEM_TICKS_PER_SEC);
}
}

158
tests/queue1.c Normal file
View File

@@ -0,0 +1,158 @@
/*
* Copyright (c) 2010, Kelvin Lawson. All rights reserved.
*
* 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. No personal names or organizations' names associated with the
* Atomthreads project may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT 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 PROJECT 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.
*/
#include "atom.h"
#include "atomqueue.h"
#include "atomtests.h"
#include "atomuser.h"
/* Test queue size */
#define QUEUE_ENTRIES 16
/* Test OS objects */
static ATOM_QUEUE queue1;
static uint8_t queue1_storage[QUEUE_ENTRIES];
/**
* \b test_start
*
* Start queue test.
*
* This test exercises the queue creation and deletion APIs.
*
* Testing of deletion while threads are actually blocking in
* queue APIs is tested in queue2.c and queue3.c.
*
* @retval Number of failures
*/
uint32_t test_start (void)
{
int failures;
uint32_t i;
/* Default to zero failures */
failures = 0;
/* Test creation and deletion of queues: good values */
for (i = 0; i < 1000; i++)
{
if (atomQueueCreate (&queue1, &queue1_storage[0], sizeof(uint8_t), QUEUE_ENTRIES) == ATOM_OK)
{
if (atomQueueDelete (&queue1) == ATOM_OK)
{
/* Success */
}
else
{
/* Fail */
ATOMLOG (_STR("Error deleting queue\n"));
failures++;
break;
}
}
else
{
/* Fail */
ATOMLOG (_STR("Error creating queue\n"));
failures++;
break;
}
}
/* Test creation and deletion of queues: creation checks */
if (atomQueueCreate (NULL, &queue1_storage[0], sizeof(uint8_t), QUEUE_ENTRIES) != ATOM_OK)
{
/* Success */
}
else
{
/* Fail */
ATOMLOG (_STR("Bad queue ptr check\n"));
failures++;
}
if (atomQueueCreate (&queue1, NULL, sizeof(uint8_t), QUEUE_ENTRIES) != ATOM_OK)
{
/* Success */
}
else
{
/* Fail */
ATOMLOG (_STR("Bad buff ptr check\n"));
failures++;
}
if (atomQueueCreate (&queue1, &queue1_storage[0], 0, QUEUE_ENTRIES) != ATOM_OK)
{
/* Success */
}
else
{
/* Fail */
ATOMLOG (_STR("Bad size check\n"));
failures++;
}
if (atomQueueCreate (&queue1, &queue1_storage[0], sizeof(uint8_t), 0) != ATOM_OK)
{
/* Success */
}
else
{
/* Fail */
ATOMLOG (_STR("Bad entries check\n"));
failures++;
}
/* Test creation and deletion of queues: deletion checks */
if (atomQueueDelete (NULL) != ATOM_OK)
{
/* Success */
}
else
{
/* Fail */
ATOMLOG (_STR("Bad queue deletion checks\n"));
failures++;
}
/* Log final status */
if (failures == 0)
{
ATOMLOG (_STR("Pass\n"));
}
else
{
ATOMLOG (_STR("Fail(%d)\n"), failures);
}
/* Quit */
return failures;
}

218
tests/queue10.c Normal file
View File

@@ -0,0 +1,218 @@
/*
* Copyright (c) 2010, Kelvin Lawson. All rights reserved.
*
* 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. No personal names or organizations' names associated with the
* Atomthreads project may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT 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 PROJECT 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.
*/
#include "atom.h"
#include "atomqueue.h"
#include "atomtests.h"
#include "atomuser.h"
/* Test queue size */
#define QUEUE_ENTRIES 8
/* Test OS objects */
static ATOM_QUEUE queue1;
static uint32_t queue1_storage[QUEUE_ENTRIES];
/* Test message values (more values than can fit in an entire 8 message queue) */
uint32_t test_values[] =
{
0x12345678,
0xFF000000,
0x00FF0000,
0x0000FF00,
0x000000FF,
0xF000000F,
0x0F0000F0,
0x00F00F00,
0x000FF000,
0x87654321,
0xABCD0000,
0x0000CDEF
};
/**
* \b test_start
*
* Start queue test.
*
* This tests basic operation of queues.
*
* Messages are posted to and received from the queue and checked
* against expected values. To ensure correct ordering, queue posts
* and receives are done in blocks of different amounts, such that
* there will already be different numbers of messages in the queue
* whenever messages are posted and received.
*
* We test using 4-byte messages.
*
* @retval Number of failures
*/
uint32_t test_start (void)
{
int failures, tx_count, rx_count;
uint32_t msg;
/* Default to zero failures */
failures = 0;
/* Create test queue */
if (atomQueueCreate (&queue1, (uint8_t *)&queue1_storage[0], sizeof(queue1_storage[0]), QUEUE_ENTRIES) != ATOM_OK)
{
ATOMLOG (_STR("Error creating test queue\n"));
failures++;
}
else
{
/* Reset tx/rx counts */
tx_count = rx_count = 0;
/* Post 2 messages to the queue */
for (; tx_count < 2; tx_count++)
{
msg = test_values[tx_count];
if (atomQueuePut (&queue1, 0, (uint8_t *)&msg) != ATOM_OK)
{
ATOMLOG (_STR("Failed post\n"));
failures++;
}
}
/* Receive 1 message from the queue */
for (; rx_count < 2; rx_count++)
{
if (atomQueueGet (&queue1, 0, (uint8_t *)&msg) != ATOM_OK)
{
ATOMLOG (_STR("Failed get\n"));
failures++;
}
else if (msg != test_values[rx_count])
{
ATOMLOG (_STR("Val%d\n"), rx_count);
failures++;
}
}
/* Post 3 messages to the queue */
for (; tx_count < 5; tx_count++)
{
msg = test_values[tx_count];
if (atomQueuePut (&queue1, 0, (uint8_t *)&msg) != ATOM_OK)
{
ATOMLOG (_STR("Failed post\n"));
failures++;
}
}
/* Receive 2 messages from the queue */
for (; rx_count < 3; rx_count++)
{
if (atomQueueGet (&queue1, 0, (uint8_t *)&msg) != ATOM_OK)
{
ATOMLOG (_STR("Failed get\n"));
failures++;
}
else if (msg != test_values[rx_count])
{
ATOMLOG (_STR("Val%d\n"), rx_count);
failures++;
}
}
/* Post 5 messages to the queue */
for (; tx_count < 10; tx_count++)
{
msg = test_values[tx_count];
if (atomQueuePut (&queue1, 0, (uint8_t *)&msg) != ATOM_OK)
{
ATOMLOG (_STR("Failed post\n"));
failures++;
}
}
/* Receive 3 messages from the queue */
for (; rx_count < 6; rx_count++)
{
if (atomQueueGet (&queue1, 0, (uint8_t *)&msg) != ATOM_OK)
{
ATOMLOG (_STR("Failed get\n"));
failures++;
}
else if (msg != test_values[rx_count])
{
ATOMLOG (_STR("Val%d\n"), rx_count);
failures++;
}
}
/* Post 2 messages to the queue */
for (; tx_count < 12; tx_count++)
{
msg = test_values[tx_count];
if (atomQueuePut (&queue1, 0, (uint8_t *)&msg) != ATOM_OK)
{
ATOMLOG (_STR("Failed post\n"));
failures++;
}
}
/* Receive 6 messages from the queue */
for (; rx_count < 12; rx_count++)
{
if (atomQueueGet (&queue1, 0, (uint8_t *)&msg) != ATOM_OK)
{
ATOMLOG (_STR("Failed get\n"));
failures++;
}
else if (msg != test_values[rx_count])
{
ATOMLOG (_STR("Val%d\n"), rx_count);
failures++;
}
}
}
/* Log final status */
if (failures == 0)
{
ATOMLOG (_STR("Pass\n"));
}
else
{
ATOMLOG (_STR("Fail(%d)\n"), failures);
}
/* Quit */
return failures;
}

277
tests/queue2.c Normal file
View File

@@ -0,0 +1,277 @@
/*
* Copyright (c) 2010, Kelvin Lawson. All rights reserved.
*
* 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. No personal names or organizations' names associated with the
* Atomthreads project may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT 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 PROJECT 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.
*/
#include "atom.h"
#include "atomqueue.h"
#include "atomtests.h"
#include "atomuser.h"
/* Test queue size */
#define QUEUE_ENTRIES 16
/* Test OS objects */
static ATOM_QUEUE queue1;
static ATOM_TCB tcb1, tcb2;
static uint8_t test1_thread_stack[TEST_THREAD_STACK_SIZE];
static uint8_t test2_thread_stack[TEST_THREAD_STACK_SIZE];
static uint8_t queue1_storage[QUEUE_ENTRIES];
/* Test result tracking */
static volatile int g_result;
/* Forward declarations */
static void test1_thread_func (uint32_t data);
static void test2_thread_func (uint32_t data);
/**
* \b test_start
*
* Start queue test.
*
* This test exercises queue deletion, waking threads blocking on a queue
* in atomQueueGet() if the queue is deleted.
*
* Deletion wakeups are tested twice: once for a thread which is blocking
* in atomQueueGet() with a timeout and once for a thread which is
* blocking in atomQueueGet() with no timeout.
*
* Deletion of threads blocking in atomQueuePut() are tested in queue3.c.
*
* @retval Number of failures
*/
uint32_t test_start (void)
{
int failures;
/* Default to zero failures */
failures = 0;
/* Test wakeup of threads on queue deletion (thread blocking with no timeout) */
g_result = 0;
if (atomQueueCreate (&queue1, &queue1_storage[0], sizeof(uint8_t), QUEUE_ENTRIES) != ATOM_OK)
{
ATOMLOG (_STR("Error creating test queue\n"));
failures++;
}
/* Create a test thread that will block because the queue is empty */
else if (atomThreadCreate(&tcb1, TEST_THREAD_PRIO, test1_thread_func, 0,
&test1_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK)
{
/* Fail */
ATOMLOG (_STR("Error creating test thread 1\n"));
failures++;
}
else
{
/*
* We have created an empty queue. We want to see that the other
* thread is woken up if its queue is deleted. This is indicated
* through g_result being set.
*/
/* Wait for the other thread to start blocking on queue1 */
if (atomTimerDelay(SYSTEM_TICKS_PER_SEC) != ATOM_OK)
{
ATOMLOG (_STR("Failed timer delay\n"));
failures++;
}
else
{
/* The other thread will be blocking on queue1 now, delete queue1 */
if (atomQueueDelete(&queue1) != ATOM_OK)
{
ATOMLOG (_STR("Failed queue1 delete\n"));
failures++;
}
else
{
/* Queue1 deleted. The thread should now wake up and set g_result. */
atomTimerDelay (SYSTEM_TICKS_PER_SEC);
if (g_result == 0)
{
ATOMLOG (_STR("Notify fail\n"));
failures++;
}
else
{
/* Success */
}
}
}
}
/* Test wakeup of threads on semaphore deletion (thread blocking with timeout) */
g_result = 0;
if (atomQueueCreate (&queue1, &queue1_storage[0], sizeof(uint8_t), QUEUE_ENTRIES) != ATOM_OK)
{
ATOMLOG (_STR("Error creating test queue\n"));
failures++;
}
/* Create a test thread that will block because the queue is empty */
else if (atomThreadCreate(&tcb2, TEST_THREAD_PRIO, test2_thread_func, 0,
&test2_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK)
{
/* Fail */
ATOMLOG (_STR("Error creating test thread 2\n"));
failures++;
}
else
{
/*
* We have created an empty queue. We want to see that the other
* thread is woken up if its queue is deleted. This is indicated
* through g_result being set.
*/
/* Wait for the other thread to start blocking on queue1 */
if (atomTimerDelay(SYSTEM_TICKS_PER_SEC) != ATOM_OK)
{
ATOMLOG (_STR("Failed timer delay\n"));
failures++;
}
else
{
/* The other thread will be blocking on queue1 now, delete queue1 */
if (atomQueueDelete(&queue1) != ATOM_OK)
{
ATOMLOG (_STR("Failed queue1 delete\n"));
failures++;
}
else
{
/* Queue1 deleted. The thread should now wake up and set g_result. */
atomTimerDelay (SYSTEM_TICKS_PER_SEC);
if (g_result == 0)
{
ATOMLOG (_STR("Notify fail\n"));
failures++;
}
else
{
/* Success */
}
}
}
}
/* Log final status */
if (failures == 0)
{
ATOMLOG (_STR("Pass\n"));
}
else
{
ATOMLOG (_STR("Fail(%d)\n"), failures);
}
/* Quit */
return failures;
}
/**
* \b test1_thread_func
*
* Entry point for test thread 1.
*
* @param[in] data Unused (optional thread entry parameter)
*
* @return None
*/
static void test1_thread_func (uint32_t data)
{
uint8_t status, msg;
/*
* Wait on queue1 with no timeout. We are expecting to be woken up
* by the main thread while blocking.
*/
status = atomQueueGet(&queue1, 0, &msg);
if (status != ATOM_ERR_DELETED)
{
ATOMLOG (_STR("Test1 thread woke without deletion (%d)\n"), status);
}
else
{
/* We were woken due to deletion as expected, set g_result to notify success */
g_result = 1;
}
/* Wait forever */
while (1)
{
atomTimerDelay (SYSTEM_TICKS_PER_SEC);
}
}
/**
* \b test2_thread_func
*
* Entry point for test thread 2.
*
* @param[in] data Unused (optional thread entry parameter)
*
* @return None
*/
static void test2_thread_func (uint32_t data)
{
uint8_t status, msg;
/*
* Wait on queue1 with timeout. We are expecting to be woken up
* by the main thread while blocking.
*/
status = atomQueueGet(&queue1, (5 * SYSTEM_TICKS_PER_SEC), &msg);
if (status != ATOM_ERR_DELETED)
{
ATOMLOG (_STR("Test2 thread woke without deletion (%d)\n"), status);
}
else
{
/* We were woken due to deletion as expected, set g_result to notify success */
g_result = 1;
}
/* Wait forever */
while (1)
{
atomTimerDelay (SYSTEM_TICKS_PER_SEC);
}
}

317
tests/queue3.c Normal file
View File

@@ -0,0 +1,317 @@
/*
* Copyright (c) 2010, Kelvin Lawson. All rights reserved.
*
* 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. No personal names or organizations' names associated with the
* Atomthreads project may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT 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 PROJECT 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.
*/
#include "atom.h"
#include "atomqueue.h"
#include "atomtests.h"
#include "atomuser.h"
/* Test queue size */
#define QUEUE_ENTRIES 16
/* Test OS objects */
static ATOM_QUEUE queue1;
static ATOM_TCB tcb1, tcb2;
static uint8_t test1_thread_stack[TEST_THREAD_STACK_SIZE];
static uint8_t test2_thread_stack[TEST_THREAD_STACK_SIZE];
static uint8_t queue1_storage[QUEUE_ENTRIES];
/* Test result tracking */
static volatile int g_result;
/* Forward declarations */
static void test1_thread_func (uint32_t data);
static void test2_thread_func (uint32_t data);
/**
* \b test_start
*
* Start queue test.
*
* This test exercises queue deletion, waking threads blocking on a queue
* in atomQueuePut() if the queue is deleted.
*
* Deletion wakeups are tested twice: once for a thread which is blocking
* in atomQueuePut() with a timeout and once for a thread which is
* blocking in atomQueuePut() with no timeout.
*
* Deletion of threads blocking in atomQueueGet() are tested in queue2.c.
*
* @retval Number of failures
*/
uint32_t test_start (void)
{
int failures, i;
uint8_t msg;
/* Default to zero failures */
failures = 0;
/* Set a test value for posting to the queue */
msg = 0x66;
/* Test wakeup of threads on queue deletion (thread blocking with no timeout) */
g_result = 0;
if (atomQueueCreate (&queue1, &queue1_storage[0], sizeof(uint8_t), QUEUE_ENTRIES) != ATOM_OK)
{
ATOMLOG (_STR("Error creating test queue\n"));
failures++;
}
/* Successful queue creation */
else
{
/* Fill up all entries */
for (i = 0; i < QUEUE_ENTRIES; i++)
{
if (atomQueuePut (&queue1, 0, &msg) != ATOM_OK)
{
ATOMLOG (_STR("Error filling queue\n"));
failures++;
}
}
/* Create a test thread that will block because the queue is full */
if (atomThreadCreate(&tcb1, TEST_THREAD_PRIO, test1_thread_func, 0,
&test1_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK)
{
/* Fail */
ATOMLOG (_STR("Error creating test thread 1\n"));
failures++;
}
else
{
/*
* We have created and filled a queue. We want to see that the other
* thread is woken up if its queue is deleted. This is indicated
* through g_result being set.
*/
/* Wait for the other thread to start blocking on queue1 */
if (atomTimerDelay(SYSTEM_TICKS_PER_SEC) != ATOM_OK)
{
ATOMLOG (_STR("Failed timer delay\n"));
failures++;
}
else
{
/* The other thread will be blocking on queue1 now, delete queue1 */
if (atomQueueDelete(&queue1) != ATOM_OK)
{
ATOMLOG (_STR("Failed queue1 delete\n"));
failures++;
}
else
{
/* Queue1 deleted. The thread should now wake up and set g_result. */
atomTimerDelay (SYSTEM_TICKS_PER_SEC);
if (g_result == 0)
{
ATOMLOG (_STR("Notify fail\n"));
failures++;
}
else
{
/* Success */
}
}
}
}
}
/* Test wakeup of threads on semaphore deletion (thread blocking with timeout) */
g_result = 0;
if (atomQueueCreate (&queue1, &queue1_storage[0], sizeof(uint8_t), QUEUE_ENTRIES) != ATOM_OK)
{
ATOMLOG (_STR("Error creating test queue\n"));
failures++;
}
/* Successful queue creation */
else
{
/* Fill up all entries */
for (i = 0; i < QUEUE_ENTRIES; i++)
{
if (atomQueuePut (&queue1, 0, &msg) != ATOM_OK)
{
ATOMLOG (_STR("Error filling queue\n"));
failures++;
}
}
/* Create a test thread that will block because the queue is full */
if (atomThreadCreate(&tcb2, TEST_THREAD_PRIO, test2_thread_func, 0,
&test2_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK)
{
/* Fail */
ATOMLOG (_STR("Error creating test thread 2\n"));
failures++;
}
else
{
/*
* We have created and filled a queue. We want to see that the other
* thread is woken up if its queue is deleted. This is indicated
* through g_result being set.
*/
/* Wait for the other thread to start blocking on queue1 */
if (atomTimerDelay(SYSTEM_TICKS_PER_SEC) != ATOM_OK)
{
ATOMLOG (_STR("Failed timer delay\n"));
failures++;
}
else
{
/* The other thread will be blocking on queue1 now, delete queue1 */
if (atomQueueDelete(&queue1) != ATOM_OK)
{
ATOMLOG (_STR("Failed queue1 delete\n"));
failures++;
}
else
{
/* Queue1 deleted. The thread should now wake up and set g_result. */
atomTimerDelay (SYSTEM_TICKS_PER_SEC);
if (g_result == 0)
{
ATOMLOG (_STR("Notify fail\n"));
failures++;
}
else
{
/* Success */
}
}
}
}
}
/* Log final status */
if (failures == 0)
{
ATOMLOG (_STR("Pass\n"));
}
else
{
ATOMLOG (_STR("Fail(%d)\n"), failures);
}
/* Quit */
return failures;
}
/**
* \b test1_thread_func
*
* Entry point for test thread 1.
*
* @param[in] data Unused (optional thread entry parameter)
*
* @return None
*/
static void test1_thread_func (uint32_t data)
{
uint8_t status, msg;
/* Set a test value for posting to the queue */
msg = 0x66;
/*
* Post to queue1 with no timeout. The queue should be full so
* we are expecting to block. We should then be woken up by the
* main thread while blocking.
*/
status = atomQueuePut(&queue1, 0, &msg);
if (status != ATOM_ERR_DELETED)
{
ATOMLOG (_STR("Test1 thread woke without deletion (%d)\n"), status);
}
else
{
/* We were woken due to deletion as expected, set g_result to notify success */
g_result = 1;
}
/* Wait forever */
while (1)
{
atomTimerDelay (SYSTEM_TICKS_PER_SEC);
}
}
/**
* \b test2_thread_func
*
* Entry point for test thread 2.
*
* @param[in] data Unused (optional thread entry parameter)
*
* @return None
*/
static void test2_thread_func (uint32_t data)
{
uint8_t status, msg;
/* Set a test value for posting to the queue */
msg = 0x66;
/*
* Post to queue1 with timeout. The queue should be full so
* we are expecting to block. We should then be woken up by the
* main thread while blocking.
*/
status = atomQueuePut(&queue1, (5 * SYSTEM_TICKS_PER_SEC), &msg);
if (status != ATOM_ERR_DELETED)
{
ATOMLOG (_STR("Test2 thread woke without deletion (%d)\n"), status);
}
else
{
/* We were woken due to deletion as expected, set g_result to notify success */
g_result = 1;
}
/* Wait forever */
while (1)
{
atomTimerDelay (SYSTEM_TICKS_PER_SEC);
}
}

293
tests/queue4.c Normal file
View File

@@ -0,0 +1,293 @@
/*
* Copyright (c) 2010, Kelvin Lawson. All rights reserved.
*
* 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. No personal names or organizations' names associated with the
* Atomthreads project may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT 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 PROJECT 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.
*/
#include "atom.h"
#include "atomqueue.h"
#include "atomtests.h"
#include "atomuser.h"
/* Number of queue entries */
#define QUEUE_ENTRIES 8
/* Test OS objects */
static ATOM_QUEUE queue1, queue2;
static uint8_t queue1_storage[QUEUE_ENTRIES];
static uint8_t queue2_storage[QUEUE_ENTRIES];
/* Test result tracking */
static volatile int g_result;
/* Forward declarations */
static void testCallbackGet (POINTER cb_data);
static void testCallbackPut (POINTER cb_data);
/**
* \b test_start
*
* Start queue test.
*
* This test exercises the atomQueueGet() and atomQueuePut() APIs
* particularly forcing the various error indications which can be
* returned from the APIs to ensure that handling for these corner
* cases has been correctly implemented.
*
* @retval Number of failures
*/
uint32_t test_start (void)
{
int failures;
uint8_t msg;
ATOM_TIMER timer_cb;
int count;
/* Default to zero failures */
failures = 0;
/* Create two test queues: queue1 is empty, queue2 is full */
/* Empty queue1 creation */
if (atomQueueCreate (&queue1, &queue1_storage[0], sizeof(uint8_t), QUEUE_ENTRIES) != ATOM_OK)
{
ATOMLOG (_STR("Queue1 create\n"));
failures++;
}
/* Full queue2 creation */
if (atomQueueCreate (&queue2, &queue2_storage[0], sizeof(uint8_t), QUEUE_ENTRIES) != ATOM_OK)
{
ATOMLOG (_STR("Queue2 create\n"));
failures++;
}
else
{
/* Fill the queue */
msg = 0x66;
for (count = 0; count < QUEUE_ENTRIES; count++)
{
/* Add one message at a time */
if (atomQueuePut (&queue2, 0, &msg) != ATOM_OK)
{
ATOMLOG (_STR("Queue2 put\n"));
failures++;
}
}
}
/* Test parameter checks */
if (atomQueueGet (NULL, 0, &msg) != ATOM_ERR_PARAM)
{
ATOMLOG (_STR("Get queue param failed\n"));
failures++;
}
if (atomQueueGet (&queue1, 0, NULL) != ATOM_ERR_PARAM)
{
ATOMLOG (_STR("Get msg param failed\n"));
failures++;
}
if (atomQueuePut (NULL, 0, &msg) != ATOM_ERR_PARAM)
{
ATOMLOG (_STR("Put queue param failed\n"));
failures++;
}
if (atomQueuePut (&queue1, 0, NULL) != ATOM_ERR_PARAM)
{
ATOMLOG (_STR("Put msg param failed\n"));
failures++;
}
/* Test atomQueueGet() can not be called from interrupt context */
g_result = 0;
/* Fill out the timer callback request structure */
timer_cb.cb_func = testCallbackGet;
timer_cb.cb_data = NULL;
timer_cb.cb_ticks = SYSTEM_TICKS_PER_SEC;
/* Request the timer callback to run in one second */
if (atomTimerRegister (&timer_cb) != ATOM_OK)
{
ATOMLOG (_STR("Error registering timer\n"));
failures++;
}
/* Wait two seconds for g_result to be set indicating success */
else
{
atomTimerDelay (2 * SYSTEM_TICKS_PER_SEC);
if (g_result != 1)
{
ATOMLOG (_STR("Get context check failed\n"));
failures++;
}
}
/* Test atomQueuePut() can not be called from interrupt context */
g_result = 0;
/* Fill out the timer callback request structure */
timer_cb.cb_func = testCallbackPut;
timer_cb.cb_data = NULL;
timer_cb.cb_ticks = SYSTEM_TICKS_PER_SEC;
/* Request the timer callback to run in one second */
if (atomTimerRegister (&timer_cb) != ATOM_OK)
{
ATOMLOG (_STR("Error registering timer\n"));
failures++;
}
/* Wait two seconds for g_result to be set indicating success */
else
{
atomTimerDelay (2 * SYSTEM_TICKS_PER_SEC);
if (g_result != 1)
{
ATOMLOG (_STR("Put context check failed\n"));
failures++;
}
}
/* Test ATOM_TIMEOUT is returned for Get/Put calls with timeout */
/* Attempt atomQueueGet() on empty queue to force timeout */
if (atomQueueGet (&queue1, SYSTEM_TICKS_PER_SEC, &msg) != ATOM_TIMEOUT)
{
ATOMLOG (_STR("Timeout q1 failed\n"));
failures++;
}
/* Attempt atomQueuePut() on full queue to force timeout */
msg = 0x66;
if (atomQueuePut (&queue2, SYSTEM_TICKS_PER_SEC, &msg) != ATOM_TIMEOUT)
{
ATOMLOG (_STR("Timeout q2 failed\n"));
failures++;
}
/* Test ATOM_WOULDBLOCK is returned for Get/Put calls with -1 timeout */
/* Attempt atomQueueGet() on empty queue to force block */
if (atomQueueGet (&queue1, -1, &msg) != ATOM_WOULDBLOCK)
{
ATOMLOG (_STR("Timeout q1 failed\n"));
failures++;
}
/* Attempt atomQueuePut() on full queue to force block */
msg = 0x66;
if (atomQueuePut (&queue2, -1, &msg) != ATOM_WOULDBLOCK)
{
ATOMLOG (_STR("Timeout q2 failed\n"));
failures++;
}
/* Delete the test queues */
if (atomQueueDelete (&queue1) != ATOM_OK)
{
ATOMLOG (_STR("Error deleting q1\n"));
failures++;
}
if (atomQueueDelete (&queue2) != ATOM_OK)
{
ATOMLOG (_STR("Error deleting q2\n"));
failures++;
}
/* Log final status */
if (failures == 0)
{
ATOMLOG (_STR("Pass\n"));
}
else
{
ATOMLOG (_STR("Fail(%d)\n"), failures);
}
/* Quit */
return failures;
}
/**
* \b testCallbackGet
*
* Attempt an atomQueueGet() on (empty) queue1 from interrupt context.
* Should receive an ATOM_ERR_CONTEXT error. Sets g_result if passes.
*
* @param[in] cb_data Not used
*/
static void testCallbackGet (POINTER cb_data)
{
uint8_t msg;
/* Check the return value from atomQueueGet() */
if (atomQueueGet(&queue1, 0, &msg) == ATOM_ERR_CONTEXT)
{
/* Received the error we expected, set g_result to notify success */
g_result = 1;
}
else
{
/* Did not get expected error, don't set g_result signifying fail */
}
}
/**
* \b testCallbackPut
*
* Attempt an atomQueuePut() on (full) queue2 from interrupt context.
* Should receive an ATOM_ERR_CONTEXT error. Sets g_result if passes.
*
* @param[in] cb_data Not used
*/
static void testCallbackPut (POINTER cb_data)
{
uint8_t msg;
/* Check the return value from atomQueuePut() */
msg = 0x66;
if (atomQueuePut(&queue2, 0, &msg) == ATOM_ERR_CONTEXT)
{
/* Received the error we expected, set g_result to notify success */
g_result = 1;
}
else
{
/* Did not get expected error, don't set g_result signifying fail */
}
}

276
tests/queue5.c Normal file
View File

@@ -0,0 +1,276 @@
/*
* Copyright (c) 2010, Kelvin Lawson. All rights reserved.
*
* 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. No personal names or organizations' names associated with the
* Atomthreads project may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT 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 PROJECT 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.
*/
#include "atom.h"
#include "atomtests.h"
#include "atomqueue.h"
/* Number of queue entries */
#define QUEUE_ENTRIES 8
/* Test OS objects */
static ATOM_QUEUE queue1;
static uint8_t queue1_storage[QUEUE_ENTRIES];
static ATOM_TCB tcb1, tcb2, tcb3, tcb4;
static uint8_t test1_thread_stack[TEST_THREAD_STACK_SIZE];
static uint8_t test2_thread_stack[TEST_THREAD_STACK_SIZE];
static uint8_t test3_thread_stack[TEST_THREAD_STACK_SIZE];
static uint8_t test4_thread_stack[TEST_THREAD_STACK_SIZE];
/* Data updated by threads */
static volatile uint8_t wake_cnt;
static volatile uint8_t wake_order[4];
/* Forward declarations */
static void test_thread_func (uint32_t data);
/**
* \b test_start
*
* Start queue test.
*
* With multiple threads blocking on a single queue, this test confirms that
* they are woken in order when the queue is posted. The correct order for
* waking is that the higher priority threads are woken first, followed by the
* lower priority threads. Where multiple threads of the same priority are
* waiting, the threads are woken in FIFO order (the order in which they started
* waiting on the queue).
*
* To test this we create four threads which all wait on a single queue.
* One pair of threads are running at high priority, with the other pair at a
* lower priority:
*
* Thread 1: low prio thread A
* Thread 2: low prio thread B
* Thread 3: high prio thread A
* Thread 4: high prio thread B
*
* The threads are forced to start blocking on the same queue in the
* above order.
*
* We expect to see them woken up in the following order:
* 3, 4, 1, 2
*
* This proves the multiple blocking thread ordering in terms of both
* the priority-queueing and same-priority-FIFO-queueing.
*
* @retval Number of failures
*/
uint32_t test_start (void)
{
int failures, count;
uint8_t msg;
/* Default to zero failures */
failures = 0;
/* Create empty queue */
if (atomQueueCreate (&queue1, &queue1_storage[0], sizeof(uint8_t), QUEUE_ENTRIES) != ATOM_OK)
{
ATOMLOG (_STR("Error creating test q1\n"));
failures++;
}
/* Start the threads */
else
{
/*
* The test threads all start by calling atomQueueGet() to receive
* a message from the queue. Because the queue is empty, all test
* threads should immediately block on the queue (until a message
* is posted to it).
*/
/* Create Thread 1 (lower priority thread A) */
if (atomThreadCreate(&tcb1, TEST_THREAD_PRIO+1, test_thread_func, 1,
&test1_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK)
{
/* Fail */
ATOMLOG (_STR("Error creating test thread\n"));
failures++;
}
/* Delay to ensure the thread will start blocking on the queue */
atomTimerDelay (SYSTEM_TICKS_PER_SEC/4);
/* Create Thread 2 (lower priority thread B) */
if (atomThreadCreate(&tcb2, TEST_THREAD_PRIO+1, test_thread_func, 2,
&test2_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK)
{
/* Fail */
ATOMLOG (_STR("Error creating test thread\n"));
failures++;
}
/* Delay to ensure the thread will start blocking on the queue */
atomTimerDelay (SYSTEM_TICKS_PER_SEC/4);
/* Create Thread 3 (higher priority thread A) */
if (atomThreadCreate(&tcb3, TEST_THREAD_PRIO, test_thread_func, 3,
&test3_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK)
{
/* Fail */
ATOMLOG (_STR("Error creating test thread\n"));
failures++;
}
/* Delay to ensure the thread will start blocking on the queue */
atomTimerDelay (SYSTEM_TICKS_PER_SEC/4);
/* Create Thread 4 (higher priority thread B) */
if (atomThreadCreate(&tcb4, TEST_THREAD_PRIO, test_thread_func, 4,
&test4_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK)
{
/* Fail */
ATOMLOG (_STR("Error creating test thread\n"));
failures++;
}
/* Delay to ensure the thread will start blocking on the queue */
atomTimerDelay (SYSTEM_TICKS_PER_SEC/4);
/* All four threads will now be blocking on queue1 */
/*
* Initialise wake count, used by threads to determine
* what order they were woken in.
*/
wake_cnt = 0;
/* Loop waking all four threads */
for (count = 0; count < 4; count++)
{
/*
* Post a message to the queue. This will wake up one of the threads
* blocking on it (because it is currently empty). That thread will
* wake up, note the order at which it was woken, then go to sleep
* forever leaving the queue empty again. This is done four times so
* that all four threads are woken, noting their wake order.
*/
msg = 0x66;
if (atomQueuePut (&queue1, 0, &msg) != ATOM_OK)
{
ATOMLOG (_STR("Post fail\n"));
failures++;
}
/*
* Sleep to give the thread time to wake up and modify the shared
* global data wake_cnt and wake_order[]. We deliberately do not
* use a mutex for protecting access to this shared data, as we
* are testing the queue module in isolation here.
*/
atomTimerDelay (SYSTEM_TICKS_PER_SEC / 4);
}
/* All four threads now woken up, check they woke in correct order */
if ((wake_order[0] != 3) && (wake_order[1] != 4)
&& (wake_order[2] != 1) && (wake_order[3] != 2))
{
ATOMLOG (_STR("Bad order %d,%d,%d,%d\n"),
wake_order[0], wake_order[1], wake_order[2], wake_order[3]);
failures++;
}
/* Delete queue, test finished */
if (atomQueueDelete (&queue1) != ATOM_OK)
{
ATOMLOG (_STR("Delete failed\n"));
failures++;
}
}
/* Log final status */
if (failures == 0)
{
ATOMLOG (_STR("Pass\n"));
}
else
{
ATOMLOG (_STR("Fail(%d)\n"), failures);
}
/* Quit */
return failures;
}
/**
* \b test_thread_func
*
* Entry point for test thread. The same thread entry point is used for all
* four test threads, with the thread number/ID (1-4) passed as the entry
* point parameter.
*
* @param[in] data Thread number (1,2,3,4)
*
* @return None
*/
static void test_thread_func (uint32_t data)
{
uint8_t thread_id;
uint8_t msg;
/* Thread ID is passed through the function parameter */
thread_id = (uint8_t)data;
/*
* Wait for a message to appear on queue1. At creation of all test
* threads the queue is empty, so all four threads will block here.
*/
if (atomQueueGet (&queue1, 0, &msg) != ATOM_OK)
{
ATOMLOG (_STR("Get fail\n"));
}
else
{
/*
* Store our thread ID in the array using the current
* wake_cnt order. The threads are woken with large
* pauses between, which provides protection for this
* global data. This allows us to test queues without
* assuming a working implementation of a mutex (or
* similar protection mechanism).
*/
wake_order[wake_cnt++] = thread_id;
}
/* Loop forever */
while (1)
{
atomTimerDelay (SYSTEM_TICKS_PER_SEC);
}
}

241
tests/queue6.c Normal file
View File

@@ -0,0 +1,241 @@
/*
* Copyright (c) 2010, Kelvin Lawson. All rights reserved.
*
* 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. No personal names or organizations' names associated with the
* Atomthreads project may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT 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 PROJECT 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.
*/
#include "atom.h"
#include "atomqueue.h"
#include "atomtests.h"
#include "atomuser.h"
/* Test queue size */
#define QUEUE_ENTRIES 8
/* Test OS objects */
static ATOM_QUEUE queue1;
static uint32_t queue1_storage[QUEUE_ENTRIES];
static ATOM_TCB tcb1;
static uint8_t test1_thread_stack[TEST_THREAD_STACK_SIZE];
/* Test message values (more values than can fit in an entire 8 message queue) */
uint32_t test_values[] =
{
0x12345678,
0xFF000000,
0x00FF0000,
0x0000FF00,
0x000000FF,
0xF000000F,
0x0F0000F0,
0x00F00F00,
0x000FF000,
0x87654321,
0xABCD0000,
0x0000CDEF
};
/* Test result tracking */
static volatile int g_result;
/* Forward declarations */
static void test1_thread_func (uint32_t data);
/**
* \b test_start
*
* Start queue test.
*
* This tests basic operation of queues.
*
* The main test thread creates a second thread and posts
* a series of messages to the second thread. The message
* values are checked against the expected values.
*
* We test using 4-byte messages.
*
* @retval Number of failures
*/
uint32_t test_start (void)
{
int failures, count;
int num_entries;
uint32_t msg;
/* Default to zero failures */
failures = 0;
g_result = 0;
/* Create test queue */
if (atomQueueCreate (&queue1, (uint8_t *)&queue1_storage[0], sizeof(queue1_storage[0]), QUEUE_ENTRIES) != ATOM_OK)
{
ATOMLOG (_STR("Error creating test queue\n"));
failures++;
}
/* Create a test thread that will block because the queue is empty */
else if (atomThreadCreate(&tcb1, TEST_THREAD_PRIO + 1, test1_thread_func, 0,
&test1_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK)
{
/* Fail */
ATOMLOG (_STR("Error creating test thread 1\n"));
failures++;
}
else
{
/*
* We have created an empty queue and a thread which should now
* be blocking on the queue. The test thread is lower priority
* than us.
*/
/* Wait for the other thread to start blocking on queue1 */
if (atomTimerDelay(SYSTEM_TICKS_PER_SEC) != ATOM_OK)
{
ATOMLOG (_STR("Failed timer delay\n"));
failures++;
}
else
{
/*
* Post all entries in the test array to the queue.
* Because the second thread is lower priority than
* us, we will post 8 messages until the queue is
* full without waking up the second thread at all.
* At that point, we will block and the second
* thread will remove one message from the queue.
* With a spare entry in the queue, this thread
* will wake up again and post another message.
* This will continue until this thread has posted
* all messages, at which point the second thread
* will drain all remaining messages from the
* queue.
*
* Through this scheme we are able to test posting
* to the queue at all possible fill levels.
*/
num_entries = sizeof(test_values) / sizeof(test_values[0]);
for (count = 0; count < num_entries; count++)
{
/* Increment through and post all test values to the queue */
msg = test_values[count];
if (atomQueuePut (&queue1, 0, (uint8_t *)&msg) != ATOM_OK)
{
ATOMLOG (_STR("Failed post\n"));
failures++;
}
}
/* Sleep a while for the second thread to finish */
atomTimerDelay (SYSTEM_TICKS_PER_SEC);
/* Check that the second thread has found all test values */
if (g_result != 1)
{
ATOMLOG (_STR("Bad test vals\n"));
failures++;
}
}
}
/* Log final status */
if (failures == 0)
{
ATOMLOG (_STR("Pass\n"));
}
else
{
ATOMLOG (_STR("Fail(%d)\n"), failures);
}
/* Quit */
return failures;
}
/**
* \b test1_thread_func
*
* Entry point for test thread 1.
*
* @param[in] data Unused (optional thread entry parameter)
*
* @return None
*/
static void test1_thread_func (uint32_t data)
{
uint32_t msg;
int num_entries, count, failures;
/* Default to no errors */
failures = 0;
/*
* Loop receiving messages until we have received the number of
* values in the test array.
*/
num_entries = sizeof(test_values) / sizeof(test_values[0]);
for (count = 0; count < num_entries; count++)
{
/* Receive a value from the queue */
if (atomQueueGet (&queue1, 0, (uint8_t *)&msg) != ATOM_OK)
{
ATOMLOG (_STR("Failed get\n"));
failures++;
}
/* Check that we received the expected value */
else if (msg != test_values[count])
{
ATOMLOG (_STR("Val%d\n"), count);
failures++;
}
}
/*
* Set g_result to indicate success if we had no failures.
* Thread-protection is not required on g_result because it
* is only ever set by this thread.
*/
if (failures == 0)
{
/* No failures */
g_result = 1;
}
/* Wait forever */
while (1)
{
atomTimerDelay (SYSTEM_TICKS_PER_SEC);
}
}

201
tests/queue7.c Normal file
View File

@@ -0,0 +1,201 @@
/*
* Copyright (c) 2010, Kelvin Lawson. All rights reserved.
*
* 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. No personal names or organizations' names associated with the
* Atomthreads project may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT 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 PROJECT 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.
*/
#include "atom.h"
#include "atomqueue.h"
#include "atomtests.h"
#include "atomuser.h"
/* Test queue size */
#define QUEUE_ENTRIES 8
/* Test OS objects */
static ATOM_QUEUE queue1;
static uint8_t queue1_storage[QUEUE_ENTRIES];
static ATOM_TCB tcb1, tcb2, tcb3;
static uint8_t test1_thread_stack[TEST_THREAD_STACK_SIZE];
static uint8_t test2_thread_stack[TEST_THREAD_STACK_SIZE];
static uint8_t test3_thread_stack[TEST_THREAD_STACK_SIZE];
/* Test results */
static volatile int pass_flag[3];
/* Forward declarations */
static void test_thread_func (uint32_t data);
/**
* \b test_start
*
* Start queue test.
*
* This test verifies the queue deletion API, by deleting a queue
* on which multiple threads are blocking, and checking that all three
* are woken up with an appropriate error code.
*
* @retval Number of failures
*/
uint32_t test_start (void)
{
int failures;
int i;
/* Default to zero failures */
failures = 0;
/* Initialise pass status for all three threads to FALSE */
for (i = 0; i < 3; i++)
{
pass_flag[i] = FALSE;
}
/* Test wakeup of three threads on queue deletion */
if (atomQueueCreate (&queue1, &queue1_storage[0], sizeof(queue1_storage[0]), QUEUE_ENTRIES) != ATOM_OK)
{
ATOMLOG (_STR("Error creating Q\n"));
failures++;
}
else
{
/* The queue is empty so all three test threads will block */
/* Create test thread 1 */
if (atomThreadCreate(&tcb1, TEST_THREAD_PRIO, test_thread_func, 0,
&test1_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK)
{
/* Fail */
ATOMLOG (_STR("Error creating test thread 1\n"));
failures++;
}
/* Create test thread 2 */
else if (atomThreadCreate(&tcb2, TEST_THREAD_PRIO, test_thread_func, 1,
&test2_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK)
{
/* Fail */
ATOMLOG (_STR("Error creating test thread 2\n"));
failures++;
}
/* Create test thread 3 */
else if (atomThreadCreate(&tcb3, TEST_THREAD_PRIO, test_thread_func, 2,
&test3_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK)
{
/* Fail */
ATOMLOG (_STR("Error creating test thread 3\n"));
failures++;
}
/* Test threads now created */
else
{
/* Wait a while for threads to start blocking on queue1 */
atomTimerDelay (SYSTEM_TICKS_PER_SEC/4);
/* Delete queue1 now that all three threads should be blocking */
if (atomQueueDelete (&queue1) != ATOM_OK)
{
ATOMLOG (_STR("Delete fail\n"));
failures++;
}
else
{
/* Wait a while for all three threads to wake up */
atomTimerDelay (SYSTEM_TICKS_PER_SEC/4);
/* Check that all three threads have passed */
if ((pass_flag[0] != TRUE) || (pass_flag[1] != TRUE) || (pass_flag[2] != TRUE))
{
ATOMLOG (_STR("Thread fail\n"));
failures++;
}
}
}
}
/* Log final status */
if (failures == 0)
{
ATOMLOG (_STR("Pass\n"));
}
else
{
ATOMLOG (_STR("Fail(%d)\n"), failures);
}
/* Quit */
return failures;
}
/**
* \b test_thread_func
*
* Entry point for test threads.
*
* @param[in] data Thread ID (0-2)
*
* @return None
*/
static void test_thread_func (uint32_t data)
{
uint8_t status;
uint8_t msg;
int thread_id;
/* Pull out the passed thread ID */
thread_id = (int)data;
/*
* Wait on queue1 with timeout. We are expecting to be woken up
* by the main thread while blocking.
*/
status = atomQueueGet(&queue1, (5 * SYSTEM_TICKS_PER_SEC), &msg);
if (status != ATOM_ERR_DELETED)
{
ATOMLOG (_STR("Test1 thread woke without deletion (%d)\n"), status);
}
else
{
/* We were woken due to deletion as expected, set pass_flag to notify success */
pass_flag[thread_id] = TRUE;
}
/* Wait forever */
while (1)
{
atomTimerDelay (SYSTEM_TICKS_PER_SEC);
}
}

118
tests/queue8.c Normal file
View File

@@ -0,0 +1,118 @@
/*
* Copyright (c) 2010, Kelvin Lawson. All rights reserved.
*
* 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. No personal names or organizations' names associated with the
* Atomthreads project may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT 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 PROJECT 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.
*/
#include "atom.h"
#include "atomtests.h"
#include "atomqueue.h"
/* Test queue size */
#define QUEUE_ENTRIES 8
/* Test OS objects */
static ATOM_QUEUE queue1;
static uint8_t queue1_storage[QUEUE_ENTRIES];
/**
* \b test_start
*
* Start queue test.
*
* This tests timeouts on a queue. We make a thread block with timeout
* on a queue, and test that sufficient time has actually passed as
* was requested by the timeout parameter.
*
* @retval Number of failures
*/
uint32_t test_start (void)
{
int failures;
uint32_t start_time, end_time;
uint8_t msg;
/* Default to zero failures */
failures = 0;
/* Create queue */
if (atomQueueCreate (&queue1, &queue1_storage[0], sizeof(queue1_storage[0]), QUEUE_ENTRIES) != ATOM_OK)
{
ATOMLOG (_STR("Error creating Q\n"));
failures++;
}
else
{
/* The queue is empty so atomQueueGet() calls will block */
/* Take note of the start time */
start_time = atomTimeGet();
/* Block on the queue with two second timeout */
if (atomQueueGet (&queue1, 2 * SYSTEM_TICKS_PER_SEC, &msg) != ATOM_TIMEOUT)
{
ATOMLOG (_STR("Failed get\n"));
failures++;
}
/* Take note of the end time */
end_time = atomTimeGet();
/* Now check that two seconds have passed */
if ((end_time < (start_time + (2 * SYSTEM_TICKS_PER_SEC)))
|| (end_time > (start_time + (2 * SYSTEM_TICKS_PER_SEC) + 1)))
{
ATOMLOG (_STR("Bad time\n"));
failures++;
}
/* Delete queue, test finished */
if (atomQueueDelete (&queue1) != ATOM_OK)
{
ATOMLOG (_STR("Delete failed\n"));
failures++;
}
}
/* Log final status */
if (failures == 0)
{
ATOMLOG (_STR("Pass\n"));
}
else
{
ATOMLOG (_STR("Fail(%d)\n"), failures);
}
/* Quit */
return failures;
}

265
tests/queue9.c Normal file
View File

@@ -0,0 +1,265 @@
/*
* Copyright (c) 2010, Kelvin Lawson. All rights reserved.
*
* 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. No personal names or organizations' names associated with the
* Atomthreads project may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT 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 PROJECT 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.
*/
#include "atom.h"
#include "atomtests.h"
#include "atomqueue.h"
#include "atomsem.h"
/* Number of test loops for stress-test */
#define NUM_TEST_LOOPS 10000
/* Test queue size */
#define QUEUE_ENTRIES 8
/* Test OS objects */
static ATOM_QUEUE queue1;
static uint8_t queue1_storage[QUEUE_ENTRIES];
static ATOM_SEM sem1;
static ATOM_TCB tcb1, tcb2, tcb3, tcb4;
static uint8_t test1_thread_stack[TEST_THREAD_STACK_SIZE];
static uint8_t test2_thread_stack[TEST_THREAD_STACK_SIZE];
static uint8_t test3_thread_stack[TEST_THREAD_STACK_SIZE];
static uint8_t test4_thread_stack[TEST_THREAD_STACK_SIZE];
/*
* Global failure count (can be updated by test threads but is
* protected by an interrupt lockout).
*/
static volatile int g_failures;
/* Forward declarations */
static void test_thread_func (uint32_t data);
/**
* \b test_start
*
* Start queue test.
*
* Stress-tests queue Get and Put operations. Four threads are created which are
* continually Putting and Getting the same queue, with no time delays between
* each Get/Put. Because all threads are at the same priority this ensures that
* on timeslices when threads are rescheduled there are several context-switch
* points, while the threads may be part-way through queue API calls.
*
* @retval Number of g_failures
*/
uint32_t test_start (void)
{
CRITICAL_STORE;
int finish_cnt;
/* Default to zero g_failures */
g_failures = 0;
/* Create queue to stress */
if (atomQueueCreate (&queue1, &queue1_storage[0], sizeof(queue1_storage[0]), QUEUE_ENTRIES) != ATOM_OK)
{
ATOMLOG (_STR("Error creating Q\n"));
g_failures++;
}
/* Create sem to receive thread-finished notification */
else if (atomSemCreate (&sem1, 0) != ATOM_OK)
{
ATOMLOG (_STR("Error creating sem\n"));
g_failures++;
}
else
{
/* Create Thread 1 */
if (atomThreadCreate(&tcb1, TEST_THREAD_PRIO, test_thread_func, 1,
&test1_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK)
{
/* Fail */
ATOMLOG (_STR("Error creating test thread\n"));
CRITICAL_START ();
g_failures++;
CRITICAL_END ();
}
/* Create Thread 2 */
if (atomThreadCreate(&tcb2, TEST_THREAD_PRIO, test_thread_func, 2,
&test2_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK)
{
/* Fail */
ATOMLOG (_STR("Error creating test thread\n"));
CRITICAL_START ();
g_failures++;
CRITICAL_END ();
}
/* Create Thread 3 */
if (atomThreadCreate(&tcb3, TEST_THREAD_PRIO, test_thread_func, 3,
&test3_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK)
{
/* Fail */
ATOMLOG (_STR("Error creating test thread\n"));
CRITICAL_START ();
g_failures++;
CRITICAL_END ();
}
/* Create Thread 4 */
if (atomThreadCreate(&tcb4, TEST_THREAD_PRIO, test_thread_func, 4,
&test4_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK)
{
/* Fail */
ATOMLOG (_STR("Error creating test thread\n"));
CRITICAL_START ();
g_failures++;
CRITICAL_END ();
}
/*
* All four threads will now be performing Gets/Puts on queue1.
* When they have finished they will post sem1, so we wait
* until sem1 is posted four times.
*/
finish_cnt = 0;
while (1)
{
/*
* Attempt to Get sem1. When we have managed to get
* the semaphore four times, it must have been posted
* by all four threads.
*/
if (atomSemGet (&sem1, 0) == ATOM_OK)
{
/* Increment our count of finished threads */
finish_cnt++;
/* Check if all four threads have now posted sem1 */
if (finish_cnt == 4)
{
break;
}
}
}
/* Delete OS objects, test finished */
if (atomQueueDelete (&queue1) != ATOM_OK)
{
ATOMLOG (_STR("Delete failed\n"));
CRITICAL_START ();
g_failures++;
CRITICAL_END ();
}
if (atomSemDelete (&sem1) != ATOM_OK)
{
ATOMLOG (_STR("Delete failed\n"));
CRITICAL_START ();
g_failures++;
CRITICAL_END ();
}
}
/* Log final status */
if (g_failures == 0)
{
ATOMLOG (_STR("Pass\n"));
}
else
{
ATOMLOG (_STR("Fail(%d)\n"), g_failures);
}
/* Quit */
return g_failures;
}
/**
* \b test_thread_func
*
* Entry point for test thread. The same thread entry point is used for all
* four test threads.
*
* @param[in] data Unused (optional thread entry parameter)
*
* @return None
*/
static void test_thread_func (uint32_t data)
{
uint32_t loop_cnt;
uint8_t status;
uint8_t msg;
CRITICAL_STORE;
/* Run a Put/Get pair many times */
loop_cnt = NUM_TEST_LOOPS;
while (loop_cnt--)
{
/* Put a message in the queue */
msg = 0x66;
if ((status = atomQueuePut (&queue1, 0, &msg)) != ATOM_OK)
{
/* Error putting mutex, notify the status code */
ATOMLOG (_STR("P%d\n"), status);
CRITICAL_START ();
g_failures++;
CRITICAL_END ();
break;
}
/* Retrieve a messages from the queue */
if ((status = atomQueueGet (&queue1, 0, &msg)) != ATOM_OK)
{
/* Error getting queue msg, notify the status code */
ATOMLOG (_STR("G%d\n"), status);
CRITICAL_START ();
g_failures++;
CRITICAL_END ();
break;
}
}
/* Post sem1 to notify the main thread we're finished */
if (atomSemPut (&sem1) != ATOM_OK)
{
ATOMLOG (_STR("Sem1 putfail\n"));
CRITICAL_START ();
g_failures++;
CRITICAL_END ();
}
/* Loop forever */
while (1)
{
atomTimerDelay (SYSTEM_TICKS_PER_SEC);
}
}

343
tests/sem1.c Normal file
View File

@@ -0,0 +1,343 @@
/*
* Copyright (c) 2010, Kelvin Lawson. All rights reserved.
*
* 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. No personal names or organizations' names associated with the
* Atomthreads project may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT 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 PROJECT 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.
*/
#include "atom.h"
#include "atomsem.h"
#include "atomtests.h"
#include "atomuser.h"
/* Test OS objects */
static ATOM_SEM sem1, sem2;
static ATOM_TCB tcb1, tcb2;
static uint8_t test1_thread_stack[TEST_THREAD_STACK_SIZE];
static uint8_t test2_thread_stack[TEST_THREAD_STACK_SIZE];
/* Forward declarations */
static void test1_thread_func (uint32_t data);
static void test2_thread_func (uint32_t data);
/**
* \b test_start
*
* Start semaphore test.
*
* This test exercises the semaphore creation and deletion APIs, including
* waking threads blocking on a semaphore if the semaphore is deleted.
* Deletion wakeups are tested twice: once for a thread which is blocking
* with a timeout and once for a thread which is blocking with no timeout.
*
* @retval Number of failures
*/
uint32_t test_start (void)
{
int failures;
uint32_t i;
uint8_t status;
/* Default to zero failures */
failures = 0;
/* Test creation and deletion of semaphores: good values */
for (i = 0; i < 1000; i++)
{
if (atomSemCreate (&sem1, 0) == ATOM_OK)
{
if (atomSemDelete (&sem1) == ATOM_OK)
{
/* Success */
}
else
{
/* Fail */
ATOMLOG (_STR("Error deleting semaphore\n"));
failures++;
break;
}
}
else
{
/* Fail */
ATOMLOG (_STR("Error creating semaphore\n"));
failures++;
break;
}
}
/* Test creation and deletion of semaphores: creation checks */
if (atomSemCreate (NULL, 0) != ATOM_OK)
{
/* Success */
}
else
{
/* Fail */
ATOMLOG (_STR("Bad semaphore creation checks\n"));
failures++;
}
/* Test creation and deletion of semaphores: deletion checks */
if (atomSemDelete (NULL) != ATOM_OK)
{
/* Success */
}
else
{
/* Fail */
ATOMLOG (_STR("Bad semaphore deletion checks\n"));
failures++;
}
/* Test wakeup of threads on semaphore deletion (thread blocking with no timeout) */
if (atomSemCreate (&sem1, 0) != ATOM_OK)
{
ATOMLOG (_STR("Error creating test semaphore 1\n"));
failures++;
}
else if (atomSemCreate (&sem2, 0) != ATOM_OK)
{
ATOMLOG (_STR("Error creating test semaphore 2\n"));
failures++;
}
else if (atomThreadCreate(&tcb1, TEST_THREAD_PRIO, test1_thread_func, 0,
&test1_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK)
{
/* Fail */
ATOMLOG (_STR("Error creating test thread 1\n"));
failures++;
}
else
{
/*
* We have created two semaphores. sem1 is for the other thread
* to wait on, which we will delete from this thread. We want
* to see that the other thread is woken up if its semaphore
* is deleted. This is indicated through sem2 being posted
* back to us.
*/
/* Wait for the other thread to start blocking on sem1 */
if (atomTimerDelay(SYSTEM_TICKS_PER_SEC) != ATOM_OK)
{
ATOMLOG (_STR("Failed timer delay\n"));
failures++;
}
else
{
/* The other thread will be blocking on sem1 now, delete sem1 */
if (atomSemDelete(&sem1) != ATOM_OK)
{
ATOMLOG (_STR("Failed sem1 delete\n"));
failures++;
}
else
{
/* Sem1 deleted. The thread should now wake up and post sem2. */
if ((status = atomSemGet (&sem2, (5*SYSTEM_TICKS_PER_SEC))) != ATOM_OK)
{
ATOMLOG (_STR("Notify fail (%d)\n"), status);
failures++;
}
else
{
/* Success */
/* Clean up the last remaining semaphore */
if (atomSemDelete (&sem2) != ATOM_OK)
{
ATOMLOG (_STR("Failed sem2 delete\n"));
failures++;
}
}
}
}
}
/* Test wakeup of threads on semaphore deletion (thread blocking with timeout) */
if (atomSemCreate (&sem1, 0) != ATOM_OK)
{
ATOMLOG (_STR("Error creating test semaphore 1\n"));
failures++;
}
else if (atomSemCreate (&sem2, 0) != ATOM_OK)
{
ATOMLOG (_STR("Error creating test semaphore 2\n"));
failures++;
}
else if (atomThreadCreate(&tcb2, TEST_THREAD_PRIO, test2_thread_func, 0,
&test2_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK)
{
/* Fail */
ATOMLOG (_STR("Error creating test thread 2\n"));
failures++;
}
else
{
/*
* We have created two semaphores. sem1 is for the other thread
* to wait on, which we will delete from this thread. We want
* to see that the other thread is woken up if its semaphore
* is deleted. This is indicated through sem2 being posted
* back to us.
*/
/* Wait for the other thread to start blocking on sem1 */
if (atomTimerDelay(SYSTEM_TICKS_PER_SEC) != ATOM_OK)
{
ATOMLOG (_STR("Failed timer delay\n"));
failures++;
}
else
{
/* The other thread will be blocking on sem1 now, delete sem1 */
if (atomSemDelete(&sem1) != ATOM_OK)
{
ATOMLOG (_STR("Failed sem1 delete\n"));
failures++;
}
else
{
/* Sem1 deleted. The thread should now wake up and post sem2. */
if ((status = atomSemGet (&sem2, (5*SYSTEM_TICKS_PER_SEC))) != ATOM_OK)
{
ATOMLOG (_STR("Notify fail (%d)\n"), status);
failures++;
}
else
{
/* Success */
/* Clean up the last remaining semaphore */
if (atomSemDelete (&sem2) != ATOM_OK)
{
ATOMLOG (_STR("Failed sem2 delete\n"));
failures++;
}
}
}
}
}
/* Log final status */
if (failures == 0)
{
ATOMLOG (_STR("Pass\n"));
}
else
{
ATOMLOG (_STR("Fail(%d)\n"), failures);
}
/* Quit */
return failures;
}
/**
* \b test1_thread_func
*
* Entry point for test thread 1.
*
* @param[in] data Unused (optional thread entry parameter)
*
* @return None
*/
static void test1_thread_func (uint32_t data)
{
uint8_t status;
/*
* Wait on sem1 with no timeout. We are expecting to be woken up
* by the main thread while blocking.
*/
status = atomSemGet(&sem1, 0);
if (status != ATOM_ERR_DELETED)
{
ATOMLOG (_STR("Test1 thread woke without deletion (%d)\n"), status);
}
else
{
/* We were woken due to deletion as expected, post sem2 to notify success */
if ((status = atomSemPut(&sem2)) != ATOM_OK)
{
ATOMLOG (_STR("Error posting sem2 on wakeup (%d)\n"), status);
}
}
/* Wait forever */
while (1)
{
atomTimerDelay (SYSTEM_TICKS_PER_SEC);
}
}
/**
* \b test2_thread_func
*
* Entry point for test thread 2.
*
* @param[in] data Unused (optional thread entry parameter)
*
* @return None
*/
static void test2_thread_func (uint32_t data)
{
uint8_t status;
/*
* Wait on sem1 with timeout. We are expecting to be woken up
* by the main thread while blocking.
*/
status = atomSemGet(&sem1, (5 * SYSTEM_TICKS_PER_SEC));
if (status != ATOM_ERR_DELETED)
{
ATOMLOG (_STR("Test2 thread woke without deletion (%d)\n"), status);
}
else
{
/* We were woken due to deletion as expected, post sem2 to notify success */
if ((status = atomSemPut(&sem2)) != ATOM_OK)
{
ATOMLOG (_STR("Error posting sem2 on wakeup\n"));
}
}
/* Wait forever */
while (1)
{
atomTimerDelay (SYSTEM_TICKS_PER_SEC);
}
}

334
tests/sem2.c Normal file
View File

@@ -0,0 +1,334 @@
/*
* Copyright (c) 2010, Kelvin Lawson. All rights reserved.
*
* 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. No personal names or organizations' names associated with the
* Atomthreads project may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT 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 PROJECT 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.
*/
#include "atom.h"
#include "atomsem.h"
#include "atomtests.h"
#include "atomuser.h"
/* Test OS objects */
static ATOM_SEM sem1, sem2;
/* Forward declarations */
static void testCallback (POINTER cb_data);
/**
* \b test_start
*
* Start semaphore test.
*
* This test exercises the atomSemGet() and atomSemPut() APIs including
* forcing the various error indications which can be returned from the
* APIs to ensure that handling for these corner cases have been correctly
* implemented.
*
* @retval Number of failures
*/
uint32_t test_start (void)
{
int failures;
uint8_t status;
ATOM_TIMER timer_cb;
/* Default to zero failures */
failures = 0;
/* Test semaphore wait timeouts */
if (atomSemCreate (&sem1, 0) != ATOM_OK)
{
ATOMLOG (_STR("Error creating test semaphore 1\n"));
failures++;
}
else
{
/* Wait on semaphore with timeout */
if ((status = atomSemGet (&sem1, SYSTEM_TICKS_PER_SEC)) != ATOM_TIMEOUT)
{
ATOMLOG (_STR("Timo %d\n"), status);
failures++;
}
else
{
/* Test semaphore still operates correctly after timeout */
if (atomSemPut (&sem1) != ATOM_OK)
{
ATOMLOG (_STR("Put failed\n"));
failures++;
}
else
{
/* Count should now be 1 */
if (atomSemGet (&sem1, 0) != ATOM_OK)
{
ATOMLOG (_STR("Get failed\n"));
failures++;
}
else
{
/* Delete it, test finished */
if (atomSemDelete (&sem1) != ATOM_OK)
{
ATOMLOG (_STR("Delete failed\n"));
failures++;
}
}
}
}
}
/* Test semaphore blocks if count is zero */
if (atomSemCreate (&sem1, 0) != ATOM_OK)
{
ATOMLOG (_STR("Error creating test semaphore 1\n"));
failures++;
}
else
{
/* Wait on semaphore with timeout */
if ((status = atomSemGet (&sem1, SYSTEM_TICKS_PER_SEC)) != ATOM_TIMEOUT)
{
ATOMLOG (_STR("Timo %d\n"), status);
failures++;
}
else
{
/* Delete it, test finished */
if (atomSemDelete (&sem1) != ATOM_OK)
{
ATOMLOG (_STR("Delete failed\n"));
failures++;
}
}
}
/* Test semaphore does not block if count is one */
if (atomSemCreate (&sem1, 1) != ATOM_OK)
{
ATOMLOG (_STR("Error creating test semaphore 1\n"));
failures++;
}
else
{
/* Wait on semaphore with timeout */
if ((status = atomSemGet (&sem1, SYSTEM_TICKS_PER_SEC)) != ATOM_OK)
{
ATOMLOG (_STR("Get %d\n"), status);
failures++;
}
else
{
/* Delete it, test finished */
if (atomSemDelete (&sem1) != ATOM_OK)
{
ATOMLOG (_STR("Delete failed\n"));
failures++;
}
}
}
/* Test parameter checks */
if (atomSemGet (NULL, 0) != ATOM_ERR_PARAM)
{
ATOMLOG (_STR("Get param failed\n"));
failures++;
}
if (atomSemPut (NULL) != ATOM_ERR_PARAM)
{
ATOMLOG (_STR("Put param failed\n"));
failures++;
}
/* Test atomSemGet() cannot be called from interrupt context */
if (atomSemCreate (&sem1, 0) != ATOM_OK)
{
ATOMLOG (_STR("Error creating test sem1\n"));
failures++;
}
else if (atomSemCreate (&sem2, 0) != ATOM_OK)
{
ATOMLOG (_STR("Error creating test sem2\n"));
failures++;
}
else
{
/* Fill out the timer callback request structure */
timer_cb.cb_func = testCallback;
timer_cb.cb_data = NULL;
timer_cb.cb_ticks = SYSTEM_TICKS_PER_SEC;
/* Request the timer callback to run in one second */
if (atomTimerRegister (&timer_cb) != ATOM_OK)
{
ATOMLOG (_STR("Error registering timer\n"));
failures++;
}
/* Wait up to two seconds for sem2 to be posted indicating success */
else if (atomSemGet (&sem2, 2 * SYSTEM_TICKS_PER_SEC) != ATOM_OK)
{
ATOMLOG (_STR("Context check failed\n"));
failures++;
}
/* Delete the two test semaphores */
if (atomSemDelete (&sem1) != ATOM_OK)
{
ATOMLOG (_STR("Sem1 delete failed\n"));
failures++;
}
if (atomSemDelete (&sem2) != ATOM_OK)
{
ATOMLOG (_STR("Sem2 delete failed\n"));
failures++;
}
}
/* Test for ATOM_WOULDBLOCK */
if (atomSemCreate (&sem1, 0) != ATOM_OK)
{
ATOMLOG (_STR("Error creating test sem1\n"));
failures++;
}
else
{
/* Semaphore count is zero so will block */
if ((status = atomSemGet (&sem1, -1)) != ATOM_WOULDBLOCK)
{
ATOMLOG (_STR("Wouldblock err %d\n"), status);
failures++;
}
/* Delete the test semaphore */
if (atomSemDelete (&sem1) != ATOM_OK)
{
ATOMLOG (_STR("Sem1 delete failed\n"));
failures++;
}
}
/* Test no timeout */
if (atomSemCreate (&sem1, 0) != ATOM_OK)
{
ATOMLOG (_STR("Error creating test sem1\n"));
failures++;
}
else
{
/* Increment the semaphore */
if (atomSemPut (&sem1) != ATOM_OK)
{
ATOMLOG (_STR("Error on put\n"));
failures++;
}
/* Semaphore count is one so will not block */
if (atomSemGet (&sem1, -1) != ATOM_OK)
{
ATOMLOG (_STR("Error on get\n"));
failures++;
}
/* Delete the test semaphore */
if (atomSemDelete (&sem1) != ATOM_OK)
{
ATOMLOG (_STR("Sem1 delete failed\n"));
failures++;
}
}
/* Test for semaphore counter overflows with too many puts */
if (atomSemCreate (&sem1, 255) != ATOM_OK)
{
ATOMLOG (_STR("Error creating test sem1\n"));
failures++;
}
else
{
/* Increment the semaphore (expect this to overflow the count at 256) */
if (atomSemPut (&sem1) != ATOM_ERR_OVF)
{
ATOMLOG (_STR("Failed to detect overflow\n"));
failures++;
}
/* Delete the test semaphore */
if (atomSemDelete (&sem1) != ATOM_OK)
{
ATOMLOG (_STR("Sem1 delete failed\n"));
failures++;
}
}
/* Log final status */
if (failures == 0)
{
ATOMLOG (_STR("Pass\n"));
}
else
{
ATOMLOG (_STR("Fail(%d)\n"), failures);
}
/* Quit */
return failures;
}
/**
* \b testCallback
*
* Attempt an atomSemGet() on sem1 from interrupt context.
* Should receive an ATOM_ERR_CONTEXT error. Posts sem2 if successful.
*
* @param[in] cb_data Not used
*/
static void testCallback (POINTER cb_data)
{
/* Check the return value from atomSemGet() */
if (atomSemGet(&sem1, 0) == ATOM_ERR_CONTEXT)
{
/* Received the error we expected, post sem2 to notify success */
atomSemPut(&sem2);
}
else
{
/*
* Did not get the expected error, don't post sem2 and the test
* thread will time out, signifying an error.
*/
}
}

253
tests/sem3.c Normal file
View File

@@ -0,0 +1,253 @@
/*
* Copyright (c) 2010, Kelvin Lawson. All rights reserved.
*
* 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. No personal names or organizations' names associated with the
* Atomthreads project may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT 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 PROJECT 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.
*/
#include "atom.h"
#include "atomtests.h"
#include "atomsem.h"
/* Test OS objects */
static ATOM_SEM sem1;
static ATOM_TCB tcb1, tcb2, tcb3, tcb4;
static uint8_t test1_thread_stack[TEST_THREAD_STACK_SIZE];
static uint8_t test2_thread_stack[TEST_THREAD_STACK_SIZE];
static uint8_t test3_thread_stack[TEST_THREAD_STACK_SIZE];
static uint8_t test4_thread_stack[TEST_THREAD_STACK_SIZE];
/* Data updated by threads */
static volatile uint8_t wake_cnt;
static volatile uint8_t wake_order[4];
/* Forward declarations */
static void test_thread_func (uint32_t data);
/**
* \b test_start
*
* Start semaphore test.
*
* With multiple threads blocking on a single semaphore, this test confirms that
* they are woken in order when the semaphore is posted. The correct order for
* waking is that the higher priority threads are woken first, followed by the
* lower priority threads. Where multiple threads of the same priority are
* waiting, the threads are woken in FIFO order (the order in which they started
* waiting on the semaphore).
*
* To test this we create four threads which all wait on a single semaphore.
* One pair of threads are running at high priority, with the other pair at a
* lower priority:
*
* Thread 1: low prio thread A
* Thread 2: low prio thread B
* Thread 3: high prio thread A
* Thread 4: high prio thread B
*
* The threads are forced to start blocking on the same semaphore in the
* above order (the semaphore is initialised with count 0 to ensure any
* threads calling atomSemGet() will block).
*
* We expect to see them woken up in the following order:
* 3, 4, 1, 2
*
* This proves the multiple blocking thread ordering in terms of both
* the priority-queueing and same-priority-FIFO-queueing.
*
* @retval Number of failures
*/
uint32_t test_start (void)
{
int failures;
int i;
/* Default to zero failures */
failures = 0;
/* Create sem with count zero (so that all threads will block) */
if (atomSemCreate (&sem1, 0) != ATOM_OK)
{
ATOMLOG (_STR("Error creating test semaphore 1\n"));
failures++;
}
else
{
/* Create Thread 1 (lower priority thread A) */
if (atomThreadCreate(&tcb1, TEST_THREAD_PRIO+1, test_thread_func, 1,
&test1_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK)
{
/* Fail */
ATOMLOG (_STR("Error creating test thread\n"));
failures++;
}
/* Delay to ensure the thread will start blocking on the semaphore */
atomTimerDelay (SYSTEM_TICKS_PER_SEC/4);
/* Create Thread 2 (lower priority thread B) */
if (atomThreadCreate(&tcb2, TEST_THREAD_PRIO+1, test_thread_func, 2,
&test2_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK)
{
/* Fail */
ATOMLOG (_STR("Error creating test thread\n"));
failures++;
}
/* Delay to ensure the thread will start blocking on the semaphore */
atomTimerDelay (SYSTEM_TICKS_PER_SEC/4);
/* Create Thread 3 (higher priority thread A) */
if (atomThreadCreate(&tcb3, TEST_THREAD_PRIO, test_thread_func, 3,
&test3_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK)
{
/* Fail */
ATOMLOG (_STR("Error creating test thread\n"));
failures++;
}
/* Delay to ensure the thread will start blocking on the semaphore */
atomTimerDelay (SYSTEM_TICKS_PER_SEC/4);
/* Create Thread 4 (higher priority thread B) */
if (atomThreadCreate(&tcb4, TEST_THREAD_PRIO, test_thread_func, 4,
&test4_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK)
{
/* Fail */
ATOMLOG (_STR("Error creating test thread\n"));
failures++;
}
/* Delay to ensure the thread will start blocking on the semaphore */
atomTimerDelay (SYSTEM_TICKS_PER_SEC/4);
/* All four threads will now be blocking on sem1 */
/*
* Initialise wake count, used by threads to determine
* what order they were woken in.
*/
wake_cnt = 0;
/*
* Wake the four threads up in order, leaving some time between
* each wake up for them to deal with global data in a
* thread-safe fashion.
*/
for (i = 0; i < 4; i++)
{
/* Post semaphore to wake one of the threads up */
if (atomSemPut (&sem1) != ATOM_OK)
{
ATOMLOG (_STR("Post fail\n"));
failures++;
}
/* Sleep to give the thread time to manipulate global data */
atomTimerDelay (SYSTEM_TICKS_PER_SEC / 4);
}
/* All four threads now woken up, check they woke in correct order */
if ((wake_order[0] != 3) || (wake_order[1] != 4)
|| (wake_order[2] != 1) || (wake_order[3] != 2))
{
ATOMLOG (_STR("Bad order %d,%d,%d,%d\n"),
wake_order[0], wake_order[1], wake_order[2], wake_order[3]);
failures++;
}
/* Delete semaphore, test finished */
if (atomSemDelete (&sem1) != ATOM_OK)
{
ATOMLOG (_STR("Delete failed\n"));
failures++;
}
}
/* Log final status */
if (failures == 0)
{
ATOMLOG (_STR("Pass\n"));
}
else
{
ATOMLOG (_STR("Fail(%d)\n"), failures);
}
/* Quit */
return failures;
}
/**
* \b test_thread_func
*
* Entry point for test thread. The same thread entry point is used for all
* four test threads, with the thread number/ID (1-4) passed as the entry
* point parameter.
*
* @param[in] data Thread number (1,2,3,4)
*
* @return None
*/
static void test_thread_func (uint32_t data)
{
uint8_t thread_id;
/* Thread ID is passed through the function parameter */
thread_id = (uint8_t)data;
/*
* Wait for sem1 to be posted. At creation of all test threads
* the semaphore count is zero, so all four threads will block
* here.
*/
if (atomSemGet (&sem1, 0) != ATOM_OK)
{
ATOMLOG (_STR("Thread sem fail\n"));
}
else
{
/*
* Store our thread ID in the array using the current
* wake_cnt order. The threads are deliberately woken up
* some time apart to ensure that no protection is required
* on this global data.
*/
wake_order[wake_cnt++] = thread_id;
}
/* Loop forever */
while (1)
{
atomTimerDelay (SYSTEM_TICKS_PER_SEC);
}
}

254
tests/sem4.c Normal file
View File

@@ -0,0 +1,254 @@
/*
* Copyright (c) 2010, Kelvin Lawson. All rights reserved.
*
* 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. No personal names or organizations' names associated with the
* Atomthreads project may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT 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 PROJECT 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.
*/
#include "atom.h"
#include "atomtests.h"
#include "atomsem.h"
/* Number of test loops for stress-test */
#define NUM_TEST_LOOPS 10000
/* Test OS objects */
static ATOM_SEM sem1, sem2;
static ATOM_TCB tcb1, tcb2, tcb3, tcb4;
static uint8_t test1_thread_stack[TEST_THREAD_STACK_SIZE];
static uint8_t test2_thread_stack[TEST_THREAD_STACK_SIZE];
static uint8_t test3_thread_stack[TEST_THREAD_STACK_SIZE];
static uint8_t test4_thread_stack[TEST_THREAD_STACK_SIZE];
/*
* Global failure count (can be updated by test threads but is
* protected by an interrupt lockout).
*/
static volatile int g_failures;
/* Forward declarations */
static void test_thread_func (uint32_t data);
/**
* \b test_start
*
* Start semaphore test.
*
* Stress-tests semaphore Get and Put operations. Four threads are created which are
* continually Getting and Putting the same semaphore, with no time delays between
* each Get/Put. A single semaphore is used, which is initially created with a count
* of two, meaning that at any one time there should be threads blocking and not
* blocking. This ensures that the stress-test covers multiple threads accessing the
* semaphore APIs simultaneously, as well as usage of the APIs while other threads
* are blocking on the semaphore.
*
* @retval Number of g_failures
*/
uint32_t test_start (void)
{
CRITICAL_STORE;
int finish_cnt;
/* Default to zero g_failures */
g_failures = 0;
/* Create sem to stress with count two */
if (atomSemCreate (&sem1, 2) != ATOM_OK)
{
ATOMLOG (_STR("Error creating test semaphore 1\n"));
g_failures++;
}
/* Create sem to receive thread-finished notification */
else if (atomSemCreate (&sem2, 0) != ATOM_OK)
{
ATOMLOG (_STR("Error creating test semaphore 1\n"));
g_failures++;
}
else
{
/* Create Thread 1 */
if (atomThreadCreate(&tcb1, TEST_THREAD_PRIO, test_thread_func, 1,
&test1_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK)
{
/* Fail */
ATOMLOG (_STR("Error creating test thread\n"));
CRITICAL_START ();
g_failures++;
CRITICAL_END ();
}
/* Create Thread 2 */
if (atomThreadCreate(&tcb2, TEST_THREAD_PRIO, test_thread_func, 2,
&test2_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK)
{
/* Fail */
ATOMLOG (_STR("Error creating test thread\n"));
CRITICAL_START ();
g_failures++;
CRITICAL_END ();
}
/* Create Thread 3 */
if (atomThreadCreate(&tcb3, TEST_THREAD_PRIO, test_thread_func, 3,
&test3_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK)
{
/* Fail */
ATOMLOG (_STR("Error creating test thread\n"));
CRITICAL_START ();
g_failures++;
CRITICAL_END ();
}
/* Create Thread 4 */
if (atomThreadCreate(&tcb4, TEST_THREAD_PRIO, test_thread_func, 4,
&test4_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK)
{
/* Fail */
ATOMLOG (_STR("Error creating test thread\n"));
CRITICAL_START ();
g_failures++;
CRITICAL_END ();
}
/*
* All four threads will now be performing Gets/Puts on sem1.
* When they have finished they will post sem2, so we wait
* until sem2 is posted four times.
*/
finish_cnt = 0;
while (1)
{
/*
* Attempt to Get sem2. When we have managed to get
* the semaphore four times, it must have been posted
* by all four threads.
*/
if (atomSemGet (&sem2, 0) == ATOM_OK)
{
/* Increment our count of finished threads */
finish_cnt++;
/* Check if all four threads have now posted sem2 */
if (finish_cnt == 4)
{
break;
}
}
}
/* Delete semaphores, test finished */
if (atomSemDelete (&sem1) != ATOM_OK)
{
ATOMLOG (_STR("Delete failed\n"));
CRITICAL_START ();
g_failures++;
CRITICAL_END ();
}
if (atomSemDelete (&sem2) != ATOM_OK)
{
ATOMLOG (_STR("Delete failed\n"));
CRITICAL_START ();
g_failures++;
CRITICAL_END ();
}
}
/* Log final status */
if (g_failures == 0)
{
ATOMLOG (_STR("Pass\n"));
}
else
{
ATOMLOG (_STR("Fail(%d)\n"), g_failures);
}
/* Quit */
return g_failures;
}
/**
* \b test_thread_func
*
* Entry point for test thread. The same thread entry point is used for all
* four test threads.
*
* @param[in] data Unused (optional thread entry parameter)
*
* @return None
*/
static void test_thread_func (uint32_t data)
{
uint32_t loop_cnt;
uint8_t status;
CRITICAL_STORE;
/* Run a Get/Put pair many times */
loop_cnt = NUM_TEST_LOOPS;
while (loop_cnt--)
{
if ((status = atomSemGet (&sem1, 0)) != ATOM_OK)
{
/* Error getting semaphore, notify the status code */
ATOMLOG (_STR("G%d\n"), status);
CRITICAL_START ();
g_failures++;
CRITICAL_END ();
break;
}
else if ((status = atomSemPut (&sem1)) != ATOM_OK)
{
/* Error putting semaphore, notify the status code */
ATOMLOG (_STR("P%d\n"), status);
CRITICAL_START ();
g_failures++;
CRITICAL_END ();
break;
}
}
/* Post sem2 to notify the main thread we're finished */
if (atomSemPut (&sem2) != ATOM_OK)
{
ATOMLOG (_STR("Sem2 putfail\n"));
CRITICAL_START ();
g_failures++;
CRITICAL_END ();
}
/* Loop forever */
while (1)
{
atomTimerDelay (SYSTEM_TICKS_PER_SEC);
}
}

195
tests/sem5.c Normal file
View File

@@ -0,0 +1,195 @@
/*
* Copyright (c) 2010, Kelvin Lawson. All rights reserved.
*
* 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. No personal names or organizations' names associated with the
* Atomthreads project may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT 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 PROJECT 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.
*/
#include "atom.h"
#include "atomtests.h"
#include "atomsem.h"
/* Test OS objects */
static ATOM_SEM sem1, sem2;
static ATOM_TCB tcb1;
static uint8_t test1_thread_stack[TEST_THREAD_STACK_SIZE];
/* Forward declarations */
static void test_thread_func (uint32_t data);
/**
* \b test_start
*
* Start semaphore test.
*
* This tests basic synchronisation between threads using a semaphore.
*
* A second thread is created, which blocks on a semaphore until posted
* by the main thread. Testing all aspects of this transaction proves
* the basic usage of semaphores as a synchronisation mechanism
* between threads.
*
* @retval Number of failures
*/
uint32_t test_start (void)
{
int failures;
/* Default to zero failures */
failures = 0;
/* Create sem with count zero for second thread to block on */
if (atomSemCreate (&sem1, 0) != ATOM_OK)
{
ATOMLOG (_STR("Error creating test semaphore 1\n"));
failures++;
}
/* Create sem to receive test-passed notification */
else if (atomSemCreate (&sem2, 0) != ATOM_OK)
{
ATOMLOG (_STR("Error creating test semaphore 1\n"));
failures++;
}
else
{
/* Create second thread */
if (atomThreadCreate(&tcb1, TEST_THREAD_PRIO, test_thread_func, 1,
&test1_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK)
{
/* Fail */
ATOMLOG (_STR("Error creating test thread\n"));
failures++;
}
/*
* The second thread has now been created and should block on
* sem1 until we increment it to 1 (sem1 was created with a
* count of zero). Once the second thread has stopped blocking
* on sem1, it will post sem2 to notify us that it has finished.
*
* We can test the count of sem2 before and after posting sem1
* to ensure that it was the posting of sem1 which caused the
* second thread to wake up.
*/
/*
* Wait a little while, then check the second thread hasn't
* already posted sem2, i.e. did not wait until sem1 was posted.
*/
atomTimerDelay (SYSTEM_TICKS_PER_SEC/4);
if (atomSemGet (&sem2, -1) != ATOM_WOULDBLOCK)
{
ATOMLOG (_STR("Did not wait\n"));
failures++;
}
/* Post sem1 to stop the second thread blocking */
else if (atomSemPut (&sem1) != ATOM_OK)
{
ATOMLOG (_STR("Put fail\n"));
failures++;
}
/* Now check that the second thread has woken up and posted sem2 */
else
{
/* Give the thread some time to wake up and post sem2 */
atomTimerDelay (SYSTEM_TICKS_PER_SEC/4);
/* Check that the second thread has now woken and posted sem2 */
if (atomSemGet (&sem2, -1) != ATOM_OK)
{
ATOMLOG (_STR("Sem2 not posted\n"));
failures++;
}
}
/* Delete semaphores, test finished */
if (atomSemDelete (&sem1) != ATOM_OK)
{
ATOMLOG (_STR("Delete failed\n"));
failures++;
}
if (atomSemDelete (&sem2) != ATOM_OK)
{
ATOMLOG (_STR("Delete failed\n"));
failures++;
}
}
/* Log final status */
if (failures == 0)
{
ATOMLOG (_STR("Pass\n"));
}
else
{
ATOMLOG (_STR("Fail(%d)\n"), failures);
}
/* Quit */
return failures;
}
/**
* \b test_thread_func
*
* Entry point for test thread.
*
* @param[in] data Unused (optional thread entry parameter)
*
* @return None
*/
static void test_thread_func (uint32_t data)
{
uint8_t status;
/* Block on sem1. Main thread will post when we should wake up. */
if ((status = atomSemGet (&sem1, 0)) != ATOM_OK)
{
/* Error getting semaphore, notify the status code */
ATOMLOG (_STR("G%d\n"), status);
}
/* Post sem2 to notify that we received the sem1 notification */
else if ((status = atomSemPut (&sem2)) != ATOM_OK)
{
/* Error putting semaphore, notify the status code */
ATOMLOG (_STR("P%d\n"), status);
}
/* Loop forever */
while (1)
{
atomTimerDelay (SYSTEM_TICKS_PER_SEC);
}
}

201
tests/sem6.c Normal file
View File

@@ -0,0 +1,201 @@
/*
* Copyright (c) 2010, Kelvin Lawson. All rights reserved.
*
* 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. No personal names or organizations' names associated with the
* Atomthreads project may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT 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 PROJECT 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.
*/
#include "atom.h"
#include "atomtests.h"
#include "atomsem.h"
/* Semaphore count */
#define INITIAL_SEM_COUNT 10
/* Test OS objects */
static ATOM_SEM sem1, sem2;
static ATOM_TCB tcb1;
static uint8_t test1_thread_stack[TEST_THREAD_STACK_SIZE];
/* Forward declarations */
static void test_thread_func (uint32_t data);
/**
* \b test_start
*
* Start semaphore test.
*
* This tests basic counting semaphore operation between two threads.
*
* A semaphore is created with a count of 10. A second thread then
* ensures that it can decrement the semaphore 10 times before
* it can no longer be decremented.
*
* @retval Number of failures
*/
uint32_t test_start (void)
{
int failures;
/* Default to zero failures */
failures = 0;
/* Create sem with count ten for second thread to decrement */
if (atomSemCreate (&sem1, INITIAL_SEM_COUNT) != ATOM_OK)
{
ATOMLOG (_STR("Error creating test semaphore 1\n"));
failures++;
}
/* Create sem to receive test-passed notification */
else if (atomSemCreate (&sem2, 0) != ATOM_OK)
{
ATOMLOG (_STR("Error creating test semaphore 1\n"));
failures++;
}
else
{
/* Check that sem2 doesn't already have a positive count */
if (atomSemGet (&sem2, -1) != ATOM_WOULDBLOCK)
{
ATOMLOG (_STR("Sem2 already put\n"));
failures++;
}
/* Create second thread */
else if (atomThreadCreate(&tcb1, TEST_THREAD_PRIO, test_thread_func, 1,
&test1_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK)
{
/* Fail */
ATOMLOG (_STR("Error creating test thread\n"));
failures++;
}
/*
* The second thread has now been created and will attempt to
* decrement sem1 ten times, then finally check that it cannot
* decrement it any further. If this passes then the second
* thread will post sem2 to notify us that the test has passed.
*/
else
{
/* Give the second thread one second to post sem2 */
if (atomSemGet (&sem2, SYSTEM_TICKS_PER_SEC) != ATOM_OK)
{
ATOMLOG (_STR("Sem2 not posted\n"));
failures++;
}
}
/* Delete semaphores, test finished */
if (atomSemDelete (&sem1) != ATOM_OK)
{
ATOMLOG (_STR("Delete failed\n"));
failures++;
}
if (atomSemDelete (&sem2) != ATOM_OK)
{
ATOMLOG (_STR("Delete failed\n"));
failures++;
}
}
/* Log final status */
if (failures == 0)
{
ATOMLOG (_STR("Pass\n"));
}
else
{
ATOMLOG (_STR("Fail(%d)\n"), failures);
}
/* Quit */
return failures;
}
/**
* \b test_thread_func
*
* Entry point for test thread.
*
* @param[in] data Unused (optional thread entry parameter)
*
* @return None
*/
static void test_thread_func (uint32_t data)
{
uint8_t status;
int count;
int failures;
/*
* Attempt to decrement sem1 ten times, which should happen immediately
* each time.
*/
failures = 0;
count = INITIAL_SEM_COUNT;
while (count--)
{
/* Decrement sem1 */
if ((status = atomSemGet (&sem1, -1)) != ATOM_OK)
{
/* Error decrementing semaphore, notify the status code */
ATOMLOG (_STR("G%d\n"), status);
failures++;
}
}
/* Check above stage was successful */
if (failures == 0)
{
/* Sem1 should now have a count of zero, and not allow a decrement */
if ((status = atomSemGet (&sem1, -1)) != ATOM_WOULDBLOCK)
{
/* Error getting semaphore, notify the status code */
ATOMLOG (_STR("W%d\n"), status);
}
/* Post sem2 to notify that the test passed */
else if ((status = atomSemPut (&sem2)) != ATOM_OK)
{
/* Error putting semaphore, notify the status code */
ATOMLOG (_STR("P%d\n"), status);
}
}
/* Loop forever */
while (1)
{
atomTimerDelay (SYSTEM_TICKS_PER_SEC);
}
}

278
tests/sem7.c Normal file
View File

@@ -0,0 +1,278 @@
/*
* Copyright (c) 2010, Kelvin Lawson. All rights reserved.
*
* 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. No personal names or organizations' names associated with the
* Atomthreads project may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT 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 PROJECT 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.
*/
#include "atom.h"
#include "atomtests.h"
#include "atomsem.h"
/* Test OS objects */
static ATOM_SEM sem1;
static ATOM_TCB tcb1;
static uint8_t test1_thread_stack[TEST_THREAD_STACK_SIZE];
/* Global shared data protected by mutex */
static volatile int shared_data;
/* Forward declarations */
static void test_thread_func (uint32_t data);
/**
* \b test_start
*
* Start semaphore test.
*
* This tests usage of a semaphore for basic mutual exclusion type
* operation. Note that Atomthreads has a more fully-featured real
* mutex implementation in the mutex module.
*
* The semaphore sem1 is initialised with a count of 1. Whichever
* thread holds this semaphore can then modify the global variable
* "shared_data".
*
* The main thread first takes the "mutex" sem1, then creates a
* second thread. The second thread should block on the sem1 mutex
* until the main thread releases it. The test checks that the
* global "shared_data" is not modified by the second thread
* until the main thread releases the mutex.
*
* @retval Number of failures
*/
uint32_t test_start (void)
{
int failures;
int i;
/* Default to zero failures */
failures = 0;
/* Create sem with count one for mutex purposes */
if (atomSemCreate (&sem1, 1) != ATOM_OK)
{
ATOMLOG (_STR("Error creating test semaphore 1\n"));
failures++;
}
else
{
/* Initialise the shared_data to zero */
shared_data = 0;
/* Take the mutex to ensure only this thread can modify shared_data */
if (atomSemGet (&sem1, 0) != ATOM_OK)
{
ATOMLOG (_STR("Error taking mutex\n"));
failures++;
}
/* Create second thread */
else if (atomThreadCreate(&tcb1, TEST_THREAD_PRIO, test_thread_func, 1,
&test1_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK)
{
/* Fail */
ATOMLOG (_STR("Error creating test thread\n"));
failures++;
}
/*
* The second thread has now been created and should block on
* the "mutex" sem1 (which now has count zero) until we release
* it. We wait a while and check that shared_data has not been
* modified.
*/
for (i = 0; i < 4; i++)
{
/*
* Sleep for a while to give the second thread a chance to
* modify shared_data, thought it shouldn't until we
* release the mutex.
*/
atomTimerDelay (SYSTEM_TICKS_PER_SEC/4);
/* Check shared data. The second thread always sets it to one. */
if (shared_data != 0)
{
ATOMLOG (_STR("Shared data modified\n"));
failures++;
break;
}
}
/* Check successful so far */
if (failures == 0)
{
/*
* Release the mutex, which will allow the second thread to
* wake and start modifying shared_data.
*/
if (atomSemPut (&sem1) != ATOM_OK)
{
ATOMLOG (_STR("Failed release\n"));
failures++;
}
/*
* Wait a little while then check that shared_data has
* been modified.
*/
atomTimerDelay (SYSTEM_TICKS_PER_SEC/4);
if (shared_data != 1)
{
ATOMLOG (_STR("Expected modify\n"));
failures++;
}
/*
* Release and take the mutex again a few times to ensure
* that the mutex continues to protect shared_data.
*/
for (i = 0; i < 4; i++)
{
/*
* Take the mutex again, to prevent second thread accessing
* shared_data.
*/
if (atomSemGet (&sem1, SYSTEM_TICKS_PER_SEC) != ATOM_OK)
{
ATOMLOG (_STR("Retake %d\n"), i);
failures++;
break;
}
else
{
/*
* Set shared_data to 0 and wait to ensure that the
* second thread doesn't modify it while we have the
* mutex again.
*/
shared_data = 0;
/* Wait a while to give second thread potential to run */
atomTimerDelay(SYSTEM_TICKS_PER_SEC/4);
/*
* Check that shared_data has not been modified while we
* own the mutex.
*/
if (shared_data != 0)
{
/* Thread is still modifying the data */
ATOMLOG (_STR("Still modifying\n"));
failures++;
break;
}
/*
* Release the mutex, which will allow the second thread to
* wake and start modifying shared_data again.
*/
if (atomSemPut (&sem1) != ATOM_OK)
{
ATOMLOG (_STR("Failed release\n"));
failures++;
}
}
}
}
/* Delete semaphore, test finished */
if (atomSemDelete (&sem1) != ATOM_OK)
{
ATOMLOG (_STR("Delete failed\n"));
failures++;
}
}
/* Log final status */
if (failures == 0)
{
ATOMLOG (_STR("Pass\n"));
}
else
{
ATOMLOG (_STR("Fail(%d)\n"), failures);
}
/* Quit */
return failures;
}
/**
* \b test_thread_func
*
* Entry point for test thread.
*
* @param[in] data Unused (optional thread entry parameter)
*
* @return None
*/
static void test_thread_func (uint32_t data)
{
uint8_t status;
/* Repeatedly attempt to get the mutex and set shared_data to 1 */
while (1)
{
/* Block on the mutex sem1 */
if ((status = atomSemGet (&sem1, 0)) != ATOM_OK)
{
/* Error getting semaphore, notify the status code */
ATOMLOG (_STR("G%d\n"), status);
break;
}
/* Got the mutex */
else
{
/* Set shared_data to signify that we think we have the mutex */
shared_data = 1;
/* Release the mutex allowing the main thread to take it again */
if ((status = atomSemPut (&sem1)) != ATOM_OK)
{
/* Error putting semaphore, notify the status code */
ATOMLOG (_STR("P%d\n"), status);
break;
}
}
}
/* Loop forever - we only reach here on error */
while (1)
{
atomTimerDelay (SYSTEM_TICKS_PER_SEC);
}
}

292
tests/sem8.c Normal file
View File

@@ -0,0 +1,292 @@
/*
* Copyright (c) 2010, Kelvin Lawson. All rights reserved.
*
* 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. No personal names or organizations' names associated with the
* Atomthreads project may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT 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 PROJECT 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.
*/
#include "atom.h"
#include "atomtests.h"
#include "atomsem.h"
/* Test OS objects */
static ATOM_SEM sem1;
static ATOM_TCB tcb1, tcb2, tcb3;
static uint8_t test1_thread_stack[TEST_THREAD_STACK_SIZE];
static uint8_t test2_thread_stack[TEST_THREAD_STACK_SIZE];
static uint8_t test3_thread_stack[TEST_THREAD_STACK_SIZE];
/* Test running flag */
static volatile int test_running;
/* Forward declarations */
static void test_thread_func (uint32_t data);
static void testCallback (POINTER cb_data);
/**
* \b test_start
*
* Start semaphore test.
*
* This stress-tests atomSemGet()/atomSemPut() with a single thread
* continually calling atomSemGet() and several contexts continually
* calling atomSemPut(). This stresses in particular the atomSemPut()
* API, with three threads at different priorities posting
* simultaneously, as well as a timer callback posting it from
* interrupt context. In all cases the same semaphore is posted.
*
* This tests the thread-safety and interrupt-safety of the semaphore
* APIs, and particularly the atomSemPut() function.
*
* @retval Number of failures
*/
uint32_t test_start (void)
{
int failures;
uint32_t end_time;
ATOM_TIMER timer_cb;
/* Default to zero failures */
failures = 0;
/* Create sem with count of zero */
if (atomSemCreate (&sem1, 0) != ATOM_OK)
{
ATOMLOG (_STR("Error creating test semaphore 1\n"));
failures++;
}
else
{
/* Set the test running flag */
test_running = TRUE;
/*
* Fill out a timer callback request structure. Pass the timer
* structure itself so that the callback can requeue the request.
*/
timer_cb.cb_func = testCallback;
timer_cb.cb_data = &timer_cb;
timer_cb.cb_ticks = 1;
/*
* Request a timer callback to run in one tick's time. The callback
* will automatically queue another so that this happens repeatedly
* until the test is flagged as finished.
*/
if (atomTimerRegister (&timer_cb) != ATOM_OK)
{
ATOMLOG (_STR("Error registering timer\n"));
failures++;
}
/* Create thread 1: Higher priority than main thread so should sleep */
else if (atomThreadCreate(&tcb1, TEST_THREAD_PRIO - 1, test_thread_func, 1,
&test1_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK)
{
/* Fail */
ATOMLOG (_STR("Error creating test thread 1\n"));
failures++;
}
/* Create thread 2: Same priority as main thread so should not sleep */
else if (atomThreadCreate(&tcb2, TEST_THREAD_PRIO, test_thread_func, 0,
&test2_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK)
{
/* Fail */
ATOMLOG (_STR("Error creating test thread 2\n"));
failures++;
}
/* Create thread 3: Same priority as main thread so should not sleep */
else if (atomThreadCreate(&tcb3, TEST_THREAD_PRIO + 1, test_thread_func, 0,
&test3_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK)
{
/* Fail */
ATOMLOG (_STR("Error creating test thread 3\n"));
failures++;
}
/* The test threads have now all been created */
else
{
/*
* Continually decrement the semaphore while the test threads
* and timer callbacks are continually incrementing it. The
* test finishes after this runs without error for 5 seconds.
*/
end_time = atomTimeGet() + (5 * SYSTEM_TICKS_PER_SEC);
while (atomTimeGet() < end_time)
{
/* Decrement the semaphore */
if (atomSemGet (&sem1, SYSTEM_TICKS_PER_SEC) != ATOM_OK)
{
ATOMLOG (_STR("SemGet\n"));
failures++;
break;
}
}
/* Test finished, stop the other threads and timer callbacks */
test_running = FALSE;
/*
* Wait before finishing: a timer callback could be due
* shortly, and we allocated the timer structure off the
* local call stack.
*/
atomTimerDelay(2);
}
/* Delete semaphores, test finished */
if (atomSemDelete (&sem1) != ATOM_OK)
{
ATOMLOG (_STR("Delete failed\n"));
failures++;
}
}
/* Log final status */
if (failures == 0)
{
ATOMLOG (_STR("Pass\n"));
}
else
{
ATOMLOG (_STR("Fail(%d)\n"), failures);
}
/* Quit */
return failures;
}
/**
* \b test_thread_func
*
* Entry point for test thread.
*
* @param[in] data sleep_flag passed through here
*
* @return None
*/
static void test_thread_func (uint32_t data)
{
uint8_t status;
int sleep_flag, count, failures;
/* Were we requested to sleep occasionally? */
sleep_flag = (int)data;
/* Run until the main thread sets the finish flag or we get an error */
failures = 0;
while ((test_running == TRUE) && (failures == 0))
{
/* Post the semaphore 50 times */
count = 50;
while (count--)
{
/*
* Post the semaphore. Allow overflows as these are likely
* to occur when so many threads are posting the same
* semaphore continually.
*/
status = atomSemPut (&sem1);
if ((status != ATOM_OK) && (status != ATOM_ERR_OVF))
{
ATOMLOG (_STR("Put\n"));
failures++;
break;
}
}
/*
* If requested to do so, sleep for a tick. This only happens on threads which
* are higher priority than the main test thread, and is necessary to allow
* the main thread to actually run. For better stress-testing, same or lower
* priority threads do not sleep.
*/
if (sleep_flag)
{
atomTimerDelay (1);
}
}
/* Loop forever */
while (1)
{
atomTimerDelay (SYSTEM_TICKS_PER_SEC);
}
}
/**
* \b testCallback
*
* Post the semaphore from interrupt context. This will be occurring while
* atomSemPut() calls for the other threads are in progress, because it is
* called by the system tick ISR. This tests that the APIs lockout
* interrupts where necessary.
*
* Automatically requeues itself for one tick in the future, so this
* continually fires until the finish flag is set.
*
* @param[in] cb_data Pointer to the original ATOM_TIMER structure
*/
static void testCallback (POINTER cb_data)
{
ATOM_TIMER *ptimer;
uint8_t status;
/* Pull out the original timer request */
ptimer = (ATOM_TIMER *)cb_data;
/* Post sem1 */
status = atomSemPut (&sem1);
if ((status != ATOM_OK) && (status != ATOM_ERR_OVF))
{
/* Error */
}
/* Enqueue another timer callback in one tick's time */
if (test_running == TRUE)
{
/* Update the callback time and requeue */
ptimer->cb_ticks = 1;
if (atomTimerRegister (ptimer) != ATOM_OK)
{
}
}
else
{
/* Test finished, no more will be queued */
}
}

193
tests/sem9.c Normal file
View File

@@ -0,0 +1,193 @@
/*
* Copyright (c) 2010, Kelvin Lawson. All rights reserved.
*
* 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. No personal names or organizations' names associated with the
* Atomthreads project may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT 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 PROJECT 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.
*/
#include "atom.h"
#include "atomsem.h"
#include "atomtests.h"
#include "atomuser.h"
/* Test OS objects */
static ATOM_SEM sem1;
static ATOM_TCB tcb1, tcb2, tcb3;
static uint8_t test1_thread_stack[TEST_THREAD_STACK_SIZE];
static uint8_t test2_thread_stack[TEST_THREAD_STACK_SIZE];
static uint8_t test3_thread_stack[TEST_THREAD_STACK_SIZE];
/* Test results */
static volatile int pass_flag[3];
/* Forward declarations */
static void test_thread_func (uint32_t data);
/**
* \b test_start
*
* Start semaphore test.
*
* This test verifies the semaphore deletion API, by deleting a semaphore
* on which multiple threads are blocking, and checking that all three
* are woken up with an appropriate error code.
*
* @retval Number of failures
*/
uint32_t test_start (void)
{
int failures;
int i;
/* Default to zero failures */
failures = 0;
/* Initialise pass status for all three threads to FALSE */
for (i = 0; i < 3; i++)
{
pass_flag[i] = FALSE;
}
/* Test wakeup of three threads on semaphore deletion */
if (atomSemCreate (&sem1, 0) != ATOM_OK)
{
ATOMLOG (_STR("Error creating test semaphore 1\n"));
failures++;
}
else
{
/* Create test thread 1 */
if (atomThreadCreate(&tcb1, TEST_THREAD_PRIO, test_thread_func, 0,
&test1_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK)
{
/* Fail */
ATOMLOG (_STR("Error creating test thread 1\n"));
failures++;
}
/* Create test thread 2 */
else if (atomThreadCreate(&tcb2, TEST_THREAD_PRIO, test_thread_func, 1,
&test2_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK)
{
/* Fail */
ATOMLOG (_STR("Error creating test thread 2\n"));
failures++;
}
/* Create test thread 3 */
else if (atomThreadCreate(&tcb3, TEST_THREAD_PRIO, test_thread_func, 2,
&test3_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK)
{
/* Fail */
ATOMLOG (_STR("Error creating test thread 3\n"));
failures++;
}
/* Test threads now created */
else
{
/* Wait a while for threads to start blocking on sem1 */
atomTimerDelay (SYSTEM_TICKS_PER_SEC/4);
/* Delete sem1 now that all three threads should be blocking */
if (atomSemDelete (&sem1) != ATOM_OK)
{
ATOMLOG (_STR("Delete fail\n"));
failures++;
}
else
{
/* Wait a while for all three threads to wake up */
atomTimerDelay (SYSTEM_TICKS_PER_SEC/4);
/* Check that all three threads have passed */
if ((pass_flag[0] != TRUE) || (pass_flag[1] != TRUE) || (pass_flag[2] != TRUE))
{
ATOMLOG (_STR("Thread fail\n"));
failures++;
}
}
}
}
/* Log final status */
if (failures == 0)
{
ATOMLOG (_STR("Pass\n"));
}
else
{
ATOMLOG (_STR("Fail(%d)\n"), failures);
}
/* Quit */
return failures;
}
/**
* \b test_thread_func
*
* Entry point for test threads.
*
* @param[in] data Thread ID (0-2)
*
* @return None
*/
static void test_thread_func (uint32_t data)
{
uint8_t status;
int thread_id;
/* Pull out the passed thread ID */
thread_id = (int)data;
/*
* Wait on sem1 with timeout. We are expecting to be woken up
* by the main thread while blocking.
*/
status = atomSemGet(&sem1, (5 * SYSTEM_TICKS_PER_SEC));
if (status != ATOM_ERR_DELETED)
{
ATOMLOG (_STR("Test1 thread woke without deletion (%d)\n"), status);
}
else
{
/* We were woken due to deletion as expected, set pass_flag to notify success */
pass_flag[thread_id] = TRUE;
}
/* Wait forever */
while (1)
{
atomTimerDelay (SYSTEM_TICKS_PER_SEC);
}
}

62
tests/test-template.c Normal file
View File

@@ -0,0 +1,62 @@
/*
* Copyright (c) 2010, Kelvin Lawson. All rights reserved.
*
* 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. No personal names or organizations' names associated with the
* Atomthreads project may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT 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 PROJECT 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.
*/
#include "atom.h"
#include "atomtests.h"
/**
* \b test_start
*
* Start test.
*
* @retval Number of failures
*/
uint32_t test_start (void)
{
int failures;
/* Default to zero failures */
failures = 0;
/* Log final status */
if (failures == 0)
{
ATOMLOG (_STR("Pass\n"));
}
else
{
ATOMLOG (_STR("Fail(%d)\n"), failures);
}
/* Quit */
return failures;
}

258
tests/timer1.c Normal file
View File

@@ -0,0 +1,258 @@
/*
* Copyright (c) 2010, Kelvin Lawson. All rights reserved.
*
* 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. No personal names or organizations' names associated with the
* Atomthreads project may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT 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 PROJECT 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.
*/
#include "atom.h"
#include "atomsem.h"
#include "atomtimer.h"
#include "atomtests.h"
/* Test OS objects */
static ATOM_SEM sem1;
/* Forward declarations */
static void testCallback (POINTER cb_data);
/**
* \b test_start
*
* Start timer test.
*
* This test exercises the atomTimerDelay() API. It checks that
* the correct time delay is used, and also checks that error
* checking is correctly implemented within the API.
*
* @retval Number of failures
*/
uint32_t test_start (void)
{
int failures;
uint32_t start_time, end_time;
ATOM_TIMER timer_cb;
/* Default to zero failures */
failures = 0;
/* Test parameter-checks */
if (atomTimerDelay(0) != ATOM_ERR_PARAM)
{
ATOMLOG(_STR("Param\n"));
failures++;
}
/* Create a semaphore for receiving test notification */
if (atomSemCreate (&sem1, 0) != ATOM_OK)
{
ATOMLOG (_STR("SemCreate\n"));
failures++;
}
else
{
/*
* Create a timer callback which will attempt to
* call atomTimerDelay() at interrupt context.
*/
timer_cb.cb_func = testCallback;
timer_cb.cb_data = NULL;
timer_cb.cb_ticks = SYSTEM_TICKS_PER_SEC;
/* Request the timer callback to run in one second */
if (atomTimerRegister (&timer_cb) != ATOM_OK)
{
ATOMLOG (_STR("TimerRegister\n"));
failures++;
}
/* Wait up to two seconds for sem1 to be posted indicating success */
else if (atomSemGet (&sem1, 2 * SYSTEM_TICKS_PER_SEC) != ATOM_OK)
{
ATOMLOG (_STR("Context check\n"));
failures++;
}
/* Delete the test semaphore */
if (atomSemDelete (&sem1) != ATOM_OK)
{
ATOMLOG (_STR("Delete\n"));
failures++;
}
}
/*
* Test that a 1 tick delay returns when system time
* has increased by exactly 1 tick.
*/
/*
* We first delay by 1 tick to ensure that the thread
* is running at the start of a new tick, which should
* ensure that the time does not tick over between
* setting start_time and actually calling
* atomTimerDelay().
*/
atomTimerDelay(1);
/* Record the start time */
start_time = atomTimeGet();
/* Request a 1 tick sleep */
if (atomTimerDelay(1) != ATOM_OK)
{
ATOMLOG (_STR("Delay1\n"));
failures++;
}
else
{
/* Record the time we woke up */
end_time = atomTimeGet();
/* Check that time has advanced by exactly 1 tick */
if ((end_time - start_time) != 1)
{
ATOMLOG (_STR("Tick1:%d\n"), (end_time-start_time));
failures++;
}
}
/*
* Test that a 2 tick delay returns when system time
* has increased by exactly 2 ticks.
*/
/*
* We first delay by 1 tick to ensure that the thread
* is running at the start of a new tick, which should
* ensure that the time does not tick over between
* setting start_time and actually calling
* atomTimerDelay().
*/
atomTimerDelay(1);
/* Record the start time */
start_time = atomTimeGet();
/* Request a 2 tick sleep */
if (atomTimerDelay(2) != ATOM_OK)
{
ATOMLOG (_STR("Delay2\n"));
failures++;
}
else
{
/* Record the time we woke up */
end_time = atomTimeGet();
/* Check that time has advanced by exactly 2 ticks */
if ((end_time - start_time) != 2)
{
ATOMLOG (_STR("Tick2:%d\n"), (end_time-start_time));
failures++;
}
}
/*
* Test that a 500 tick delay returns when system time
* has increased by exactly 500 ticks.
*/
/*
* We first delay by 1 tick to ensure that the thread
* is running at the start of a new tick, which should
* ensure that the time does not tick over between
* setting start_time and actually calling
* atomTimerDelay().
*/
atomTimerDelay(1);
/* Record the start time */
start_time = atomTimeGet();
/* Request a 500 tick sleep */
if (atomTimerDelay(500) != ATOM_OK)
{
ATOMLOG (_STR("Delay500\n"));
failures++;
}
else
{
/* Record the time we woke up */
end_time = atomTimeGet();
/* Check that time has advanced by exactly 500 ticks */
if ((end_time - start_time) != 500)
{
ATOMLOG (_STR("Tick500:%d\n"), (end_time-start_time));
failures++;
}
}
/* Log final status */
if (failures == 0)
{
ATOMLOG (_STR("Pass\n"));
}
else
{
ATOMLOG (_STR("Fail(%d)\n"), failures);
}
/* Quit */
return failures;
}
/**
* \b testCallback
*
* Attempt an atomTimerDelay() call from interrupt context.
* Should receive an ATOM_ERR_CONTEXT error. Posts sem1 if successful.
*
* @param[in] cb_data Not used
*/
static void testCallback (POINTER cb_data)
{
/* Check the return value from atomTimerDelay() */
if (atomTimerDelay(1) == ATOM_ERR_CONTEXT)
{
/* Received the error we expected, post sem1 to notify success */
atomSemPut(&sem1);
}
else
{
/*
* Did not get the expected error, don't post sem1 and the test
* thread will time out, signifying an error.
*/
}
}

176
tests/timer2.c Normal file
View File

@@ -0,0 +1,176 @@
/*
* Copyright (c) 2010, Kelvin Lawson. All rights reserved.
*
* 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. No personal names or organizations' names associated with the
* Atomthreads project may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT 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 PROJECT 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.
*/
#include "atom.h"
#include "atomtimer.h"
#include "atomtests.h"
/* Test period (in seconds) */
#define TEST_PERIOD_SECS 10
/* Test OS objects */
static ATOM_TCB tcb1, tcb2, tcb3;
static uint8_t test1_thread_stack[TEST_THREAD_STACK_SIZE];
static uint8_t test2_thread_stack[TEST_THREAD_STACK_SIZE];
static uint8_t test3_thread_stack[TEST_THREAD_STACK_SIZE];
/* Per-thread failure counts */
static volatile int g_failure_cnt[3];
/* Forward declarations */
static void test_thread_func (uint32_t data);
/**
* \b test_start
*
* Start timer test.
*
* Tests that atomTimerDelay() delays for the correct time
* period when used by three threads simultanously.
*
* @retval Number of failures
*/
uint32_t test_start (void)
{
int failures;
/* Default to zero failures */
failures = 0;
g_failure_cnt[0] = g_failure_cnt[1] = g_failure_cnt[2] = 0;
/* Create Thread 1 */
if (atomThreadCreate(&tcb1, TEST_THREAD_PRIO, test_thread_func, 1,
&test1_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK)
{
/* Fail */
ATOMLOG (_STR("Thread1\n"));
failures++;
}
/* Create Thread 2 */
if (atomThreadCreate(&tcb2, TEST_THREAD_PRIO, test_thread_func, 2,
&test2_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK)
{
/* Fail */
ATOMLOG (_STR("Thread2\n"));
failures++;
}
/* Create Thread 3 */
if (atomThreadCreate(&tcb3, TEST_THREAD_PRIO, test_thread_func, 3,
&test3_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK)
{
/* Fail */
ATOMLOG (_STR("Thread3\n"));
failures++;
}
/* Sleep for 10 seconds allowing the three threads to run tests */
if (atomTimerDelay(TEST_PERIOD_SECS * SYSTEM_TICKS_PER_SEC) != ATOM_OK)
{
ATOMLOG (_STR("Period\n"));
failures++;
}
/* Add the per-thread failure count to the main count */
failures += g_failure_cnt[0] + g_failure_cnt[1] + g_failure_cnt[2];
/* Log final status */
if (failures == 0)
{
ATOMLOG (_STR("Pass\n"));
}
else
{
ATOMLOG (_STR("Fail(%d)\n"), failures);
}
/* Quit */
return failures;
}
/**
* \b test_thread_func
*
* Entry point for test thread. The same thread entry point is used for all
* three test threads, with the thread number/ID (1-3) passed as the entry
* point parameter.
*
* @param[in] data Thread number (1,2,3)
*
* @return None
*/
static void test_thread_func (uint32_t data)
{
uint8_t thread_id;
uint32_t start_time, end_time;
/* Thread ID is passed through the function parameter */
thread_id = (uint8_t)data;
/*
* Sleep for 1 tick to ensure that the thread starts near
* a timer tick boundary. This ensures that the system
* tick does not advance between the atomTimeGet() call
* and the actual atomTimerDelay() call being made.
*/
atomTimerDelay (1);
/* Loop running the test forever */
while (1)
{
/* Record the start time */
start_time = atomTimeGet();
/* Sleep for n ticks, where n is the thread ID */
if (atomTimerDelay(thread_id) != ATOM_OK)
{
g_failure_cnt[thread_id-1]++;
}
else
{
/* Record the time we woke up */
end_time = atomTimeGet();
/* Check that time has advanced by exactly n ticks */
if ((end_time - start_time) != thread_id)
{
g_failure_cnt[thread_id-1]++;
}
}
}
}

252
tests/timer3.c Normal file
View File

@@ -0,0 +1,252 @@
/*
* Copyright (c) 2010, Kelvin Lawson. All rights reserved.
*
* 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. No personal names or organizations' names associated with the
* Atomthreads project may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT 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 PROJECT 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.
*/
#include "atom.h"
#include "atomsem.h"
#include "atomtimer.h"
#include "atomtests.h"
/* Test OS objects */
static ATOM_SEM sem1;
/* Forward declarations */
static void testCallback (POINTER cb_data);
/**
* \b test_start
*
* Start timer test.
*
* This test exercises the atomTimerRegister() API. It tests that bad
* parameters are trapped, and that timer callbacks occur at the
* correct time.
*
* @retval Number of failures
*/
uint32_t test_start (void)
{
int failures;
ATOM_TIMER timer_cb;
uint32_t expected_time;
/* Default to zero failures */
failures = 0;
/* Create a semaphore for receiving test notifications */
if (atomSemCreate (&sem1, 0) != ATOM_OK)
{
ATOMLOG (_STR("SemCreate\n"));
failures++;
}
/* Test that bad parameters are trapped */
/* NULL parameter check */
if (atomTimerRegister(NULL) != ATOM_ERR_PARAM)
{
ATOMLOG (_STR("Param1\n"));
failures++;
}
/* NULL callback function */
timer_cb.cb_ticks = 1;
timer_cb.cb_func = NULL;
if (atomTimerRegister(&timer_cb) != ATOM_ERR_PARAM)
{
ATOMLOG (_STR("Param2\n"));
failures++;
}
/* Zero ticks */
timer_cb.cb_ticks = 0;
timer_cb.cb_func = testCallback;
if (atomTimerRegister(&timer_cb) != ATOM_ERR_PARAM)
{
ATOMLOG (_STR("Param3\n"));
failures++;
}
/* Request a callback in 1 tick */
/*
* Sleep for one tick first to ensure we start near a
* tick boundary. This should ensure that the timer
* tick does not advance while we are setting up the
* timer but before registering the timer.
*/
atomTimerDelay(1);
/* Request a callback in one tick time */
timer_cb.cb_ticks = 1;
/* We pass our testCallback() the expected end time */
timer_cb.cb_func = testCallback;
expected_time = atomTimeGet() + timer_cb.cb_ticks;
timer_cb.cb_data = &expected_time;
/* Register the timer callback */
if (atomTimerRegister (&timer_cb) != ATOM_OK)
{
ATOMLOG (_STR("TimerReg1\n"));
failures++;
}
/* Wait up to 5 ticks for sem1 to be posted indicating success */
else if (atomSemGet (&sem1, 5) != ATOM_OK)
{
ATOMLOG (_STR("Tick1\n"));
failures++;
}
/* Request a callback in 2 ticks */
/*
* Sleep for one tick first to ensure we start near a
* tick boundary. This should ensure that the timer
* tick does not advance while we are setting up the
* timer but before registering the timer.
*/
atomTimerDelay(1);
/* Request a callback in 2 ticks time */
timer_cb.cb_ticks = 2;
/* We pass our testCallback() the expected end time */
timer_cb.cb_func = testCallback;
expected_time = atomTimeGet() + timer_cb.cb_ticks;
timer_cb.cb_data = &expected_time;
/* Register the timer callback */
if (atomTimerRegister (&timer_cb) != ATOM_OK)
{
ATOMLOG (_STR("TimerReg2\n"));
failures++;
}
/* Wait up to 5 ticks for sem1 to be posted indicating success */
else if (atomSemGet (&sem1, 5) != ATOM_OK)
{
ATOMLOG (_STR("Tick2\n"));
failures++;
}
/* Request a callback in 500 ticks */
/*
* Sleep for one tick first to ensure we start near a
* tick boundary. This should ensure that the timer
* tick does not advance while we are setting up the
* timer but before registering the timer.
*/
atomTimerDelay(1);
/* Request a callback in 500 ticks time */
timer_cb.cb_ticks = 500;
/* We pass our testCallback() the expected end time */
timer_cb.cb_func = testCallback;
expected_time = atomTimeGet() + timer_cb.cb_ticks;
timer_cb.cb_data = &expected_time;
/* Register the timer callback */
if (atomTimerRegister (&timer_cb) != ATOM_OK)
{
ATOMLOG (_STR("TimerReg500\n"));
failures++;
}
/* Wait up to 600 ticks for sem1 to be posted indicating success */
else if (atomSemGet (&sem1, 600) != ATOM_OK)
{
ATOMLOG (_STR("Tick500\n"));
failures++;
}
/* Delete the test semaphore */
if (atomSemDelete (&sem1) != ATOM_OK)
{
ATOMLOG (_STR("Delete\n"));
failures++;
}
/* Log final status */
if (failures == 0)
{
ATOMLOG (_STR("Pass\n"));
}
else
{
ATOMLOG (_STR("Fail(%d)\n"), failures);
}
/* Quit */
return failures;
}
/**
* \b testCallback
*
* Check the time at which this timer callback occurs matches
* the time we expected. Post sem1 if correct.
*
* @param[in] cb_data Not used
*/
static void testCallback (POINTER cb_data)
{
uint32_t expected_end_time;
/* Pull out the expected end time */
expected_end_time = *(uint32_t *)cb_data;
/*
* Check the callback time (now) matches the time
* we expected the callback.
*/
if (atomTimeGet() == expected_end_time)
{
/* Called back when we expected, post sem1 to notify success */
atomSemPut(&sem1);
}
else
{
/*
* Not called at expected time, don't post sem1 and the test
* thread will time out, signifying an error.
*/
}
}

183
tests/timer4.c Normal file
View File

@@ -0,0 +1,183 @@
/*
* Copyright (c) 2010, Kelvin Lawson. All rights reserved.
*
* 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. No personal names or organizations' names associated with the
* Atomthreads project may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT 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 PROJECT 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.
*/
#include "atom.h"
#include "atomtimer.h"
#include "atomtests.h"
/* Test OS objects */
static ATOM_TIMER timer_cb[4];
/* Global test data */
static uint32_t cb_ticks[4];
/* Forward declarations */
static void testCallback (POINTER cb_data);
/**
* \b test_start
*
* Start timer test.
*
* This test exercises the atomTimerRegister() API, particularly the
* linked lists used internally for managing an ordered list of timers.
*
* Several timers are registered out of order (in terms of the callback
* time) and we check that the callbacks are called at the expected
* times.
*
* @retval Number of failures
*/
uint32_t test_start (void)
{
int failures;
int i;
/* Default to zero failures */
failures = 0;
/*
* Fill out four timer request structures. Callbacks are
* requested starting in one second, with the others
* at 1 tick intervals thereafter.
*/
for (i = 0; i < 4; i++)
{
/*
* testCallback() is passed the expected
* callback time via cb_data.
*/
cb_ticks[i] = SYSTEM_TICKS_PER_SEC + i;
timer_cb[i].cb_ticks = cb_ticks[i];
timer_cb[i].cb_func = testCallback;
timer_cb[i].cb_data = &cb_ticks[i];
}
/* Register the timers in a different order */
if (atomTimerRegister (&timer_cb[1]) != ATOM_OK)
{
ATOMLOG (_STR("TimerReg1\n"));
failures++;
}
else if (atomTimerRegister (&timer_cb[3]) != ATOM_OK)
{
ATOMLOG (_STR("TimerReg3\n"));
failures++;
}
else if (atomTimerRegister (&timer_cb[2]) != ATOM_OK)
{
ATOMLOG (_STR("TimerReg2\n"));
failures++;
}
else if (atomTimerRegister (&timer_cb[0]) != ATOM_OK)
{
ATOMLOG (_STR("TimerReg0\n"));
failures++;
}
else
{
/* Successfully registered timers */
/* Wait two seconds for callbacks to complete */
if (atomTimerDelay(2 * SYSTEM_TICKS_PER_SEC) != ATOM_OK)
{
ATOMLOG (_STR("Wait\n"));
failures++;
}
else
{
/*
* The callbacks should have cleared down cb_ticks[x]
* to zero if they were called at the expected
* system time.
*/
for (i = 0; i < 4; i++)
{
/* Check the callback has zeroed the area */
if (cb_ticks[i] != 0)
{
ATOMLOG (_STR("Clear%d\n"), i);
failures++;
}
}
}
}
/* Log final status */
if (failures == 0)
{
ATOMLOG (_STR("Pass\n"));
}
else
{
ATOMLOG (_STR("Fail(%d)\n"), failures);
}
/* Quit */
return failures;
}
/**
* \b testCallback
*
* Check the time at which this timer callback occurs matches
* the time we expected. Clear down the expected time location
* if correct.
*
* @param[in] cb_data Not used
*/
static void testCallback (POINTER cb_data)
{
uint32_t expected_end_time;
/* Pull out the expected end time */
expected_end_time = *(uint32_t *)cb_data;
/*
* Check the callback time (now) matches the time
* we expected the callback.
*/
if (atomTimeGet() == expected_end_time)
{
/* Called back when we expected, clear the passed location */
*(uint32_t *)cb_data = 0;
}
else
{
/* Not called at expected time, don't clear the location */
}
}

150
tests/timer5.c Normal file
View File

@@ -0,0 +1,150 @@
/*
* Copyright (c) 2010, Kelvin Lawson. All rights reserved.
*
* 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. No personal names or organizations' names associated with the
* Atomthreads project may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT 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 PROJECT 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.
*/
#include "atom.h"
#include "atomsem.h"
#include "atomtimer.h"
#include "atomtests.h"
/* Global test data */
static volatile int callback_ran_flag;
/* Forward declarations */
static void testCallback (POINTER cb_data);
/**
* \b test_start
*
* Start timer test.
*
* This test exercises the atomTimerCancel() API. It tests that bad
* parameters are trapped, and that it can be used to cancel an
* in-progress timer callback request.
*
* @retval Number of failures
*/
uint32_t test_start (void)
{
int failures;
ATOM_TIMER timer_cb;
/* Default to zero failures */
failures = 0;
/* Test parameter checks */
if (atomTimerCancel(NULL) != ATOM_ERR_PARAM)
{
ATOMLOG (_STR("Param\n"));
failures++;
}
/* Test cancel when timer not registered */
if (atomTimerCancel(&timer_cb) != ATOM_ERR_NOT_FOUND)
{
ATOMLOG (_STR("NotFound\n"));
failures++;
}
/* Test a callback can be cancelled */
callback_ran_flag = FALSE;
/* Request a callback in one second, no callback param required */
timer_cb.cb_ticks = SYSTEM_TICKS_PER_SEC;
timer_cb.cb_func = testCallback;
/* Register the timer callback */
if (atomTimerRegister (&timer_cb) != ATOM_OK)
{
ATOMLOG (_STR("TimerReg\n"));
failures++;
}
else
{
/* Successfully registered for one second's time */
/* Cancel the callback */
if (atomTimerCancel (&timer_cb) != ATOM_OK)
{
ATOMLOG (_STR("TimerCancel\n"));
failures++;
}
else
{
/* Successfully cancelled the callback */
/* Wait two seconds, and check callback did not occur */
if (atomTimerDelay(2 * SYSTEM_TICKS_PER_SEC) != ATOM_OK)
{
ATOMLOG (_STR("Wait\n"));
failures++;
}
else
{
/* The ran flag should still be FALSE */
if (callback_ran_flag != FALSE)
{
ATOMLOG (_STR("Called back\n"));
failures++;
}
}
}
}
/* Log final status */
if (failures == 0)
{
ATOMLOG (_STR("Pass\n"));
}
else
{
ATOMLOG (_STR("Fail(%d)\n"), failures);
}
/* Quit */
return failures;
}
/**
* \b testCallback
*
* Set a flag to say we ran. Expected not to run for a pass.
*
* @param[in] cb_data Not used
*/
static void testCallback (POINTER cb_data)
{
/* Callback was called */
callback_ran_flag = TRUE;
}

165
tests/timer6.c Normal file
View File

@@ -0,0 +1,165 @@
/*
* Copyright (c) 2010, Kelvin Lawson. All rights reserved.
*
* 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. No personal names or organizations' names associated with the
* Atomthreads project may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT 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 PROJECT 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.
*/
#include "atom.h"
#include "atomtimer.h"
#include "atomtests.h"
/* Test OS objects */
static ATOM_TIMER timer_cb[4];
/* Global test data */
static int callback_ran_flag[4];
/* Forward declarations */
static void testCallback (POINTER cb_data);
/**
* \b test_start
*
* Start timer test.
*
* This test exercises the atomTimerCancel() API, particularly its
* behaviour when there are several timers registered. Four timers
* are registered, two of which are cancelled, and the test confirms
* that only the two which are not cancelled are called back.
*
* @retval Number of failures
*/
uint32_t test_start (void)
{
int failures;
int i;
/* Default to zero failures */
failures = 0;
/* Clear down the ran flag for all four timers */
for (i = 0; i < 4; i++)
{
callback_ran_flag[i] = FALSE;
}
/*
* Fill out four timer request structures. Callbacks are
* requested starting in one second, with the others
* at 1 tick intervals thereafter.
*/
for (i = 0; i < 4; i++)
{
/*
* testCallback() is passed a pointer to the flag it
* should set to notify that it has run.
*/
timer_cb[i].cb_ticks = SYSTEM_TICKS_PER_SEC + i;
timer_cb[i].cb_func = testCallback;
timer_cb[i].cb_data = &callback_ran_flag[i];
}
/* Register all four timers */
for (i = 0; i < 4; i++)
{
if (atomTimerRegister (&timer_cb[i]) != ATOM_OK)
{
ATOMLOG (_STR("TimerReg\n"));
failures++;
}
}
/* Check timers were successfully created */
if (failures == 0)
{
/* Cancel two of the callbacks */
if (atomTimerCancel (&timer_cb[1]) != ATOM_OK)
{
ATOMLOG (_STR("Cancel1\n"));
failures++;
}
if (atomTimerCancel (&timer_cb[2]) != ATOM_OK)
{
ATOMLOG (_STR("Cancel2\n"));
failures++;
}
/* Wait two seconds for callbacks to complete */
if (atomTimerDelay(2 * SYSTEM_TICKS_PER_SEC) != ATOM_OK)
{
ATOMLOG (_STR("Wait\n"));
failures++;
}
else
{
/*
* We should now find that timer callbacks 0 and 3
* have run, but 1 and 2 did not (due to cancellation).
*/
if ((callback_ran_flag[0] != TRUE) || (callback_ran_flag[3] != TRUE)
|| (callback_ran_flag[1] != FALSE) || (callback_ran_flag[2] != FALSE))
{
ATOMLOG (_STR("Cancellations\n"));
failures++;
}
}
}
/* Log final status */
if (failures == 0)
{
ATOMLOG (_STR("Pass\n"));
}
else
{
ATOMLOG (_STR("Fail(%d)\n"), failures);
}
/* Quit */
return failures;
}
/**
* \b testCallback
*
* Set a flag to say we ran. Some of the callbacks are
* expected to execute this, while those that are
* cancelled should not.
*
* @param[in] cb_data Not used
*/
static void testCallback (POINTER cb_data)
{
/* Callback was called */
*(int *)cb_data = TRUE;
}

183
tests/timer7.c Normal file
View File

@@ -0,0 +1,183 @@
/*
* Copyright (c) 2010, Kelvin Lawson. All rights reserved.
*
* 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. No personal names or organizations' names associated with the
* Atomthreads project may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT 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 PROJECT 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.
*/
#include "atom.h"
#include "atomtests.h"
/* Test OS objects */
static ATOM_TIMER timer_cb[4];
/* Global test data */
static volatile uint32_t cb_order[4];
static int cb_cnt = 0;
static uint32_t cb_time[4];
/* Forward declarations */
static void testCallback (POINTER cb_data);
/**
* \b test_start
*
* Start timer test.
*
* Test the behaviour of the timer subsystem on a system clock rollover.
*
* Sets the system clock to just before rollover and registers several
* timers. Tests that all timer callbacks occur and that they occur in
* in the correct order, when they span a timer rollover.
*
* @retval Number of failures
*/
uint32_t test_start (void)
{
CRITICAL_STORE;
int i, failures;
/* Default to zero failures */
failures = 0;
/*
* Lockout interrupts while registering to ensure that the clock does
* not roll over under us while we are registering our test timers.
*/
CRITICAL_START ();
/* Set the clock to rollover - 6 */
atomTimeSet (0xFFFFFFFA);
/* Timer in 2 ticks (pre-rollover): should be called back first */
timer_cb[0].cb_ticks = 2;
timer_cb[0].cb_func = testCallback;
timer_cb[0].cb_data = (POINTER)0;
if (atomTimerRegister (&timer_cb[0]) != ATOM_OK)
{
ATOMLOG (_STR("TimerReg0\n"));
failures++;
}
/* Timer in 10 ticks (post-rollover): should be called back last */
timer_cb[1].cb_ticks = 10;
timer_cb[1].cb_func = testCallback;
timer_cb[1].cb_data = (POINTER)3;
if (atomTimerRegister (&timer_cb[1]) != ATOM_OK)
{
ATOMLOG (_STR("TimerReg1\n"));
failures++;
}
/* Timer in 4 ticks (pre-rollover): should be called back second */
timer_cb[2].cb_ticks = 4;
timer_cb[2].cb_func = testCallback;
timer_cb[2].cb_data = (POINTER)1;
if (atomTimerRegister (&timer_cb[2]) != ATOM_OK)
{
ATOMLOG (_STR("TimerReg2\n"));
failures++;
}
/* Timer in 9 ticks (post-rollover): should be called back third */
timer_cb[3].cb_ticks = 9;
timer_cb[3].cb_func = testCallback;
timer_cb[3].cb_data = (POINTER)2;
if (atomTimerRegister (&timer_cb[3]) != ATOM_OK)
{
ATOMLOG (_STR("TimerReg3\n"));
failures++;
}
/* Initialise the cb_order delay to known values */
for (i = 0; i < 4; i++)
{
cb_order[i] = 99;
}
/* Unlock interrupts and let the test begin */
CRITICAL_END ();
/*
* Wait 20 ticks for the callbacks to complete. Also tests another
* timer registration via atomTimerDelay() for us.
*/
atomTimerDelay (20);
/* Check the order the callbacks came in matched our expectations */
for (i = 0; i < 4; i++)
{
if (cb_order[i] != i)
{
ATOMLOG (_STR("T%d=%d\n"), i, cb_order[i]);
failures++;
}
}
/* Log final status */
if (failures == 0)
{
ATOMLOG (_STR("Pass\n"));
}
else
{
ATOMLOG (_STR("Fail(%d)\n"), failures);
}
/* Quit */
return failures;
}
/**
* \b testCallback
*
* Timer callback. Store our cb_data value in cb_order[].
* This allows us to check that the callback order was as expectd.
*
* @param[in] cb_data Not used
*/
static void testCallback (POINTER cb_data)
{
int expected_order;
/* Pull out the expected ordere */
expected_order = (int)cb_data;
/* Store our callback order in cb_order[] */
cb_order[cb_cnt] = expected_order;
/* Store the current time for debug purposes */
cb_time[cb_cnt] = atomTimeGet();
/* Interrupts are locked out so we can modify cb_cnt without protection */
cb_cnt++;
}