The multitasking APIs are flexible but do not guarantee realtime response, since any task may run for a long time. Processing that needs to occur at a strict interval is done by real time tasks, sometimes abbreviated in the code as RTTs.
Optimizing the realtime tasks is key to an efficient system. To date, about 35-40% of the 6809 is spent in realtime tasks; the actual value depends on the machine and the number of device drivers needed.
Real-time tasks are scheduled at compile-time. When you write an RTT, you must also add an entry to the schedule file, either kernel/system.sched for the core parts of the system, or machine/machine/machine.sched for the game-specific RTTs. Each entry in the schedule file gives the rate at which you want your function to be run, for example, once every 8ms. You can schedule to run as frequently as once every 1ms, but you should only schedule as often as you really need it, otherwise the CPU will have little time to do anything else!
Real-time functions run in interrupt context. The periodic interrupt will preempt anything else that is running at non-real-time priority. Realtime functions cannot sleep.
A scheduler, gensched, processes the schedule files at compile-time and generates the interrupt handler code which calls all of the RTTs at the correct frequency. The output can be seen in build/sched_irq.c.
Each line in the schedule file is formatted as follows:
function frequency duration
function identifies the name of the function that you want called in realtime context. If you prefix the name with an exclamation point '!', the function is assumed to be an inline function; otherwise, it is an ordinary function. You can append a conditional as a suffix, e.g. my_rtt?CONFIG_FOO will call my_rtt, but only if the conditional CONFIG_FOO is set.
frequency says how often the function should be called, in multiples of the system IRQ tick, which is 1ms on WPC. Presently, this value can range from 1 to 2048 ms. It must also be a power of 2.
duration says how long this function takes to run, in the worst case. The scheduler tries to reserve enough CPU cycles for all RTTs and to arrange the order in which they are called so that tasks are balanced. The duration is also given in milliseconds and should be less than 1ms. It can also be specified in CPU cycles by appending the 'c' suffix. It is not critical if the value is not exact, but a reasonable approximation should be listed.
Blank lines and lines beginning with the '#' sign are treated as comments.
By default, the scheduler will unroll the interrupt handler into 8 smaller handlers. That is, over a period of 8ms, the system will cycle through 8 different interrupt handlers. The reason is that this cuts down on the number of checks which have to be performed on every IRQ, since infrequent (greater than 8ms) actions do not need to be tested every time.
If a function is scheduled every 4ms, then it will be called from half of those interrupt handlers (every other one, either the even-numbered ones or the odd- numbered ones). Likewise for functions scheduled every 2ms. Functions scheduled every 1ms are called every time. For functions scheduled 8ms or more, those are all executed from the last of the 8 handlers, and 'if' statements wrap the blocks of code that need to run less often.
Sometimes you may want to unroll a function manually. An example of where this is used is with the switch detection logic. All switches must be polled every 2ms. However, it takes a long time to poll every switch. Instead, we poll half of the switches during 1ms, and the other half on the next 1ms. This spreads out the processing and keeps the length of any one IRQ call from being too long.
To tell the scheduler to do this, first write two separate rtt functions. By convention these are numbered with a suffix, e.g. switch_rtt_0 and switch_rtt_1. Then in the schedule file:
switch_rtt/2 1 280c
The "/2" says that the function was manually unrolled into two separate RTTs. The frequency is now 1ms, because one or the other will be scheduled twice as often now. The duration is the length of time it takes for one of them to run, and is currently assumed to be the same for both.