-
Notifications
You must be signed in to change notification settings - Fork 2
The Plan
Each core has essentially its own kernel (with local storage).
The aim is to eliminate anything that synchonously takes a "long" time from the kernel (meaning anything where interrupts would be enabled, like font drawing or memory management) and delegate that to user mode tasks.
The context for a given process will allow it to be blocked until awoken in response to an event (usually an interrupt), so device drivers, file systems, etc. will start a process running (e.g. a block fetch from a storage device) and block the current thread until that process completes or fails, allowing another thread to execute.
The VDU command interface will be just one possible output stream from a program, each process maintaining its own version of "current working directory", "text foreground colour", etc. Sub-processes inheriting that context should mean that processes shouldn't interfere with each other unintentionally.
Streams will allow senders to block until there's space for them to write the amount of data they need and receivers to block until there's a minimum amount of data for them to read (e.g. the size of a disc block).
There will be no need to re-write filesystems immediately, having them work asynchronously is an aspiration, and even so, multiple cores may well show an improvement in speed since other cores can continue their tasks while the file is read or written.
As I understand it, the Wimp is currently in charge of managing tasking; if a Wimp task starts another program, it's mapped out of memory while the program runs and mapped back in when it completes (or calls Wimp_Initialise to indicate that the two programs can co-exist).
If the program running the new command is not a Wimp task it will be replaced by the new command (unless it takes steps to protect itself, I suppose).
On entry
R0 = code
Others depending on code
On exit
Registers preserved depending on code
May not return
Store the current thread's context and resume the thread given, if not already running. Makes the current thread not runnable (blocked). If the given thread is not blocked (runnable, or even running on a different core), increments its wakeup count
On entry
R0 = 0
On exit
R0 preserved
R1 = task handle
Store the current thread's context and resume the thread given, if not already running. Makes the current thread not runnable (blocked). If the given thread is not blocked (runnable, or even running on a different core), increments its wakeup count
On entry
R0 = 1
R1 = task handle
If the given task is not blocked (runnable, or even running on a different core), increments its wakeup count The current task will continue running
On entry
R0 = 2
R1 = task handle
Store the current thread's context and resume the thread given, if not already running. The current task will remain runnable (so will resume when scheduled, or even continue running if the given task is running on another core). If the given thread is not blocked (runnable, or even running on a different core), increments its wakeup count
On entry
R0 = 1
R1 = task handle
On entry
R0 = 4
R1 = delay in microseconds; 0 => until woken
On exist
R0,R1 preserved
R2 = time remaining (0 if timeout occurred)
R3 = task that interrupted this sleep (0 if timeout occurred)
On entry
R0 = 5
R1 = task handle
R2 -> Register change block
On exist
R0-R2 preserved, change block updated
Register change block
24 words (96 bytes), 12 BIC words, and 12 EOR words
task r0 = (task r0 AND NOT [R2, #0x00]) EOR [R2, #0x30]
[R2, #0x00] = old task r0
task r1 = (task r1 AND NOT [R2, #0x04]) EOR [R2, #0x34]
[R2, #0x08] = old task r1
task r2 = (task r2 AND NOT [R2, #0x08]) EOR [R2, #0x38]
[R2, #0x10] = old task r2
...
task r9 = (task r9 AND NOT [R2, #0x24]) EOR [R2, #0x44]
[R2, #0x16] = old task r9
task pc = (task pc AND NOT [R2, #0x28]) EOR [R2, #0x58]
[R2, #0x16] = old task pc
task spsr = (task spsr AND NOT [R2, #0x2c]) EOR [R2, #0x5c]
[R2, #0x16] = old task spsr
Set the block to all zeros to read values into the first 12 words.
The call MUST be CERTAIN that the task it is accessing is blocked. That is to say that the task must be known to be in the sleeping until woken state (not just waiting for a timeout).
Will fail if the task is not blocked without timeout. (It's up to the driver to implement timeouts for its clients, if desired.)
A device driver module's task should follow this pattern:
Initialise pending requests container
Initialise active requests container
REPEAT
Sleep for a while (possibly forever)
IF timed out THEN
Retry the hardware or inform the client of failure
ELSIF woken by interrupt THEN
Check the hardware
IF a client's active request has completed THEN
Update the client's registers and wake it
ENDIF
Start the next request, if needed
ELSE ; Woken by task making a request
Claim a lock on the pending container
Find the pending request associated with the task that woke this task
Release the lock
Put the client into active requests container
Start the hardware acting on the request
ENDIF
UNTIL FALSE
The module code called by the client will:
Examine the request
IF the request can be handled instantly
Do so
ELSE
Set a callback to:
Claim a lock on the pending container
Add itself to the pending requests container
Release the lock
Wait for the driver task (both blocks the current task and wakes the other)
ENDIF
Since it's a callback, the SVC mode stack will be at its "ready" position, practically empty.