Porting Guide - Required Port Functions
Atomthreads has been designed to minimise the effort required to port it to a new architecture. Only a small number of architecture-specific routines are required to get Atomthreads up and running on your target. These functions are as follows:
void archThreadContextInit (ATOM_TCB *tcb_ptr, void *stack_top, void (*entry_point)(UINT32), UINT32 entry_param)
This function initialises each thread's stack during creation, before the thread is first run. New threads are scheduled in using the same context-switch function used for threads which were previously scheduled out, therefore this function should set up a stack context which looks much like a thread which has been scheduled out and had its context saved. We fill part of the stack with those registers which are involved in the context switch, including appropriate stack or register contents to cause the thread to branch to its entry point function when it is scheduled in.
Interrupts should also be enabled whenever a thread is restored, hence ports may wish to explicitly include the interrupt-enable register here which will be restored when the thread is scheduled in. Other methods can be used to enable interrupts, however, without explicitly storing it in the thread's context.
This function generally does not need to access registers directly, and hence can usually be written entirely in C.
archContextSwitch(ATOM_TCB *old_tcb, ATOM_TCB *new_tcb)
This function is responsible for doing the actual switching between thread contexts. It is responsible for saving the current thread's context, and restoring the context for new thread being scheduled in. This must be done using appropriate assembly language instructions for saving and restoring the registers necessary for your architecture.
It is up to you whether you code this routine in C with inline assembler or as a straight assembler routine. In some cases the compiler may generate some code in the prologue and epilogue, or temporarily use registers outside of the prologue/epilogue. Because you must look at the generated assembler output to be sure the instructions and register usages were approprate, and sometimes must hand-code workarounds to the generated code, this can make the function overly complicated. In these cases it may be easier to use a straight assembler routine over which you have ultimate control over what happens with the various registers. In addition, the compiler may generate different code between different compiler versions and command-line switches, which means you must revisit the assembler listings output by the compiler to be sure nothing inappropriate has been introduced. In these cases it is safer to write an assembler routine so that you can be sure of compatibility regardless of what the compiler chooses to generate.
The function must do the following:
Save the current thread's registers
These are typically saved directly onto the thread's stack at the current stack pointer, though you may use a separate context-save area if this is preferred or required on your architecture. In addition to the general purpose registers you should save any status registers, stack pointer etc. as required for your architecture.
The stack pointer is normally saved in the sp_save_ptr member of the thread's ATOM_TCB structure. For ease of access from assembler routines this is the first member of the ATOM_TCB structure.
Restore the new thread's registers
This is essentially the reverse of the context-save process. The context-restore code gets the new thread's stack pointer from the TCB, and can begin popping the saved registers off the stack. Once all context has been popped from the stack, the stack pointer should then be the same as the thread's stack pointer when it was scheduled out, and its registers should be restored to their previous values. Depending on the architecture and port, the stack or CPU registers will contain the return address for restoring the thread to its location before it was scheduled out.
See here for a discussion of which registers should be saved.
This function is responsible for restoring and starting the first thread the OS runs. It expects to find the thread context exactly as it would be if a context save had previously taken place on it. The only real difference between this and the archContextSwitch() routine is that there is no previous thread for which context must be saved.
The final action this function must do is to restore interrupts. Your initialisation code should keep interrupts disabled during OS initialisation until this function has finished. This protects crucial operating system structures and data until it is safe for reschedules to take place.
The same advice as given above in archContextSwitch() applies to this routine, regarding whether to write this routine in assembler or as a C function. While this routine can be written in C with inline assembler, it is often more appropriate to write it in assembler in order to avoid possible problems with compiler-generated code.
Timer Tick Interrupt
You must arrange for a timer interrupt to call the kernel system-tick handler at regular intervals. This handles any registered timer callbacks and calls the scheduler in case any threads are scheduled in as a result, and to thread-switch if more than one thread is currently ready at the same priority (in a round-robin fashion). System ticks of 100Hz or 200Hz are common, but you may alter this depending on your application needs. On every system tick (e.g. every 10ms for a 100Hz system tick) you must arrange for the atomTimerTick() function to be called.