mirror of
https://github.com/kelvinlawson/atomthreads.git
synced 2026-02-26 08:33:16 +01:00
STM8: Cosmic compiler: save virtual register c_lreg. Add details on Cosmic virtual registers to documentation. IAR compiler: Add __task to entry points to reduce unnecessary stack usage.
This commit is contained in:
@@ -436,4 +436,71 @@ included in any STM8 products. If it is included in future products then
|
||||
you will need to put the device in the stack compatible mode described.
|
||||
|
||||
|
||||
---------------------------------------------------------------------------
|
||||
|
||||
WRITING NEW INTERRUPT HANDLERS
|
||||
|
||||
All interrupt handlers which will call out to the OS kernel and potentially
|
||||
cause a thread switch must call atomIntEnter() and atomIntExit(). An
|
||||
example of this can be seen in the timer tick ISR in atomport.c.
|
||||
|
||||
With the Cosmic compiler port it is also necessary to add the @svlreg
|
||||
modifier to any interrupt handlers which call out to the OS kernel.
|
||||
Alternatively you may use the INTERRUPT macro from atomport-private.h which
|
||||
always adds the @svlreg modifier. This modifier ensures that the c_lreg
|
||||
virtual register is saved on the interrupted thread's stack for any
|
||||
preemptive context switches. It also ensures that longs are available for
|
||||
use within any OS kernel code called as part of the interrupt handling.
|
||||
|
||||
You may also implement fast interrupt handlers in the system which do not
|
||||
call atomIntEnter()/atomIntExit() and which do not need the @svlreg
|
||||
modifier, however these ISRs cannot perform OS functions such as posting
|
||||
semaphores or effecting a thread switch.
|
||||
|
||||
|
||||
---------------------------------------------------------------------------
|
||||
|
||||
COSMIC COMPILER VIRTUAL REGISTERS
|
||||
|
||||
The STM8 has only very few CPU registers, so the Cosmic compiler augments
|
||||
them with three "virtual" registers, which are simply locations in fast
|
||||
memory. These registers are called c_x, c_y and c_lreg.
|
||||
|
||||
The Atomthreads context switch for Cosmic/STM8 takes advantage of the fact
|
||||
that all CPU and virtual registers are automatically saved on the stack by
|
||||
the compiler when calling out to C functions.
|
||||
|
||||
For cooperative context switches (where a thread calls an OS kernel
|
||||
function to schedule itself out), any of these registers which should be
|
||||
preserved across the function call are automatically saved on the stack by
|
||||
the compiler before the context switch is even called. This means that no
|
||||
CPU or virtual registers actually have to be saved in the context switch
|
||||
routine, making cooperative switches potentially very cheap if few
|
||||
registers must be preserved.
|
||||
|
||||
For preemptive switches (where an ISR has interrupted a thread and wishes
|
||||
to switch to a new thread), the interrupt handler prologue also saves all
|
||||
CPU and virtual registers. In this case all registers must always be saved
|
||||
because the ISR has no knowledge of what registers the interrupted thread
|
||||
was using, so we cannot take advantage of the potential for saving fewer
|
||||
than the full set of registers that we achieve with cooperative switches.
|
||||
With the Cosmic compiler, interrupt handlers that call out to C functions
|
||||
(as would happen on a thread switch) always save the CPU registers and
|
||||
the virtual registers c_x and c_y. For the Atomthreads port we force
|
||||
interrupt handlers to also save the virtual register c_lreg. This is to
|
||||
ensure that the interrupted thread's c_lreg value is preserved across a
|
||||
thread switch, but also ensures that longs can be used within the OS
|
||||
kernel code called by interrupt handlers (c_lreg is used by the compiler
|
||||
for handling longs and floats).
|
||||
|
||||
An alternative scheme would be to not save c_lreg in all interrupt
|
||||
handlers and instead save it in the context-switch function. This would
|
||||
allow interrupt handlers to avoid saving the 4-byte c_lreg on the stack,
|
||||
but it would mean that any OS kernel code called by interrupt handlers
|
||||
could not deal with longs, which would be an unfortunate burden on the
|
||||
core portable OS code just for the benefit of this one architecture and
|
||||
compiler. It would also mean that c_lreg is always saved unnecessarily
|
||||
for every cooperative context switch.
|
||||
|
||||
|
||||
---------------------------------------------------------------------------
|
||||
|
||||
@@ -36,19 +36,19 @@ xdef _archContextSwitch, _archFirstThreadRestore
|
||||
|
||||
|
||||
; \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.
|
||||
;
|
||||
;
|
||||
; @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)
|
||||
_archContextSwitch:
|
||||
|
||||
@@ -63,7 +63,11 @@ _archContextSwitch:
|
||||
; PC: Program counter
|
||||
; CC: Code condition register
|
||||
;
|
||||
;
|
||||
; Cosmic compiler virtual registers:
|
||||
;
|
||||
; c_x, c_y: Scratch memory areas saved by ISRs
|
||||
; c_lreg: Scratch memory area only saved by ISRs with @svlreg
|
||||
;
|
||||
; If this is a cooperative context switch (a thread has called us
|
||||
; to schedule itself out), the Cosmic compiler will have saved any
|
||||
; of the registers which it does not want us to clobber. There are
|
||||
@@ -77,15 +81,19 @@ _archContextSwitch:
|
||||
; similarly saved all registers which it needs us not to clobber
|
||||
; which in the case of this compiler is all registers. Again, we
|
||||
; do not need to save any registers because no registers are
|
||||
; expected to be unclobbered by a subroutine.
|
||||
; expected to be unclobbered by a subroutine. Note that it is
|
||||
; necessary to add the @svlreg modifier to ISRs which call out to
|
||||
; the OS in order to force a save of c_lreg. The rest of the CPU
|
||||
; registers and the c_x and c_y virtual registers are, however,
|
||||
; always saved by ISRs which call out to C subroutines.
|
||||
;
|
||||
; This is an unusual context switch routine, because it does not
|
||||
; need to actually save any registers. Instead, the act of
|
||||
; calling this function causes all registers which must not be
|
||||
; clobbered to be saved on the stack anyway in the case of
|
||||
; clobbered to be saved on the stack anyway in the case of
|
||||
; cooperative context switches. For preemptive switches, the
|
||||
; interrupt service routine which calls out to here causes all
|
||||
; registers to be saved in a similar fashion.
|
||||
; interrupt service routine which calls out to here also causes
|
||||
; all registers to be saved in a similar fashion.
|
||||
|
||||
; We do have to do some work in here though: we need to store
|
||||
; the current stack pointer to the current thread's TCB, and
|
||||
@@ -109,7 +117,7 @@ _archContextSwitch:
|
||||
; Our stack frame now contains all registers (if this is a preemptive
|
||||
; switch due to an interrupt handler) or those registers which the
|
||||
; calling function did not wish to be clobbered (if this is a
|
||||
; cooperative context switch). It also contains the return address
|
||||
; cooperative context switch). It also contains the return address
|
||||
; which will be either a function called via an ISR (for preemptive
|
||||
; switches) or a function called from thread context (for cooperative
|
||||
; switches). Finally, the stack also contains the aforementioned
|
||||
@@ -118,7 +126,7 @@ _archContextSwitch:
|
||||
; 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. In most
|
||||
; We are now ready to restore the new thread's context. In most
|
||||
; architecture ports we would typically switch our stack pointer
|
||||
; to the new thread's stack pointer, and pop all of its context
|
||||
; off the stack, before returning to the caller (the original
|
||||
@@ -135,11 +143,11 @@ _archContextSwitch:
|
||||
|
||||
; Pull the new_tcb_ptr parameter from the stack into X register
|
||||
ldw X,($3,SP)
|
||||
|
||||
; Pull the first entry out of new_tcb_ptr (the new thread's
|
||||
|
||||
; Pull the first entry out of new_tcb_ptr (the new thread's
|
||||
; stack pointer) into X register.
|
||||
ldw X,(X)
|
||||
|
||||
|
||||
; Switch our current stack pointer to that of the new thread.
|
||||
ldw SP,X
|
||||
|
||||
@@ -161,7 +169,7 @@ _archContextSwitch:
|
||||
; because this is a subroutine regardless of whether we were called
|
||||
; during an ISR or by a thread cooperatively switching out. The
|
||||
; difference between RET and IRET on the STM8 architecture is that
|
||||
; RET only pops the return address off the stack, while IRET also
|
||||
; RET only pops the return address off the stack, while IRET also
|
||||
; pops from the stack all of the CPU registers saved when the ISR
|
||||
; started, including restoring the interrupt-enable bits from the CC
|
||||
; register.
|
||||
@@ -243,8 +251,8 @@ _archContextSwitch:
|
||||
; different thread's stack. Because the stack pointer is
|
||||
; switched, it does not matter that on entry via ISRs more
|
||||
; registers are saved on the original thread's stack than entries
|
||||
; via non-ISRs. Those extra registers will be restored properly
|
||||
; by an IRET when the thread is eventually scheduled back in
|
||||
; via non-ISRs. Those extra registers will be restored properly
|
||||
; by an IRET when the thread is eventually scheduled back in
|
||||
; (which could be a long way off). This assumes that the CPU does
|
||||
; not have hidden behaviour that occurs on interrupts, and we can
|
||||
; in fact trick it into leaving via another thread's call stack,
|
||||
@@ -269,7 +277,7 @@ _archContextSwitch:
|
||||
; data for being restored by either this function or the normal
|
||||
; function used for scheduling threads in, archContextSwitch(). Only
|
||||
; the first thread run by the system is launched via this function,
|
||||
; after which all other new threads will first be run by
|
||||
; after which all other new threads will first be run by
|
||||
; archContextSwitch().
|
||||
;
|
||||
; Typically ports will implement something similar here to the
|
||||
@@ -277,21 +285,21 @@ _archContextSwitch:
|
||||
; switch does not restore many registers, and instead relies on the
|
||||
; fact that returning from any function which called
|
||||
; archContextSwitch() will restore any of the necessary registers.
|
||||
; For new threads which have never been run there is no calling
|
||||
; For new threads which have never been run there is no calling
|
||||
; function which will restore context on return, therefore we
|
||||
; do not restore many register values here. It is not necessary
|
||||
; for the new threads to have initialised values for the scratch
|
||||
; registers A, X and Y or the code condition register CC which
|
||||
; registers A, X and Y or the code condition register CC which
|
||||
; leaves SP and PC. SP is restored because this is always needed to
|
||||
; switch to a new thread's stack context. It is not necessary to
|
||||
; restore PC, because the thread's entry point is in the stack
|
||||
; context (when this function returns using RET the PC is
|
||||
; switch to a new thread's stack context. It is not necessary to
|
||||
; restore PC, because the thread's entry point is in the stack
|
||||
; context (when this function returns using RET the PC is
|
||||
; automatically changed to the thread's entry point because the
|
||||
; entry point is stored in the preinitialised stack).
|
||||
; entry point is stored in the preinitialised stack).
|
||||
;
|
||||
; When new threads are started interrupts must be enabled, so there
|
||||
; is some scope for enabling interrupts in the CC here. It must be
|
||||
; done for all new threads, however, not just the first thread, so
|
||||
; done for all new threads, however, not just the first thread, so
|
||||
; we use a different system. We instead use a thread shell routine
|
||||
; which all functions run when they are first started, and
|
||||
; interrupts are enabled in there. This allows us to avoid having
|
||||
@@ -327,7 +335,7 @@ _archFirstThreadRestore:
|
||||
; As described above, first thread restores in this port do not
|
||||
; expect any initial register context to be pre-initialised in
|
||||
; the thread's stack area. The thread's initial stack need only
|
||||
; contain the thread's initial entry point, and we do not even
|
||||
; contain the thread's initial entry point, and we do not even
|
||||
; "restore" that within this function. We leave the thread's entry
|
||||
; point in the stack, and RET at the end of the function pops it
|
||||
; off and "returns" to the entry point as if we were called from
|
||||
@@ -342,7 +350,7 @@ _archFirstThreadRestore:
|
||||
; pointer it conveniently located at the top of the TCB so no
|
||||
; indexing is required to pull it out.
|
||||
ldw X,(X)
|
||||
|
||||
|
||||
; Switch our current stack pointer to that of the new thread.
|
||||
ldw SP,X
|
||||
|
||||
@@ -353,4 +361,4 @@ _archFirstThreadRestore:
|
||||
ret
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -39,19 +39,19 @@
|
||||
|
||||
|
||||
; \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.
|
||||
;
|
||||
;
|
||||
; @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)
|
||||
PUBLIC archContextSwitch
|
||||
archContextSwitch:
|
||||
@@ -190,7 +190,7 @@ archContextSwitch:
|
||||
; because this is a subroutine regardless of whether we were called
|
||||
; during an ISR or by a thread cooperatively switching out. The
|
||||
; difference between RET and IRET on the STM8 architecture is that
|
||||
; RET only pops the return address off the stack, while IRET also
|
||||
; RET only pops the return address off the stack, while IRET also
|
||||
; pops from the stack all of the CPU registers saved when the ISR
|
||||
; started, including restoring the interrupt-enable bits from the CC
|
||||
; register.
|
||||
@@ -272,8 +272,8 @@ archContextSwitch:
|
||||
; different thread's stack. Because the stack pointer is
|
||||
; switched, it does not matter that on entry via ISRs more
|
||||
; registers are saved on the original thread's stack than entries
|
||||
; via non-ISRs. Those extra registers will be restored properly
|
||||
; by an IRET when the thread is eventually scheduled back in
|
||||
; via non-ISRs. Those extra registers will be restored properly
|
||||
; by an IRET when the thread is eventually scheduled back in
|
||||
; (which could be a long way off). This assumes that the CPU does
|
||||
; not have hidden behaviour that occurs on interrupts, and we can
|
||||
; in fact trick it into leaving via another thread's call stack,
|
||||
@@ -298,7 +298,7 @@ archContextSwitch:
|
||||
; data for being restored by either this function or the normal
|
||||
; function used for scheduling threads in, archContextSwitch(). Only
|
||||
; the first thread run by the system is launched via this function,
|
||||
; after which all other new threads will first be run by
|
||||
; after which all other new threads will first be run by
|
||||
; archContextSwitch().
|
||||
;
|
||||
; Typically ports will implement something similar here to the
|
||||
@@ -306,21 +306,21 @@ archContextSwitch:
|
||||
; switch does not restore many registers, and instead relies on the
|
||||
; fact that returning from any function which called
|
||||
; archContextSwitch() will restore any of the necessary registers.
|
||||
; For new threads which have never been run there is no calling
|
||||
; For new threads which have never been run there is no calling
|
||||
; function which will restore context on return, therefore we
|
||||
; do not restore many register values here. It is not necessary
|
||||
; for the new threads to have initialised values for the scratch
|
||||
; registers A, X and Y or the code condition register CC which
|
||||
; registers A, X and Y or the code condition register CC which
|
||||
; leaves SP and PC. SP is restored because this is always needed to
|
||||
; switch to a new thread's stack context. It is not necessary to
|
||||
; restore PC, because the thread's entry point is in the stack
|
||||
; context (when this function returns using RET the PC is
|
||||
; switch to a new thread's stack context. It is not necessary to
|
||||
; restore PC, because the thread's entry point is in the stack
|
||||
; context (when this function returns using RET the PC is
|
||||
; automatically changed to the thread's entry point because the
|
||||
; entry point is stored in the preinitialised stack).
|
||||
; entry point is stored in the preinitialised stack).
|
||||
;
|
||||
; When new threads are started interrupts must be enabled, so there
|
||||
; is some scope for enabling interrupts in the CC here. It must be
|
||||
; done for all new threads, however, not just the first thread, so
|
||||
; done for all new threads, however, not just the first thread, so
|
||||
; we use a different system. We instead use a thread shell routine
|
||||
; which all functions run when they are first started, and
|
||||
; interrupts are enabled in there. This allows us to avoid having
|
||||
@@ -356,7 +356,7 @@ archFirstThreadRestore:
|
||||
; As described above, first thread restores in this port do not
|
||||
; expect any initial register context to be pre-initialised in
|
||||
; the thread's stack area. The thread's initial stack need only
|
||||
; contain the thread's initial entry point, and we do not even
|
||||
; contain the thread's initial entry point, and we do not even
|
||||
; "restore" that within this function. We leave the thread's entry
|
||||
; point in the stack, and RET at the end of the function pops it
|
||||
; off and "returns" to the entry point as if we were called from
|
||||
@@ -371,7 +371,7 @@ archFirstThreadRestore:
|
||||
; pointer it conveniently located at the top of the TCB so no
|
||||
; indexing is required to pull it out.
|
||||
ldw X,(X)
|
||||
|
||||
|
||||
; Switch our current stack pointer to that of the new thread.
|
||||
ldw SP,X
|
||||
|
||||
@@ -392,4 +392,4 @@ archFirstThreadRestore:
|
||||
ret
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -31,12 +31,40 @@
|
||||
#define __ATOM_PORT_PRIVATE_H
|
||||
|
||||
|
||||
/* Function prototypes */
|
||||
void archInitSystemTickTimer (void);
|
||||
#ifdef __CSMC__
|
||||
@far @interrupt void TIM1_SystemTickISR (void);
|
||||
#elif __IAR_SYSTEMS_ICC__
|
||||
__interrupt void TIM1_SystemTickISR (void);
|
||||
/**
|
||||
* Compiler-specific modifier to prevent some functions from saving
|
||||
* and restoring registers on entry and exit, if the function is
|
||||
* known to never complete (e.g. thread entry points).
|
||||
* Reduces stack usage on supporting compilers.
|
||||
*/
|
||||
#ifdef __IAR_SYSTEMS_ICC__
|
||||
#define NO_REG_SAVE __task
|
||||
#else
|
||||
#define NO_REG_SAVE
|
||||
#endif
|
||||
|
||||
|
||||
/**
|
||||
* Compiler-specific modifiers for interrupt handler functions.
|
||||
*
|
||||
* COSMIC: Uses @interrupt modifier for interrupt handlers. We
|
||||
* also force all interrupts to save c_lreg, a separate memory
|
||||
* area which Cosmic uses for longs and floats. This memory
|
||||
* area must be saved by interrupt handlers for context
|
||||
* switch purposes, and to avoid making it impossible to use
|
||||
* longs in any OS kernel code accessed by interrupt handlers.
|
||||
*
|
||||
* IAR: Uses __interrupt modifier for interrupt handlers.
|
||||
*/
|
||||
#ifdef __CSMC__
|
||||
#define INTERRUPT @far @interrupt @svlreg
|
||||
#else
|
||||
#define INTERRUPT __interrupt
|
||||
#endif
|
||||
|
||||
|
||||
/* Function prototypes */
|
||||
void archInitSystemTickTimer (void);
|
||||
INTERRUPT void TIM1_SystemTickISR (void);
|
||||
|
||||
#endif /* __ATOM_PORT_PRIVATE_H */
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
|
||||
|
||||
/** Forward declarations */
|
||||
static void thread_shell (void);
|
||||
static NO_REG_SAVE void thread_shell (void);
|
||||
|
||||
|
||||
/**
|
||||
@@ -66,9 +66,16 @@ static void thread_shell (void);
|
||||
* first time because you can preinitialise the stack context with
|
||||
* a suitable register value that will enable interrupts.
|
||||
*
|
||||
* If the compiler supports it, stack space can be saved by preventing
|
||||
* the function from saving registers on entry. This is because we
|
||||
* are called directly by the context-switch assembler, and know that
|
||||
* threads cannot return from here. The NO_REG_SAVE macro is used to
|
||||
* denote such functions in a compiler-agnostic way, though not all
|
||||
* compilers support it.
|
||||
*
|
||||
* @return None
|
||||
*/
|
||||
static void thread_shell (void)
|
||||
static NO_REG_SAVE void thread_shell (void)
|
||||
{
|
||||
ATOM_TCB *curr_tcb;
|
||||
|
||||
@@ -207,7 +214,7 @@ void archThreadContextInit (ATOM_TCB *tcb_ptr, void *stack_top, void (*entry_poi
|
||||
|
||||
/**
|
||||
* All thread context has now been initialised. All that is left
|
||||
* is to save the current stack pointer to the thread's TCB so
|
||||
* is to save the current stack pointer to the thread's TCB so
|
||||
* that it knows where to start looking when the thread is started.
|
||||
*/
|
||||
tcb_ptr->sp_save_ptr = stack_ptr;
|
||||
@@ -277,9 +284,8 @@ void archInitSystemTickTimer ( void )
|
||||
*/
|
||||
#if defined(__IAR_SYSTEMS_ICC__)
|
||||
#pragma vector = 13
|
||||
__interrupt
|
||||
#endif
|
||||
void TIM1_SystemTickISR (void)
|
||||
INTERRUPT void TIM1_SystemTickISR (void)
|
||||
{
|
||||
/* Call the interrupt entry routine */
|
||||
atomIntEnter();
|
||||
|
||||
@@ -122,8 +122,15 @@ static void main_thread_func (uint32_t data);
|
||||
*
|
||||
* Sets up the STM8 hardware resources (system tick timer interrupt) necessary
|
||||
* for the OS to be started. Creates an application thread and starts the OS.
|
||||
*
|
||||
* If the compiler supports it, stack space can be saved by preventing
|
||||
* the function from saving registers on entry. This is because we
|
||||
* are called directly by the C startup assembler, and know that we will
|
||||
* never return from here. The NO_REG_SAVE macro is used to denote such
|
||||
* functions in a compiler-agnostic way, though not all compilers support it.
|
||||
*
|
||||
*/
|
||||
void main ( void )
|
||||
NO_REG_SAVE void main ( void )
|
||||
{
|
||||
int8_t status;
|
||||
|
||||
@@ -164,7 +171,10 @@ void main ( void )
|
||||
}
|
||||
|
||||
/* There was an error starting the OS if we reach here */
|
||||
return;
|
||||
while (1)
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -63,16 +63,16 @@ uint32_t test_start (void)
|
||||
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;
|
||||
uint16_t eleven = 11;
|
||||
uint16_t twelve = 12;
|
||||
uint16_t thirteen = 13;
|
||||
uint16_t fourteen = 14;
|
||||
uint16_t fifteen = 15;
|
||||
uint32_t sixteen = 16;
|
||||
uint32_t seventeen = 17;
|
||||
uint32_t eighteen = 18;
|
||||
uint32_t nineteen = 19;
|
||||
uint32_t twenty = 20;
|
||||
|
||||
/* Default to zero failures */
|
||||
failures = 0;
|
||||
@@ -185,4 +185,4 @@ uint32_t test_start (void)
|
||||
/* Quit */
|
||||
return failures;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user