Table of Contents
- Introduction
- Usage
- Basics
- Notes
- Setup
- Defining a function
- Start a new thread
- Calling a function
- Calling a function indirectly
- Returning from a function
- Obtaining the return code
- Finishing a thread
- Suspending within a thread
- Resuming a thread
- Trapping
- The parent IOB
- User context
- MT_CALL sequence of events
- Macros
- Test code
- License
1 Introduction
A user level system uses multi-threading (a.k.a. blocking threads); however, for most interrupt driven code, system multi-threading is not used. This document describes a set of macros called MT_CALL
which simulates multi-threading systems. Using these macros has these major advantages:
- it uses only one system thread to support thousands of I/O threads,
- an Operating System is not required, and
- it supports threads on the interrupt level.
Typically, while in interrupt level, work can be either dispatched to task level or kept in interrupt level depending on the implementation of the IO handler. For example, a cache subsystem and RAID subsystem may be best implemented at task level. When the cache handler is activated, it may run a system task; but the storage handler may be more effective if it runs at interrupt level. MT_CALL
s can run at interrupt level and provide task style programming.
These macros are used to maintain structured programming techniques within the interrupt context. Just as with normal task level software, it is much easier to follow the flow of control using multi-tasking than it would be to use a traditional state machine. For example, at task level, one would rarely use a state machine to perform disk I/O because the Operating System provides synchronous calls. But, at interrupt level, synchronous I/O is not possible, so asynchronous methods must be employed. Normally, this requires complicated state machines, and complicated state machines require an in depth knowledge of how all states fit together. As new requirements are placed on the software, there is a temptation to use flags to avoid new states. These flags, or even the introduction of new states, often cause "code balance" to be upset.
Figure 1 (MT_CALL sequence) shows a sample flow. Function A starts a thread with MT_START
(path 1); the thread (function B) starts into action and performs an MT_CALL
to function C (path 2); function C performs the initial setup and performs some I/O which will eventually lead to an interrupt; function C uses MT_SUSPEND
to suspend operations; as can be seen by path 3, a hidden return occurs back to the initial creator of the thread, and the thread creator eventually returns to the Operating System which proceeds to other tasks; an interrupt occurs (path 4) and the flow continues to a user provided handler; the user handler identifies the source of the interrupt and uses MT_RESUME
to resume execution of the suspended thread (path 5); the suspended thread picks up control after MT_SUSPEND
and returns to its caller using an MT_RETURN
(path 6); MT_RESUME
continues its activities by calling the previous function (path 7) which determines that it is finished; function B uses MT_FINISHED
(path 8) to terminate the thread; MT_FINISHED
causes control to pass back to MT_RESUME
which returns back to the interrupt system and finally back to the interrupted task.
2 Usage
2.1 Basics
- To start a thread, use
MT_START
. - Each function called with
MT_START
or MT_CALL
must be an MT_FUNCTION
. - Each
MT_FUNCTION
must begin with MT_ENTRY
and must return using MT_RETURN
. - If an
MT_FUNCTION
determines that the thread is finished, it uses MT_FINISHED
. - If a thread must suspend for an external event (e.g., an I/O operation), it uses
MT_SUSPEND
. - When the external event occurs (e.g., an interrupt), the handler uses
MT_RESUME
.
2.2 Notes
- Variables on the stack will not be preserved around an
MT_CALL
; those variables should be placed within your personal IOB.
2.3 Setup
The macros are used as follows:
- Define if you are debugging or not
The MT_DEBUG
macro will cause stack checking, and will print the use of each macro, allowing you to trace your execution. For example:
#define MT_DEBUG
Define the depth of the nestingA stack is setup in the Task Control Block which holds the address and state of each MT_CALL
. The maximum depth is defined as in this example:
#define MT_MAX_DEPTH 3
Define the states you will be usingThese states are defined by the MT_STATES
construct. These will be appended to an internal enum typedef called eMT_STATE
. Separate each state with a comma, and if separate lines are used, use the macro continuation operator. Be sure to leave the operator off of the last line. For example:
#define MT_STATES \
DO_SCAN, \
START_IO, \
TEST_UNIT_READY, \
CHECKING_DRIVE, \
GETTING_TYPE, \
STOP_SCAN, \
EXECUTE_SCSI
Define the pointer to be used for the Task Control Block.
This pointer is encapsulated in many of the macros. It is generally not used by the application, but since some macros are general, it may be used for those cases. For example:
#define MT_TCB_PTR pTcb
Include the macro fileFor example:
#include <MT_CALL.H>
Define the Task Control BlockThe Task Control Block is the struct MT_TCB
, type sMT_TCB
, and pointer pMT_TCB
. For example:
sMT_TCB CheckDevicesTcb;
Define the prototypes for the MT_Function
sAn MT function is defined with the MT_FUNCTION
macro. For example:
MT_FUNCTION ( CheckDevicesThread );
2.4 Defining a function
When a function starts a thread or is called by an MT_CALL
, it must be defined as an MT_FUNCTION
. Furthermore, it must contain an MT_ENTRY
to define the states being used, and must use MT_RETURN
to return. It may use the macros MT_START
, MT_CALL
, MT_SUSPEND
, MT_SET_TRAP
, MT_RESUME
, or MT_GOTO_TRAP
(and maybe some that I forgot).
The MT_NAME
macro is used to define the name of the function, and the MT_FUNCTION
macro is used to form the function. The MT_NAME
macro will be used within some of the other macros. This name is used to identify the function name in the Task Control Block.
#undef MT_NAME
#define MT_NAME FunctionName
MT_FUNCTION ( FunctionName )
Where:
FunctionName
is the name of the function. It must appear within the MT_FUNCTION
macro. The macro could be written as MT_FUNCTION( MT_NAME )
if you wish (I prefer using the name so I can just cut and paste to form the prototype).
The function may not take any arguments. This seems unfortunate, but it is the nature of a state driven strategy in any case (remember, this is simply an easy to use state machine); sorry.
The MT_ENTRY
macro generally appears after the automatic variable definitions. However, it may appear after code, as explained in an example below.
MT_ENTRY
{
MT_USE( StateName );
MT_USE( AnotherStateName );
MT_END_OF_LIST(); }
Where:
StateName
and AnotherStateName
are the names of the states used in this function. For each state used, there must be a corresponding MT_CALL
or MT_SUSPEND
, and the states must have been defined in the MT_STATES
macro.
The MT_RETURN
macro is used to return from an MT_FUNCTION
. It does not take an argument.
MT_RETURN();
Caution
Since these functions are actually re-entered when an MT_RESUME
is used, any automatic variable will be lost around an MT_CALL
or MT_SUSPEND
. So, some variables may need to be within a user defined structure.
Here is an example:
#undef MT_NAME
#define MT_NAME CheckDevicesThread
MT_FUNCTION (CheckDevicesThread)
{
FUNNY_IOB *pIob = GetTheIob();
MT_ENTRY()
{
MT_USE( CHECKING_DRIVE );
MT_USE( GETTING_TYPE );
MT_END_OF_LIST();
}
for(pIob->dn = 0; pIob->dn < MAX_DRIVES; pIob->dn++)
{
MT_CALL(CheckDrive, CHECKING_DRIVE);
}
MT_CALL(DoInquiry, GETTING_TYPE);
MT_RETURN();
}
2.5 Start a new thread
Threads are started with the MT_START
macro. Once started, a thread is on its own in its own execution path. The Task Control Block will maintain the state of the thread (the MT_TCB_PTR
is used for this purpose).
The calling sequence is:
MT_START( pTcb, funToCall, pPrev );
Where:
pTcb | is a pointer to the Task Control Block to use. |
funToCall | is the name of an MT_FUNCTION to be called. |
pPrev | is a pointer to a Task Control Block which represents a thread to be resumed when the new thread finishes (using the MT_FINISHED macro); this may be NULL if there is no previous thread. |
For example:
MT_START( &CheckDevicesTcb, CheckDevicesThread, NULL );
In this case, the function called CheckDevicesThread()
will be called. The Task Control Block is CheckDevicesTcb
, and there is no previous Task Control Block involved.
2.6 Calling a function
Whenever a function is called that must suspend or lead to a suspend, it must be coded as an MT_CALL
and the function must be coded as an MT_FUNCTION
.
Caution: When making an MT_CALL
, automatic (stack) variables will not be preserved across the MT_CALL
.
The calling sequence is:
MT_CALL( funToCall, callState );
Where
funToCall | is the MT_FUNCTION to call. |
callState | is one of the states defined in MT_STATES and used in an MT_USE . |
2.7 Calling a function indirectly
A function can be called indirectly by using the following method.
Define the function pointer using double parenthesis, such as:
MT_FUNCTION((*pFunction));
Call the function referencing the pointer just as if it were a normal MT_CALL
, as:
MT_CALL( pFunction, callState );
Following is an example of using an array of pointers:
MT_FUNCTION((*pFunction[10]));
...
MT_CALL( pFunction[j], callState );
2.8 Returning from a function
When you must return from the function, use the MT_RETURN
macro.
The calling sequence is:
MT_RETURN( returnCode )
Where
returnCode | is an unsigned int to be returned to the caller; this is obtained via the MT_GET_RETURN_CODE macro. |
2.9 Obtaining the return code
After a return from an MT_CALL
, you can get the return code from the called function with the MT_GET_RETURN_CODE
macro.
The calling sequence is:
rc = MT_GET_RETURN_CODE()
Where:
rc
is an unsigned int. It is set by an argument of an MT_RETURN
, MT_FINISHED
, or MT_GOTO_TRAP
.
2.10 Finishing a thread
When you are finished with the thread, it can be terminated with an MT_FINISHED
macro. If a previous TCB was specified in the MT_START
macro (see pPrev
above), the MT_FINISHED
macro will resume the thread which used MT_START
.
Note: The thread that used MT_START
and specified a pPrev
argument must have suspended with an MT_SUSPEND
macro or must be proceeding to an MT_SUPEND
.
The calling sequence is:
MT_FINISHED( returnCode )
Where:
returnCode
is an unsigned int. It can be retrieved by using MT_GET_RETURN_CODE()
.
2.11 Suspending within a thread
A thread may suspend execution at anytime by using the MT_SUSPEND
macro. When suspended, execution must continue by some external logic supplied by the user. This logic is generally part of the interrupt process; however, the logic could be simply a matter of satisfying the proper conditions.
The calling sequence is:
MT_SUSPEND( callState )
Where:
callState
is one of the enums supplied with the MT_USE
macro.
2.12 Resuming a thread
A thread may be resumed via the MT_RESUME
macro before or after it has suspended. This "early resume" overcomes a possible timing problem where an interrupt may occur after an I/O was started but before the thread actually executed the MT_SUSPEND
.
The MT_RESUME
macro is generally used from the interrupt service routine; however, this is not a requirement.
The calling sequence is:
MT_RESUME( pTcb )
Where:
pTcb
is a pointer to the Task Control Block that has been assigned to the thread to be resumed.
2.13 Trapping
A thread may trap its execution to a predetermined point. This is useful when an error occurs at a low level. It is similar to the setjmp
/longjmp
functions in C.
The calling sequence is:
MT_SET_TRAP( pEnv, callState )
MT_GOTO_TRAP(pEnv, returnCode )
Where:
pEnv | is a pointer to an environment structure of type MT_ENV . |
callState | is the state name for the trap and is named in an MT_USE macro. |
returnCode | is a return code that can be obtained with the MT_GET_RETURN_CODE macro. |
Usage:
The usage of the trap function is as follows:
- Setup the trap return point with the
MT_SET_TRAP
macro. - Test whether or not the trap has occurred with the
MT_IS_TRAPPING
macro. - Perform the trap with the
MT_GOTO_TRAP
macro.
Here is an example:
sMT_ENV Environment;
...
MT_FUNCTION (CheckDrive)
{
MT_ENTRY()
{
MT_USE( STOP_SCAN );
MT_END_OF_LIST();
}
MT_SET_TRAP( &Environment, STOP_SCAN );
if (MT_IS_TRAPPING())
{
printf("\nCheckDrive cought a trap, rc=%d",
MT_GET_RETURN_CODE());
MT_RETURN(0);
}
...
}
...
MT_FUNCTION (ExecuteScsi)
{
MT_ENTRY()
{
...
}
...
if( an error occurred )
{
printf("\nTrapping in ExecuteScsi");
MT_GOTO_TRAP( &Environment, 9876);
}
...
}
2.14 The parent IOB
With each TCB, sometimes there may be an I/O structure (IOB) associated with it. When that method is used, it is usually necessary to get the address of the IOB that contains the TCB. This can be done in several ways; for convenience, the MT_GET_PARENT_STRUCT
macro can be used.
The calling sequence is:
MT_GET_PARENT_STRUCT( structType, tcb )
Where:
structType | is the typedef for the IOB structure |
tcb | is the name of the TCB structure within the IOB |
2.15 User context
Very often, it is necessary to set some user context that will be carried along with the thread. This can be done using the MT_SET_CONTEXT_PTR
and MT_GET_CONTEXT_PTR
macros.
The calling sequence is:
MT_SET_CONTEXT_PTR( pTcb, ptr )
ptr = MT_GET_CONTEXT_PTR()
Where:
pTcb | is a pointer to the Task Control Block which is to carry the context pointer. |
ptr | is a user context pointer; this can point to anything and is not understood by the macros. |
2.16 MT_CALL sequence of events
Referencing Figure 1 - MT_CALL sequence of events, the sequence of events that occur with these functions is as follows:
Trail 1 shows the thread starting with an MT_START
macro. This initializes the Thread Control Block (TCB) named in the calling sequence. Specifically, the current state (currentState
) is set to -1.
- The new thread proceeds through the
MT_ENTRY
statement because the currentState
has been initialized to -1 by MT_START
. - Execution continues normally, and trail 2 shows an
MT_CALL
. This increments the stackIndex
, adds the address of the current function (named by #define MT_NAME
) to the callback stack (callStack
), and sets the call state (callState
). MT_CALL
also sets currentState
to -1 to force the new function to proceed through the MT_ENTRY
statement. - Execution continues normally through the
MT_ENTRY
statement and on to the MT_SUSPEND
statement.
Trail 3 shows the departure back to the Operating System. MT_SUSPEND
has also incremented the stackIndex
and added the address of the current function just as above. Now, things are suspended.
- Trail 3 returns to the Operating System because
MT_SUSPEND
has an embedded return NULL
statement. The MT_CALL
that started trail 2 checks the return value, and if it is a NULL
, it repeats the return NULL
. Any other MT_CALL
s in the sequence also do the same, hence control returns to MT_START
. The MT_START
assumes the thread is free running and just continues execution (in this case, Function A actually does the return to the Operating System). Note: before MT_SUSPEND
does anything, it checks to see if an MT_RESUME
has already been used, and if so, it does nothing (example not shown in this figure).
Trail 4 shows an interrupt occurring. The interrupt system passes control to a user handler, and that handler, after determining the source of the interrupt, uses the MT_RESUME
macro to resume execution of the thread.
- The
MT_RESUME
is a loop that calls the functions back using stackIndex
and supplies each function with its saved callState
. If MT_RESUME
sees that MT_SUSPEND
has not yet been used, it only records the fact that MT_RESUME
was used and then does nothing. This allows for the case where an interrupt may actually occur before MT_SUSPEND
had a chance to execute.
Trail 5 shows the resumption of Function C. Now, the MT_ENTRY
in Function C performs a switch(callState)
which causes a goto
around the MT_SUSPEND
.
- Execution continues along trail 5 to the point of an
MT_RETURN
.
Trail 6 shows that an MT_RETURN
returns back to the User Handler. The MT_RETURN
returns a non-NULL
value which keeps MT_RESUME
in its loop.
Trail 7 shows that the previous function is called with its callState
. This could have done more MT_CALL
s or an MT_SUSPEND
, in which case, the above scenario continues to occur.
- In our case, trail 7 runs into an
MT_FINISHED
statement. If no previous TCB was supplied in the MT_START
macro, MT_FINISHED
just does a return NULL
. That causes everything to return to the Operating System because MT_RESUME
stops when something returns a NULL
. - If Function B had used an
MT_SUSPEND
, that would return NULL
, which would also cause the MT_RESUME
loop to terminate.
Figure 1 - MT_CALL sequence of events
3 Macros
#ifndef MT_MAX_DEPTH
#define MT_MAX_DEPTH 6 // the debugger picks this up by mistake so for
#endif
#ifndef MT_STATES
#define MT_STATES MT_NOTHING
#endif
#ifndef __MT_CALL_PREDEF__
#define __MT_CALL_PREDEF__
typedef struct MT_SEMAPHORE
{
int signaled;
struct MT_TCB *pTcb;
} sMT_SEMAPHORE, *pMT_SEMAPHORE;
#endif // __MT_CALL_PREDEF__
#ifndef __MT_CALL_H__
#define __MT_CALL_H__
#ifdef __KERNEL__
#include <linux>
#else
#include <stddef.h>
#endif
#ifdef WIN32
#define MT_ENTER_DEBUGGER() _asm { int 3 }
#endif
#ifdef IDISX_MIPS_COMPILER
#define MT_ENTER_DEBUGGER() printk("%d\n", *(unsigned int *)0)
#endif
#define MT_FUNCTION(funName) sMT_TCB * funName(struct MT_TCB *MT_TCB_PTR)
typedef enum MT_STATE
{
MT_INITIAL_CALL = -1,
MT_UNINITIALIZED = 0,
MT_STATES
} eMT_STATE;
typedef struct MT_TCB * (*MT_FUNCTION_PTR) (struct MT_TCB *);
typedef struct MT_CALLBACK
{
MT_FUNCTION_PTR pFunction;
eMT_STATE functionState;
} sMT_CALLBACK, *pMT_CALLBACK;
typedef struct MT_TCB
{
int stackIndex;
sMT_CALLBACK callStack[ MT_MAX_DEPTH ];
enum MT_STATE currentState;
int suspends;
struct MT_TCB *pPrevTcb;
void *pContext;
MT_FUNCTION_PTR pTrappingFunction;
union
{
unsigned int returnCode;
void *returnContext;
} u;
} sMT_TCB, *pMT_TCB;
typedef struct MT_ENVIRONMENT
{
eMT_STATE currentState;
int stackIndex;
sMT_CALLBACK callBack;
} sMT_ENVIRONMENT, *pMT_ENVIRONMENT;
#ifdef MT_DEBUG
#ifdef WIN32
#include <assert.h>
#include>stdio.h>
#endif
#define MT_ASSERT(exp) if (!(exp)) { PRINTF("\nAssert failed: "\
#exp", "__FILE__", %d\n", __LINE__); MT_ENTER_DEBUGGER(); }
#define MT_STACK_CHECK(a) \
MT_ASSERT ( ((a)->stackIndex+1) < (MT_MAX_DEPTH) );
#if 0 // This does not always work because if an MT_RESUME is the last resume
#define MT_CLEAR_CALL(a) \
if ((a) != NULL) \
{ \
(a)->currentState = MT_UNINITIALIZED; \
(a)->callStack[ (a)->stackIndex+1 ].pFunction \
= NULL; \
(a)->callStack[ (a)->stackIndex+1 ].functionState \
= MT_UNINITIALIZED; \
}
#else
#define MT_CLEAR_CALL(a)
#endif
#define MT_CLEAR_ALL(a) \
{ \
\
int iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii; \
void *savedContext = (a)->pContext; \
for (iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii = 0; \
iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii < sizeof(sMT_TCB); \
++iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii) \
{ \
((char *)(a))[iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii] = 0; \
} \
(a)->pContext = savedContext; \
(a)->stackIndex = -1; \
}
#ifdef MT_DEBUG_PRINT
#define MT_PRINTF(a,b) \
{ \
\
int iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii; \
for (iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii = 0; \
iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii < (a)->stackIndex; \
++iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii) PRINTF(" "); \
} \
PRINTF b; \
PRINTF("\n");
#else
#define MT_PRINTF(a,b)
#endif
#else
#define MT_ASSERT(a)
#define MT_STACK_CHECK(a)
#define MT_CLEAR_CALL(a)
#define MT_CLEAR_ALL(a)
#define MT_PRINTF(a,b)
#endif
#define MT_START(pTcb,FunToCall,pPrev) \
{ \
MT_CLEAR_ALL( (pTcb) ) \
MT_PRINTF((pTcb),("MT_START "#FunToCall", TCB=%08X, "#pPrev"=%08X", pTcb, \
pPrev)) \
(pTcb)->currentState = MT_INITIAL_CALL; \
(pTcb)->stackIndex = -1; \
(pTcb)->suspends = 0; \
(pTcb)->pTrappingFunction = NULL; \
(pTcb)->u.returnCode = 0; \
(pTcb)->pPrevTcb = pPrev; \
FunToCall(pTcb); \
} \
#define MT_ENTRY() \
switch( (MT_TCB_PTR)->currentState ) \
#ifdef _DEBUG
#define MT_USE(callState) \
case callState: \
goto MT_LABEL_##callState; \
MT_USE_##callState: \
return NULL \
#else
#define MT_USE(callState) \
case callState: \
goto MT_LABEL_##callState;
#endif
#define MT_RETURN(code) \
{ \
if ( (MT_TCB_PTR)->stackIndex == -1 ) \
{ \
MT_FINISHED(code) \
} \
else \
{ \
(MT_TCB_PTR)->u.returnCode = code; \
return MT_TCB_PTR; \
} \
} \
#ifdef _DEBUG
#define MT_CALL(FunToCall,callState) \
{ \
MT_PRINTF(MT_TCB_PTR,("MT_CALL "#FunToCall", "#callState", TCB=%08X", \
MT_TCB_PTR)) \
MT_STACK_CHECK(MT_TCB_PTR) \
(MT_TCB_PTR)->currentState = MT_INITIAL_CALL; \
++(MT_TCB_PTR)->stackIndex; \
(MT_TCB_PTR)->callStack[ (MT_TCB_PTR)->stackIndex ].pFunction = \
MT_NAME; \
(MT_TCB_PTR)->callStack[ (MT_TCB_PTR)->stackIndex ].functionState = \
callState; \
if ( FunToCall(MT_TCB_PTR) == NULL ) \
{ \
goto MT_USE_##callState; \
} \
MT_LABEL_##callState: \
--(MT_TCB_PTR)->stackIndex; \
MT_PRINTF(MT_TCB_PTR,("MT_RETURN "#FunToCall", "#callState", TCB=%08X",\
MT_TCB_PTR)) \
} \
#else
#define MT_CALL(FunToCall,callState) \
{ \
MT_PRINTF(MT_TCB_PTR,("MT_CALL "#FunToCall", "#callState", TCB=%08X", \
MT_TCB_PTR)) \
MT_STACK_CHECK(MT_TCB_PTR) \
(MT_TCB_PTR)->currentState = MT_INITIAL_CALL; \
++(MT_TCB_PTR)->stackIndex; \
(MT_TCB_PTR)->callStack[ (MT_TCB_PTR)->stackIndex ].pFunction = \
MT_NAME; \
(MT_TCB_PTR)->callStack[ (MT_TCB_PTR)->stackIndex ].functionState = \
callState; \
if ( FunToCall(MT_TCB_PTR) == NULL ) \
{ \
return NULL; \
} \
MT_LABEL_##callState: \
--(MT_TCB_PTR)->stackIndex; \
MT_PRINTF(MT_TCB_PTR,("MT_RETURN "#FunToCall", "#callState", TCB=%08X",\
MT_TCB_PTR)) \
} \
#endif
#ifdef _DEBUG
#define MT_SUSPEND(callState) \
{ \
ASSERT( (MT_TCB_PTR)->suspends <= 1 ); \
ASSERT( (MT_TCB_PTR)->suspends >= -1 ); \
++(MT_TCB_PTR)->suspends; \
if ((MT_TCB_PTR)->suspends == 1) \
{ \
MT_PRINTF(MT_TCB_PTR,("MT_SUSPEND, "#callState", TCB=%08X", \
MT_TCB_PTR)) \
MT_STACK_CHECK(MT_TCB_PTR) \
++(MT_TCB_PTR)->stackIndex; \
(MT_TCB_PTR)->callStack[ (MT_TCB_PTR)->stackIndex ].pFunction = \
MT_NAME; \
(MT_TCB_PTR)->callStack[ (MT_TCB_PTR)->stackIndex ].functionState =\
callState; \
goto MT_USE_##callState; \
MT_LABEL_##callState: \
--(MT_TCB_PTR)->stackIndex; \
MT_PRINTF(MT_TCB_PTR,("MT_RESUME, "#callState", TCB=%08X", \
MT_TCB_PTR)) \
} \
else \
{ \
MT_PRINTF(MT_TCB_PTR,("MT_SUSPEND, "#callState", TCB=%08X - " \
"already resumed", MT_TCB_PTR))\
} \
} \
#else
#define MT_SUSPEND(callState) \
{ \
++(MT_TCB_PTR)->suspends; \
if ((MT_TCB_PTR)->suspends == 1) \
{ \
MT_PRINTF(MT_TCB_PTR,("MT_SUSPEND, "#callState", TCB=%08X", \
MT_TCB_PTR)) \
MT_STACK_CHECK(MT_TCB_PTR) \
++(MT_TCB_PTR)->stackIndex; \
(MT_TCB_PTR)->callStack[ (MT_TCB_PTR)->stackIndex ].pFunction = \
MT_NAME; \
(MT_TCB_PTR)->callStack[ (MT_TCB_PTR)->stackIndex ].functionState =\
callState; \
return NULL; \
MT_LABEL_##callState: \
--(MT_TCB_PTR)->stackIndex; \
MT_PRINTF(MT_TCB_PTR,("MT_RESUME, "#callState", TCB=%08X", \
MT_TCB_PTR)) \
} \
else \
{ \
MT_PRINTF(MT_TCB_PTR,("MT_SUSPEND, "#callState", TCB=%08X - " \
"already resumed", MT_TCB_PTR))\
} \
} \
#endif
#define MT_RESUME(pTcb) \
{ \
sMT_TCB *pNext; \
pNext = pTcb; \
ASSERT( pNext->suspends <= 1 ); \
ASSERT( pNext->suspends >= -1 ); \
--pNext->suspends; \
if (pNext->suspends == 0) \
{ \
while (pNext != NULL && pNext->stackIndex >= 0) \
{ \
register sMT_TCB *pNext1; \
register int stackIndex; \
stackIndex = pNext->stackIndex; \
pNext->currentState = pNext->callStack[ stackIndex ].functionState;\
pNext1 = (*pNext->callStack[ stackIndex ].pFunction)(pNext); \
MT_CLEAR_CALL( pNext ); \
pNext = pNext1; \
} \
} \
} \
#define MT_FINISHED(code) \
{ \
MT_PRINTF(MT_TCB_PTR,("MT_FINISHED, TCB=%08X, pPrevTcb=%08X", MT_TCB_PTR, \
(MT_TCB_PTR)->pPrevTcb)) \
if ((MT_TCB_PTR)->pPrevTcb == NULL) \
{ \
MT_CLEAR_ALL(MT_TCB_PTR); \
return NULL; \
} \
else \
{ \
sMT_TCB *pPrev = (MT_TCB_PTR)->pPrevTcb; \
pPrev->u.returnCode = code; \
ASSERT( (MT_TCB_PTR)->suspends <= 1 ); \
ASSERT( (MT_TCB_PTR)->suspends >= -1 ); \
--pPrev->suspends; \
if (pPrev->suspends == 0) \
{ \
return pPrev; \
} \
MT_PRINTF(MT_TCB_PTR,("MT_FINISHED, TCB=%08X, pPrevTcb=%08X - early " \
"resume", MT_TCB_PTR, pPrev)) \
return NULL; \
} \
} \
#ifdef _DEBUG
#define MT_SET_TRAP(pEnv,callState) \
MT_PRINTF(MT_TCB_PTR, ("MT_SET_TRAP, TCB=%08X, ENV=%08X, "#callState, \
MT_TCB_PTR, pEnv)); \
(pEnv)->stackIndex = (MT_TCB_PTR)->stackIndex; \
\
\
\
(pEnv)->callBack.pFunction = MT_NAME; \
(pEnv)->callBack.functionState = callState; \
(MT_TCB_PTR)->pTrappingFunction = NULL; \
\
if ((MT_TCB_PTR)->pTrappingFunction != NULL) goto MT_USE_##callState; \
MT_LABEL_##callState: \
#else
#define MT_SET_TRAP(pEnv,callState) \
MT_PRINTF(MT_TCB_PTR, ("MT_SET_TRAP, TCB=%08X, ENV=%08X, "#callState, \
MT_TCB_PTR, pEnv)); \
(pEnv)->stackIndex = (MT_TCB_PTR)->stackIndex; \
\
\
\
(pEnv)->callBack.pFunction = MT_NAME; \
(pEnv)->callBack.functionState = callState; \
(MT_TCB_PTR)->pTrappingFunction = NULL; \
if ((MT_TCB_PTR)->pTrappingFunction != NULL) return NULL; \
MT_LABEL_##callState: \
#endif
#define MT_GOTO_TRAP(pEnv,code) \
{ \
(MT_TCB_PTR)->stackIndex = (pEnv)->stackIndex; \
MT_PRINTF(MT_TCB_PTR,("MT_GOTO_TRAP, TCB=%08X, ENV=%08X", MT_TCB_PTR, \
pEnv)); \
(MT_TCB_PTR)->currentState = (pEnv)->callBack.functionState; \
(MT_TCB_PTR)->pTrappingFunction = MT_NAME; \
(MT_TCB_PTR)->u.returnCode = code; \
if ( (*(pEnv)->callBack.pFunction)(MT_TCB_PTR) == NULL ) \
{ \
return NULL; \
} \
else \
{ \
sMT_TCB *pNext; \
pNext = MT_TCB_PTR; \
while (pNext != NULL && pNext->stackIndex >= 0) \
{ \
register sMT_TCB *pNext1; \
register int stackIndex; \
stackIndex = pNext->stackIndex; \
pNext->currentState = pNext->callStack[ stackIndex ].functionState;\
pNext1 = (*pNext->callStack[ stackIndex ].pFunction)(pNext); \
MT_CLEAR_CALL( pNext ); \
pNext = pNext1; \
} \
return NULL; \
} \
}
#define MT_RESUME_AT_TRAP(pTcb,pEnv,code) \
{ \
(pTcb)->stackIndex = (pEnv)->stackIndex; \
MT_PRINTF(pTcb,("MT_GOTO_TRAP, TCB=%08X, ENV=%08X", pTcb, pEnv)); \
(pTcb)->currentState = (pEnv)->callBack.functionState; \
(pTcb)->pTrappingFunction = (MT_FUNCTION_PTR)1; \
(pTcb)->u.returnCode = code; \
if ( (*(pEnv)->callBack.pFunction)(pTcb) == NULL ) \
{ \
\
} \
else \
{ \
sMT_TCB *pNext; \
pNext = pTcb; \
while (pNext != NULL && pNext->stackIndex >= 0) \
{ \
register sMT_TCB *pNext1; \
register int stackIndex; \
stackIndex = pNext->stackIndex; \
pNext->currentState = pNext->callStack[ stackIndex ].functionState;\
pNext1 = (*pNext->callStack[ stackIndex ].pFunction)(pNext); \
MT_CLEAR_CALL( pNext ); \
pNext = pNext1; \
} \
\
} \
}
#define MT_IS_TRAP() \
(MT_TCB_PTR)->pTrappingFunction \
#define MT_GET_PARENT_STRUCT(iobType, tcb) \
(iobType *)((char *)MT_TCB_PTR - offsetof(iobType, tcb)) \
#define MT_SET_CONTEXT_PTR(pTcb,ptr) \
(pTcb)->pContext = (void *)ptr
#define MT_GET_CONTEXT_PTR() \
(MT_TCB_PTR)->pContext
#define MT_GET_CONTEXT_PTR_FROM_TCB(pTcb) \
(pTcb)->pContext
#define MT_SET_RETURN_CODE(tcb,code) \
(tcb)->u.returnCode = code \
#define MT_GET_RETURN_CODE() \
(MT_TCB_PTR)->u.returnCode \
#define MT_SEM_INITIALIZE(pSemaphore) \
{ \
(pSemaphore)->signaled = 0; \
} \
#define MT_SEM_WAIT(pSemaphore,callState) \
{ \
--(pSemaphore)->signaled; \
if ((pSemaphore)->signaled < 0) \
{ \
(pSemaphore)->pTcb = MT_TCB_PTR; \
MT_SUSPEND(callState); \
} \
} \
#define MT_SEM_SIGNAL(pSemaphore) \
{ \
++(pSemaphore)->signaled; \
if ((pSemaphore)->signaled <= 0) \
{ \
MT_RESUME((pSemaphore)->pTcb); \
} \
} \
#endif // ifdef __MT_CALL_H__
#define MT_ABORT_THREAD(pTcb,pEnv,code) \
(pTcb)->stackIndex = (pEnv)->stackIndex; \
MT_PRINTF(pTcb,("MT_ABORT_THREAD, TCB=%08X, ENV=%08X, code=%08X", pTcb, \
pEnv, code)); \
(pTcb)->currentState = (pEnv)->callBack.functionState; \
(pTcb)->pTrappingFunction = (MT_FUNCTION_PTR)1; \
(pTcb)->u.returnCode = code; \
if ( (*(pEnv)->callBack.pFunction)(pTcb) == NULL ) \
{ \
MT_ASSERT(!"MT_ABORT_THREAD didn't abort"); \
}
#define MT_EXIT() \
return NULL;
#include <stddef.h>
#include <stdio.h>
#include <process.h>
#include <assert.h>
#define ASSERT assert
#define PRINTF printf
#define MT_ENTER_DEBUGGER() _asm { int 3 }
#define MT_DEBUG
#define MT_DEBUG_PRINT
#define MT_MAX_DEPTH 3
#define MT_STATES \
DO_SCAN, \
START_IO, \
TEST_UNIT_READY, \
CHECKING_DRIVE, \
GETTING_TYPE, \
TRAP_SCAN, \
EXECUTE_SCSI, \
WAIT_FOR_ITEM, \
FINISHED_NO_SUSPEND, \
FINISHED_WITH_SUSPEND, \
SUSPEND_THREAD
#define MT_TCB_PTR pTcb
#include "MtCall.h"
void main(void);
void Isr(void);
sMT_TCB CheckDevicesTcb;
sMT_ENVIRONMENT Environment;
typedef struct FUNNY_IOB
{
int something;
sMT_TCB DetermineTypeTcb;
} sFUNNY_IOB, *pFUNNY_IOB;
sFUNNY_IOB DetermineTypeIob;
pMT_TCB pPendingTcb;
int InterruptPending;
sMT_SEMAPHORE Semaphore;
#define TEST_TRAP_FROM_CALL
#define MAX_DRIVES 1
MT_FUNCTION (CheckDevicesThread);
MT_FUNCTION (DetermineTypeThread);
MT_FUNCTION (CheckDrive);
MT_FUNCTION (ExecuteScsi);
MT_FUNCTION (NoSuspendThread);
MT_FUNCTION (SuspendThread);
int DriveNum;
unsigned int *pItemToDo;
unsigned int *FirstItem;
unsigned int *SecondItem;
int StartIo(sMT_TCB *pTcb)
{
printf("Starting I/O\n");
#ifdef TEST_NO_INTERRUPT_ON_IO
return 0;
#endif
pPendingTcb = pTcb;
InterruptPending = 1;
#ifdef TEST_INTERRUPT_IS_EARLY
Isr();
#endif
return 1;
}
void Isr(void)
{
printf("Enter interrupt level\n");
InterruptPending = 0;
MT_RESUME(pPendingTcb);
printf("Leave interrupt level\n");
}
void main(void)
{
char ch;
MT_SEM_INITIALIZE( &Semaphore );
MT_START( &CheckDevicesTcb, CheckDevicesThread, NULL );
SecondItem = NULL;
FirstItem = (unsigned int *)&SecondItem;
pItemToDo = (unsigned int *)&FirstItem;
MT_SEM_SIGNAL( &Semaphore );
MT_SEM_SIGNAL( &Semaphore );
MT_SEM_SIGNAL( &Semaphore );
#ifndef MT_USE_FUNCTIONS_DIRECTLY
while (InterruptPending)
{
printf("Running some other task\n");
Isr();
}
#endif
if (CheckDevicesTcb.suspends != 0)
{
printf("**************Error, CheckDevicesTcb is still suspended\n");
}
else if (DetermineTypeIob.DetermineTypeTcb.suspends != 0)
{
printf("**************Error, DetermineTypeIob.DetermineTypeTcb is still"
" suspended\n");
}
else
{
printf("All done.\n");
}
printf("Press Enter to exit ...");
scanf_s("%c", &ch, 1);
printf("\n");
}
#undef MT_NAME
#define MT_NAME CheckDevicesThread
MT_FUNCTION (CheckDevicesThread)
{
unsigned int *pItem;
MT_ENTRY()
{
MT_USE( CHECKING_DRIVE );
MT_USE( GETTING_TYPE );
MT_USE( WAIT_FOR_ITEM );
MT_USE( FINISHED_NO_SUSPEND );
}
for (;;)
{
MT_SEM_WAIT( &Semaphore, WAIT_FOR_ITEM );
pItem = pItemToDo;
if (pItem == NULL)
{
printf("pItem == NULL, nothing to do\n");
break;
}
printf("Working on item %08X\n", pItem );
pItemToDo = (unsigned int *)*pItemToDo;
for (DriveNum = 0; DriveNum < MAX_DRIVES; DriveNum++)
{
MT_CALL(CheckDrive, CHECKING_DRIVE);
}
MT_START( &DetermineTypeIob.DetermineTypeTcb, NoSuspendThread,
MT_TCB_PTR );
MT_SUSPEND( FINISHED_NO_SUSPEND );
MT_START( &DetermineTypeIob.DetermineTypeTcb, DetermineTypeThread,
MT_TCB_PTR );
MT_SUSPEND( GETTING_TYPE );
printf("Finished with DetermineTypeThread, rc=%d\n",
MT_GET_RETURN_CODE());
}
MT_RETURN( 0 );
}
#undef MT_NAME
#define MT_NAME NoSuspendThread
MT_FUNCTION (NoSuspendThread)
{
MT_FINISHED(4321);
}
#undef MT_NAME
#define MT_NAME SuspendThread
MT_FUNCTION (SuspendThread)
{
MT_ENTRY()
{
MT_USE( SUSPEND_THREAD );
}
MT_SUSPEND( SUSPEND_THREAD );
MT_FINISHED(6789);
}
#undef MT_NAME
#define MT_NAME DetermineTypeThread
MT_FUNCTION (DetermineTypeThread)
{
pFUNNY_IOB pIob;
pIob = MT_GET_PARENT_STRUCT( sFUNNY_IOB, DetermineTypeTcb);
MT_ENTRY()
{
MT_USE( EXECUTE_SCSI );
}
#ifdef TEST_FINISH_FROM_CALL
MT_FINISHED(4321);
#endif
MT_CALL( ExecuteScsi, EXECUTE_SCSI );
MT_FINISHED(1234);
}
#undef MT_NAME
#define MT_NAME CheckDrive
MT_FUNCTION (CheckDrive)
{
MT_ENTRY()
{
MT_USE( TEST_UNIT_READY );
#if defined(TEST_TRAP_FROM_CALL) || defined(TEST_TRAP_FROM_RESUME)
MT_USE( TRAP_SCAN );
#endif
}
#if defined(TEST_TRAP_FROM_CALL) || defined(TEST_TRAP_FROM_RESUME)
MT_SET_CONTEXT_PTR( MT_TCB_PTR, &Environment );
MT_SET_TRAP( &Environment, TRAP_SCAN );
if (MT_IS_TRAP())
{
printf("CheckDrive cought trap, rc=%d\n", MT_GET_RETURN_CODE());
MT_RETURN( 0 );
}
#endif
MT_CALL( ExecuteScsi, TEST_UNIT_READY );
MT_RETURN( 0 );
}
#undef MT_NAME
#define MT_NAME ExecuteScsi
MT_FUNCTION (ExecuteScsi)
{
MT_ENTRY()
{
MT_USE( START_IO );
}
#ifdef TEST_TRAP_FROM_CALL
if (MT_GET_CONTEXT_PTR() != NULL)
{
printf("Traping in ExecuteScsi\n");
MT_GOTO_TRAP((sMT_ENVIRONMENT *)MT_GET_CONTEXT_PTR(), 9876);
printf("\n*************error, trap returned\n");
exit(1);
}
#endif
#ifdef TEST_ASYNC_RESUME
{
static int done;
if (!done)
{
done = 1;
MT_RESUME( );
}
}
#endif
if (StartIo(pTcb))
{
MT_SUSPEND( START_IO );
#ifdef TEST_TRAP_FROM_RESUME
if (MT_GET_CONTEXT_PTR(pMT_ENV) != NULL)
{
printf("Traping in ExecuteScsi\n");
MT_GOTO_TRAP(MT_GET_CONTEXT_PTR(sMT_ENVIRONMENT), 6789);
printf("\n*************error, trap returned\n");
exit(1);
}
#endif
}
MT_RETURN( 0 );
}
5 License
MT_CALL is free software: you can redistribute it and/or modify it under the terms of the The Code Project Open License (CPOL) as published by www.codeproject.com (http://www.codeproject.com/info/cpol10.aspx).
MT_CALL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See The Code Project Open License (CPOL) at http://www.codeproject.com/info/cpol10.aspx for more details.