|void||archThreadContextInit (ATOM_TCB *tcb_ptr, void *stack_top, void(*entry_point)(uint32_t), uint32_t entry_param)|
|INTERRUPT void||TIM1_SystemTickISR (void)|
Initialise the system tick timer. Uses the STM8's TIM1 facility.
Referenced by main().
|void archThreadContextInit||(||ATOM_TCB *||tcb_ptr,|
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().
(COSMIC) On this port we take advantage of the fact that when the context switch routine is called the compiler will automatically stack all registers which should not be clobbered. This means that the context switch need only save and restore the stack pointer, which is stored in the thread's TCB. Because of this, it is not necessary to prefill a new thread's stack with any register values here. The only entry we need to make in the stack is the thread's entry point - this is not exactly restored when the thread is context switched in, but rather is popped off the stack by the context switch routine's RET call. That is used to direct the program counter to our thread's entry point - we are faking a return to a caller which never actually existed.
(IAR) The IAR compiler works around the lack of CPU registers on STM8 by allocating some space in low SRAM which is used for "virtual" registers. The compiler uses these like normal CPU registers, and hence their values must be preserved when context-switching between threads. Some of these (?b8 to ?b15) are expected to be preserved by called functions, and hence we actually need to save/restore those registers (unlike the rest of the virtual registers and the standard CPU registers). We therefore must prefill the stack with values for ?b8 to ?b15 here.
We could pre-initialise the stack so that the RET call goes directly to the thread entry point, with the thread entry parameter filled in. On this architecture, however, we use an outer thread shell routine which is used to call all threads. The thread entry point and parameter are stored in the thread's TCB which the thread shell uses to make the actual call to the entry point. We don't therefore need to store the actual thread entry and parameter within the stack.
Note that interrupts must be enabled the first time a thread is run. On some architectures this might be done by setting an initial value for the interrupt-enable register within the stack area. In this port, however, we use the thread shell to enable interrupts at the start of any thread.
|[in]||tcb_ptr||Pointer to the TCB of the thread being created|
|[in]||stack_top||Pointer to the top of the new thread's stack|
|[in]||entry_point||Pointer to the thread entry point function|
|[in]||entry_param||Parameter to be passed to the thread entry point|
Start at stack top
The thread restore routines will perform a RET which expects 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 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).
Because we are using a thread shell which is responsible for calling the real entry point, it also passes the parameters to entry point and we need not stack the entry parameter here.
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.
(IAR) Set up initial values for ?b8 to ?b15.
(COSMIC) We do not initialise any registers via the initial stack context at all.
All thread context has now been initialised. All that is left 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.
|INTERRUPT void TIM1_SystemTickISR||(||void||)|
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 CPU automatically saves all registers before calling out to an interrupt handler like this.
The system may decide to schedule in a new thread during the call to atomTimerTick(), in which case the program counter will be redirected to the new thread's running location during atomIntExit(). This ISR function will not actually complete until the thread we interrupted is scheduled back in, at which point the end of this function will be reached (after atomIntExit()) and the IRET call by the compiler will return us to the interrupted thread as if we hadn't run any other thread in the meantime. In other words the interrupted thread can be scheduled out by atomIntExit() and several threads could run before we actually reach the end of this function. When this function does finally complete, the return address (the PC of the thread which was interrupted) will be on the interrupted thread's stack because it was saved on there by the CPU when the interrupt triggered.
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.