4 ; Preemptive multitasking of idle threads (MDW)
6 ; © 1994-1998 Straylight
9 ;----- Licensing note -------------------------------------------------------
11 ; This file is part of Straylight's Sapphire library.
13 ; Sapphire is free software; you can redistribute it and/or modify
14 ; it under the terms of the GNU General Public License as published by
15 ; the Free Software Foundation; either version 2, or (at your option)
18 ; Sapphire is distributed in the hope that it will be useful,
19 ; but WITHOUT ANY WARRANTY; without even the implied warranty of
20 ; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 ; GNU General Public License for more details.
23 ; You should have received a copy of the GNU General Public License
24 ; along with Sapphire. If not, write to the Free Software Foundation,
25 ; 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
27 ;----- Standard header ------------------------------------------------------
32 ;----- External dependencies ------------------------------------------------
40 ;----- Main code ------------------------------------------------------------
42 AREA |Sapphire$$Code|,CODE,READONLY
47 ; --- thread_create ---
49 ; On entry: R0 == size of stack to allocate, or 0 for a default
50 ; R1 == pointer to thread routine
51 ; R2 == workspace pointer to pass in R10
52 ; R3 == workspace pointer to pass in R12
54 ; On exit: R0 == thread handle for the thread
57 ; Use: Creates a new thread running `in the background' (i.e. over
60 ; The thread is passed control with the registers R10 and R12
61 ; set up from R1 and R2 passed to this routine and R13 pointing
62 ; to the top of a stack chunk. R0 on entry contains the
63 ; thread's handle. The thread is passed the scratchpad
64 ; address (in R11). The values of other registers are
65 ; indeterminate and must not be relied upon.
67 ; The default stack size for a new thread is 1K, although this
68 ; may change in future.
70 ; The thread may exit by calling thread_destroy or by
71 ; returning in the normal way.
76 STMFD R13!,{R1-R8,R12,R14} ;Save some registers
77 WSPACE thr__wSpace ;Load my workspace pointer
79 ; --- Try to allocate the stack ---
81 CMP R0,#0 ;Does he want the default?
82 MOVEQ R0,#thr__stkSize ;Yes -- give it to him
83 CMP R0,#256 ;Make sure it's big enough
84 MOVLT R0,#256 ;If not, make it bigger
85 MOV R5,R0 ;Keep a copy of the size
86 BL alloc ;Try to allocate it nicely
87 BLCS alloc_error ;It failed, so get an error
88 BVS %99thread_create ;And skip to the end
89 MOV R6,R0 ;Keep the stack block ptr
91 ; --- Now allocate a thread block ---
93 MOV R0,#thr__size ;The size of the block
94 BL alloc ;Allocate the block for me
95 BLCS alloc_error ;It failed, so get an error
96 BVS %98thread_create ;If it failed, skip onward
98 ; --- Set up the initial context ---
100 ADD R5,R6,R5 ;R5 is the inital R13 to give
101 MOV R14,R1 ;Get start address in R14
102 MOV R1,R2 ;Move `R10' down a register
103 MOV R2,R11 ;Give scratchpad in R11
104 ADR R4,thread_destroy ;Returning destroys thread
105 STMFD R5!,{R1-R4,R14} ;Save these on the stack
106 STMFD R5!,{R0-R9} ;Fill the rest with rubbish
108 ; --- Fill in the block ---
110 ADD R14,R0,#thr__suspend ;Don't fill in the links
111 STMIA R14,{R1-R8} ;Store information in block
112 MOV R1,#0 ;Thread is not suspended yet
113 MOV R2,#0 ;Thread not blocked on sem
114 MOV R3,#0 ;Start at standard priority
115 MOV R4,#thr__idleFreq ;Set standard timeslice
116 STMIA R14!,{R1-R4} ;Save these in the block
117 MOV R4,#0 ;Not in critical section
118 MOV R7,#0 ;No flags to speak of yet
119 MOV R8,#0 ;No error handler defined
120 STMIA R14!,{R4-R8} ;Fill the rest in too
121 BL thr__insert ;Insert it into the list
123 ; --- Bump the active counter ---
125 BL thr__incCount ;Bump the active counter
126 LDMFD R13!,{R1-R8,R12,R14} ;Restore all the registers
127 BICS PC,R14,#V_flag ;Return without V set
129 ; --- We stumbled across a mishap ---
131 98thread_create MOV R4,R0 ;Keep the error pointer
132 MOV R0,R6 ;Point to the stack block
133 BL free ;Deallocate it -- don't want
134 MOV R0,R4 ;Point to the error again
136 99thread_create ADD R2,R0,#4 ;Point to the error text
137 ADR R0,thr__noCreate ;Point to the error block
138 BL msgs_error ;Set up the error nicely
139 LDMFD R13!,{R1-R8,R12,R14} ;Restore all the registers
140 ORRS PC,R14,#V_flag ;Return with V set
147 ; --- thread_setPriority ---
149 ; On entry: R0 == thread handle
150 ; R1 == new priority to set
154 ; Use: Changes the priority of a thread. The priority if a thread
155 ; is a signed integer. The highest priority thread is the one
156 ; which runs. If more than one thread has maximum priority,
157 ; they are run in a cyclical order.
159 EXPORT thread_setPriority
160 thread_setPriority ROUT
162 STMFD R13!,{R2,R3,R12,R14} ;Save some registers here
163 WSPACE thr__wSpace ;Load my workspace pointer
164 LDR R14,[R0,#thr__priority] ;Get the current priority
165 CMP R1,R14 ;Are we changing anything?
166 LDMEQFD R13!,{R2,R3,R12,PC}^ ;No -- return right now
168 ; --- Unlink the thread ---
170 LDMIA R0,{R2,R3} ;Get the next and previous
171 CMP R2,#0 ;Is there a next pointer?
172 STRNE R3,[R2,#thr__prev] ;Yes -- fill in its prev
173 CMP R3,#0 ;Is there a prev pointer?
174 STRNE R2,[R3,#thr__next] ;Yes -- fill in its next
175 STREQ R2,wsp__threads ;No -- make next first thread
177 ; --- Change priority and insert the thread again ---
179 STR R1,[R0,#thr__priority] ;Save the new priority
180 BL thr__insert ;Insert in the right place
181 LDMFD R13!,{R2,R3,R12,PC}^ ;Return to caller
185 ; --- thread_setTimeSlice ---
187 ; On entry: R0 == thread handle
188 ; R1 == new timeslice size, in centiseconds
192 ; Use: Changes a thread's timeslice size. Specify 0 to indicate
193 ; that thread shouldn't be pre-empted.
195 EXPORT thread_setTimeSlice
196 thread_setTimeSlice ROUT
198 STR R1,[R0,#thr__timeSlice] ;Save the new timeslice
199 MOVS PC,R14 ;Return to caller
204 ; --- thread_destroy ---
206 ; On entry: R0 == thread handle to destroy, if not executing a thread
210 ; Use: Destroys either the current thread or a thread with the
211 ; the given handle if no thread is executing currently. You
212 ; can't destroy an arbitrary thread while running in one.
214 ; If a thread is waiting for a semaphore, it is removed from
217 EXPORT thread_destroy
220 STMFD R13!,{R0-R5,R12,R14} ;Save some registers
221 WSPACE thr__wSpace ;Find my workspace
222 MOV R5,R0 ;Keep the thread pointer
224 ; --- Find out which thread to destroy ---
226 LDR R4,wsp__current ;Get the current thread
227 CMP R4,#0 ;Is there one running?
228 MOVNE R5,R4 ;Yes -- destroy it then
230 ; --- If the thread was active, decrement active count ---
232 LDR R14,[R5,#thr__suspend] ;Get the suspension counter
233 CMP R14,#0 ;Is it currently active?
234 BLEQ thr__decCount ;Yes -- decrement actives
236 ; --- Remove thread from semaphore waiting lists ---
238 LDR R3,[R5,#thr__semaphore] ;Get the blocking semaphore
239 CMP R3,#0 ;Is there one?
240 BEQ %10thread_destroy ;No -- skip this part
242 MOV R2,#0 ;Previous item in the list
243 LDR R0,[R3,#sem__blocked] ;Get the blocked list
244 00 CMP R0,#0 ;Have we reached the end?
245 BEQ %10thread_destroy ;Yes -- skip onwards
246 LDR R14,[R0,#sml__thread] ;Get the waiting thread hnd
247 CMP R14,R5 ;Is it this thread?
248 MOVNE R2,R0 ;No -- update previous ptr
249 LDRNE R0,[R0,#sml__next] ;Find the next link block
250 BNE %00thread_destroy ;And move to next block
252 ; --- Delete this thread waiting block ---
254 LDR R14,[R0,#sml__next] ;Find the next link block
255 CMP R2,#0 ;Is there a previous block?
256 STRNE R14,[R2,#sml__next] ;Yes -- store it as next
257 STREQ R14,[R3,#sem__blocked] ;No -- next is new first item
258 CMP R14,#0 ;Is there a next block?
259 STREQ R2,[R3,#sem__blockEnd] ;No -- previous one is last
261 BL free ;Destroy the link block
263 ; --- Delink the thread then ---
265 10 LDMIA R5,{R1,R2} ;R1 == next, R2 == prev
266 CMP R1,#0 ;Is there a next?
267 STRNE R2,[R1,#thr__prev] ;Yes -- store prev away
268 CMP R2,#0 ;Is there a prev?
269 STRNE R1,[R2,#thr__next] ;Yes -- store next pointer
270 STREQ R1,wsp__threads ;No -- store as list head
272 ; --- Destroy the thread's memory ---
274 LDR R0,[R5,#thr__stack] ;Find the thread's stack
275 BL free ;Get rid of it -- it's no use
276 MOV R0,R5 ;Get the pointer back again
277 MOV R1,#thr__size ;The size of the block
278 BL sub_free ;Destroy the thread block
280 ; --- Now we must return, but where to? ---
282 ; If the caller is not the thread itself, this is easy.
283 ; If it *was* the thread, we've just killed its stack, so
284 ; we can't return to it. Instead, we do the same job as
287 CMP R4,#0 ;Was there a current thread?
288 LDMEQFD R13!,{R0-R5,R12,PC}^ ;No -- return normally
290 MOV R0,#0 ;We're stopping current thrd
291 STR R0,wsp__current ;So clear current thread
293 BL thr__end ;Kill off any handlers
294 LDR R13,wsp__stackPtr ;Get the system stack pointer
295 LDMFD R13!,{R0-R12,PC}^ ;Return to thread dispatcher
299 ; --- thr__decCount ---
305 ; Use: Decrements the active threads counter, and disables the idle
306 ; claimer if there aren't any left.
310 STMFD R13!,{R0-R3,R14} ;Save registers
311 LDR R14,wsp__active ;Get the active counter
312 SUBS R14,R14,#1 ;Decrement the counter
313 STR R14,wsp__active ;Store it back again
314 LDMGTFD R13!,{R0-R3,PC}^ ;If some still active, return
316 ; --- Remove the idle claiming routine ---
318 MOV R0,#thr__idleFreq ;How often I should be called
319 ADR R1,thr__idles ;Point to the idle handler
320 MOV R2,#0 ;Don't care about R10
321 MOV R3,R12 ;Pass workspace in R12
322 BL idle_removeHandler ;Stop it from being called
324 LDMFD R13!,{R0-R3,PC}^ ;Return to caller
328 ; --- thr__incCount ---
334 ; Use: Increments the active threads counter, and adds in the idle
335 ; claimer if it was previously disabled.
339 STMFD R13!,{R0-R3,R14} ;Save registers
340 LDR R14,wsp__active ;Get the active counter
341 ADD R14,R14,#1 ;Increment the counter
342 STR R14,wsp__active ;Store it back again
343 CMP R14,#1 ;Was it previously off?
344 LDMGTFD R13!,{R0-R3,PC}^ ;No -- return right now
346 ; --- Add in the idle claiming routine ---
348 MOV R0,#thr__idleFreq ;How often I should be called
349 ADR R1,thr__idles ;Point to the idle handler
350 MOV R2,#0 ;Don't care about R10
351 MOV R3,R12 ;Pass workspace in R12
352 BL idle_handler ;Get it called nicely
353 LDMFD R13!,{R0-R3,PC}^ ;Return to caller
363 ; Use: Handles idle events by passing control to each thread in
364 ; turn. This is the main scheduler for the threads.
366 ; We use a simple but well-respected algorithm. We find the
367 ; first active thread in the list, move it to the end of its
368 ; priority group and run it.
372 ; --- Save current context away ---
374 ; We don't actually return from this routine -- actually
375 ; we restore from thread_destroy, thread_suspend or
376 ; thread_yield, or on the CallBack from a timer interrupt.
378 STMFD R13!,{R0-R12,R14} ;Save context on the stack
379 STR R13,wsp__stackPtr ;Save the stack pointer
381 ; --- Now we need to find an unsuspended thread ---
383 ; There must be an active thread, because we don't get called
384 ; unless the count is non-0
386 LDR R10,wsp__threads ;Find the first thread
387 10thr__idles CMP R10,#0 ;Is this the end?
388 LDMEQFD R13!,{R0-R12,PC}^ ;Yes -- nothing to do then
389 LDR R0,[R10,#thr__suspend] ;Get the thread's suspend
390 CMP R0,#0 ;Is this one active?
391 LDRNE R10,[R10,#thr__next] ;No -- get the next one out
392 BNE %10thr__idles ;And go round again
394 ; --- We have an active thread ---
396 ; We now unlink the thread and move it to the end of its
397 ; priority group in the list, so next time the next one in
398 ; the group gets a chance.
400 LDR R9,[R10,#thr__priority] ;Get the thread's priority
401 LDMIA R10,{R0,R1} ;Get next and prev pointers
402 CMP R0,#0 ;Is there a next pointer?
403 STRNE R1,[R0,#thr__prev] ;Yes -- fill in its prev
404 CMP R1,#0 ;Is there a prev pointer?
405 STRNE R0,[R1,#thr__next] ;Yes -- fill in its next
406 STREQ R0,wsp__threads ;No -- make it the new first
408 ; --- Now search forwards for the end of the group ---
410 20thr__idles CMP R0,#0 ;Is this the list end?
411 BEQ %25thr__idles ;Yes -- skip out of loop
412 LDR R8,[R0,#thr__priority] ;Get the next one's priority
413 CMP R8,R9 ;Do they match up nicely?
414 MOVGE R1,R0 ;This is the previous one
415 LDRGE R0,[R0,#thr__next] ;Get the next one out
416 BGE %20thr__idles ;And loop round again
418 ; --- Insert our thread between R1 and R0 ---
420 25thr__idles CMP R1,#0 ;Is the previous one OK?
421 STRNE R10,[R1,#thr__next] ;Yes -- fill in its next
422 STREQ R10,wsp__threads ;Otherwise make it first one
423 CMP R0,#0 ;Is there a next one?
424 STRNE R10,[R0,#thr__prev] ;Yes -- fill in its previous
425 STMIA R10,{R0,R1} ;Save next and prev back
427 ; --- Now we can run the thread at last ---
429 LDR R0,[R10,#thr__timeSlice] ;Load the timeslice we want
430 BL thr__start ;Set up timer interrupt
431 STR R10,wsp__current ;Save the current thread
432 LDR R13,[R10,#thr__stackPtr] ;Find its stack pointer
433 LDMFD R13!,{R0-R12,R14,PC}^ ;Start it up again
437 ; --- thr__insert ---
439 ; On entry: R0 == pointer to thread block
443 ; Use: Inserts a thread into the thread list in the right place for
448 STMFD R13!,{R1-R4,R14} ;Save some registers
450 LDR R4,[R0,#thr__priority] ;Get the thread;s priority
451 LDR R1,wsp__threads ;Find the first item
452 MOV R2,#0 ;No previous list item yet
453 10thr__insert CMP R1,#0 ;Is this the end of the list?
454 BEQ %20thr__insert ;Yes -- skip forwards
455 LDR R3,[R1,#thr__priority] ;Get its priority ready
456 CMP R3,R4 ;How do they compare?
457 MOVGT R2,R1 ;Too high -- now previous
458 LDRGT R1,[R1,#thr__next] ;Find the next thread handle
459 BGT %10thr__insert ;And loop round for another
461 ; --- Insert the thread between R2 and R1 ---
463 20thr__insert CMP R2,#0 ;Is there a previous one?
464 STRNE R0,[R2,#thr__next] ;Yes -- fill in its next
465 STREQ R0,wsp__threads ;No -- this is the first one
466 CMP R1,#0 ;Is there a next one?
467 STRNE R0,[R1,#thr__prev] ;Yes -- fill in its prev
468 STMIA R0,{R1,R2} ;Store next and prev in block
469 LDMFD R13!,{R1-R4,PC}^ ;Return to caller then
473 ; --- thread_suspend ---
475 ; On entry: R0 == thread handle, or 0 for the current thread
479 ; Use: Suspends a thread's execution. If a thread is currently
480 ; running, that thread is suspended. Otherwise, any thread
483 ; If the thread is currently claiming semaphores, the
484 ; semaphores are not released, because we don't whether the
485 ; system is in a fit state for this.
487 ; Thread suspensions are counted. i.e. if you suspend a thread
488 ; 5 times, you have to resume it 5 times for it to become
491 EXPORT thread_suspend
494 STMFD R13!,{R12,R14} ;Save some registers away
495 WSPACE thr__wSpace ;Locate my workspace
496 LDR R14,wsp__current ;Get the current thread
497 CMP R0,#0 ;Is current one wanted?
498 CMPNE R0,R14 ;Or is it just coincidence?
499 BNE %10thread_suspend ;No -- deal with that case
501 ; --- We want to suspend the current thread ---
503 ; To suspend, we need to save the current thread context,
504 ; and resume from the system stack. We know the thread must
505 ; be active currently, otherwise we wouldn't be executing
508 STMFD R13!,{R0-R12} ;Save entire context on stack
509 STR R13,[R14,#thr__stackPtr] ;Save the thread's stack ptr
510 MOV R0,#1 ;Thread must be active
511 STR R0,[R14,#thr__suspend] ;So store 1 as suspend count
512 BL thr__decCount ;There's one less active now
514 ; --- Now restore context to dispatcher ---
516 BL thr__end ;Stop all the handlers
517 MOV R0,#0 ;There is no current thread
518 STR R0,wsp__current ;So clear current pointer
519 LDR R13,wsp__stackPtr ;Get the system stack pointer
520 LDMFD R13!,{R0-R12,PC}^ ;Restore context again
522 ; --- We're just meant to suspend any old thread ---
524 ; We just need to bump its counter -- this is easy ---
526 10 LDR R14,[R0,#thr__suspend] ;Get current suspend count
527 ADD R14,R14,#1 ;Bump it up one
528 STR R14,[R0,#thr__suspend] ;Store it back again
529 CMP R14,#1 ;Was it previously active?
530 BLEQ thr__decCount ;Yes -- decrement actives
531 LDMFD R13!,{R12,PC}^ ;Return to caller happy
535 ; --- thread_resume ---
537 ; On entry: R0 == thread handle
541 ; Use: Allows a suspended thread to continue operations. If you
542 ; resume a thread more times than it has been suspended,
543 ; any excess resumes are ignored. You can't resume a thread
544 ; to stop it being blocked by a semaphore.
549 STMFD R13!,{R10,R12,R14} ;Save some registers
550 WSPACE thr__wSpace ;Find my workspace address
552 ; --- Make sure the thread isn't running now ---
554 LDR R14,[R0,#thr__suspend] ;Get the suspension counter
555 CMP R14,#0 ;Is it zero already?
556 LDMEQFD R13!,{R10,R12,PC}^ ;Yes -- return right now
558 ; --- Decrement the counter ---
560 LDR R10,[R0,#thr__semaphore] ;Get the blocking semaphore
561 CMP R10,#0 ;Is there a blocking sem?
562 MOVEQ R10,#0 ;No -- minimum count is 0
563 MOVNE R10,#1 ;Yes -- minimum count is 1
564 SUB R14,R14,#1 ;Decrement the counter
565 CMP R14,R10 ;Is it too low?
566 MOVLT R14,R10 ;Yes -- bring it up again
567 STR R14,[R0,#thr__suspend] ;Store the counter back again
569 ; --- Bump the active count if thread now active ---
571 CMP R14,#0 ;Is the thread active now?
572 BLEQ thr__incCount ;Yes -- increment the count
573 CMP R14,#1 ;Is the thread almost active?
574 LDMNEFD R13!,{R10,R12,PC}^ ;No -- return right now
576 ; --- Check if the thread can start from a semaphore ---
578 STMFD R13!,{R0-R2} ;Save some registers
579 MOV R10,R0 ;Look after the thread handle
580 LDR R14,[R10,#thr__semaphore] ;Is it waiting for a sem?
581 CMP R14,#0 ;Is the pointer null?
582 BEQ %20thread_resume ;Yes -- skip to end
584 LDR R0,[R14,#sem__counter] ;Is it signalled enough?
585 CMP R0,#0 ;If so, it isn't 0
586 BEQ %20thread_resume ;If not, skip to the end
588 ; --- Go through the waiting list ---
590 ; We want to find the entry in the list, but we also want
591 ; to avoid `jumping the queue' -- i.e. getting the semaphore
592 ; before an unsuspended thread. With the algorithms used
593 ; in thread_signal, I don't think this can happen, but it's
594 ; as well to make sure. We have to go through the list
597 LDR R0,[R14,#sem__blocked] ;Find the blocked list
598 MOV R2,#0 ;No previous block found
599 10 CMP R0,#0 ;Is it the end of the list?
600 BEQ %20thread_resume ;Yes -- it's all over then
601 LDR R1,[R0,#sml__thread] ;Get the thread's handle
602 CMP R1,R10 ;Do they match up?
603 BEQ %11thread_resume ;Yes -- skip onwards
604 LDR R1,[R1,#thr__suspend] ;Get the suspended count
605 CMP R1,#0 ;Is this thread active?
606 BEQ %20thread_resume ;Yes -- skip to end
607 MOV R2,R0 ;Set up the previous pointer
608 LDR R0,[R0,#sml__next] ;Get the next block
609 B %10thread_resume ;And go back round the loop
611 ; --- Remove it from the waiting list ---
613 11thread_resume LDR R1,[R0,#sml__next] ;Get the next list item
614 CMP R2,#0 ;Is there a previous one?
615 STRNE R1,[R2,#sml__next] ;Yes -- fill in its next ptr
616 STREQ R1,[R14,#sem__blocked] ;No -- it's the new list head
617 CMP R1,#0 ;Is there a next block?
618 STREQ R2,[R14,#sem__blockEnd] ;No -- fill in the last ptr
620 MOV R2,R14 ;Keep the semaphore pointer
621 MOV R1,#sml__size ;Size of the link blocks
622 BL sub_free ;Destroy the link block
624 ; --- Now decrement the semaphore counter ---
626 LDR R0,[R2,#sem__counter] ;Get the current counter
627 SUB R0,R0,#1 ;Decrement it nicely
628 STR R0,[R2,#sem__counter] ;Store it back again
629 MOV R0,#0 ;Thread not suspended at all
630 STR R0,[R10,#thr__suspend] ;Store 0 as suspend count
631 BL thr__incCount ;Increment active threads
633 20thread_resume LDMFD R13!,{R0-R2,R10,R12,PC}^ ;Return to caller
637 ; --- thread_yield ---
643 ; Use: Pauses the thread for a while. You only need to use this
644 ; call if you have stopped the current thread from being
650 STMFD R13!,{R0-R14} ;Save the old context
651 WSPACE thr__wSpace ;Find my workspace pointer
652 LDR R0,wsp__current ;Get the current thread ptr
653 BL thr__end ;Kill off the handlers
654 STR R13,[R0,#thr__stackPtr] ;Save the thread's stack ptr
655 MOV R0,#0 ;Current thread is stopped
656 STR R0,wsp__current ;So clear the pointer
657 LDR R13,wsp__stackPtr ;Get the system stack pointer
658 LDMFD R13!,{R0-R12,PC}^ ;Restore main system context
662 ; --- thread_createSem ---
664 ; On entry: R0 == initial value for semaphore (0 for counter, 1 for
667 ; On exit: R0 == semaphore handle and V clear if all went well
668 ; R0 == pointer to error and V set if something went wrong
670 ; Use: Creates a semaphore with the given initial counter value.
672 ; The semaphore can be used to provide serialised access to
673 ; a resource by initialising its value to 1 and performing the
676 ; thread_wait(mySemaphore)
678 ; // Do things with the resource
680 ; thread_signal(mySemaphore)
682 ; Or you can inform a thread that it has items in its input
683 ; queue by having the following in the thread code:
686 ; thread_wait(theSemaphore)
687 ; getFromQueue(myQueue,item)
691 ; and when inserting queue items:
694 ; thread_signal(theSemaphore)
696 ; It is distinctly possible that input queue management will
697 ; be introduced in a separate Sapphire module.
699 EXPORT thread_createSem
700 thread_createSem ROUT
702 STMFD R13!,{R0-R3,R14} ;Save some registers
703 MOV R0,#sem__size ;The size of a semaphore
704 BL sub_alloc ;Try to create the semaphore
705 BVS %99thread_createSem ;If it failed, tidy up
707 LDR R1,[R13],#4 ;Get the initial sem value
708 MOV R2,#0 ;No threads waiting either
709 MOV R3,#0 ;So no last link block
710 STMIA R0,{R1-R3} ;Save in semaphore block
711 LDMFD R13!,{R1-R3,PC} ;Return to caller
713 ; --- Error occurred ---
715 99 ADD R2,R0,#4 ;Point to the error text
716 ADR R0,thr__noSemCrt ;Point to error block
717 BL msgs_error ;Translate the error message
718 ADD R13,R13,#4 ;Don't restore R0 from stack
719 LDMFD R13!,{R1-R3,PC} ;Return with V set on exit
726 ; --- thread_destroySem ---
728 ; On entry: R0 == semaphore handle
732 ; Use: Destroys a semaphore when it's no use any more. If threads
733 ; are waiting for it, an error is generated.
735 EXPORT thread_destroySem
736 thread_destroySem ROUT
738 STMFD R13!,{R1,R14} ;Save some registers away
739 LDR R14,[R0,#sem__blocked] ;Get the waiting list head
740 CMPEQ R14,#0 ;Is there a waiting list?
741 BNE %90thread_destroySem ;If so, complain
743 MOV R1,#sem__size ;The size of a semaphore
744 BL sub_free ;Free up the block
745 LDMFD R13!,{R1,PC}^ ;Return to caller
747 ; --- The semaphore is still in use ---
749 90 ADR R0,thr__semInUse ;Point to error message
750 BL msgs_error ;Translate the message
751 SWI OS_GenerateError ;It's a serious problem
758 ; --- thread_threaded ---
762 ; On exit: CS if currently running a thread, CC otherwise
764 ; Use: Informs the caller whether a thread is currently executing.
766 EXPORT thread_threaded
769 STMFD R13!,{R12} ;Save a register
770 WSPACE thr__wSpace ;Load my workspace address
771 LDR R12,wsp__current ;Load current thread handle
772 CMP R12,#0 ;Is there a thread running?
773 LDMFD R13!,{R12} ;Restore the register
774 ORRNES PC,R14,#C_flag ;Return CS if thread running
775 BICEQS PC,R14,#C_flag ;Otherwise return CC
779 ; --- thread_wait ---
781 ; On entry: R0 == semaphore handle
783 ; On exit: If successful, R0 preserved and V clear.
784 ; If failed, R0 == pointer to error block and V set
786 ; Use: Waits on a sempahore. The algorithm actually is as follows:
788 ; if semaphore.counter == 0 then
789 ; addToWaitingList(semaphore,currentThread)
790 ; suspend(currentThread)
792 ; semaphore.counter -= 1
795 ; See thread_createSem for suggestions on how to make use of
801 BIC R14,R14,#V_flag ;Assume that all is well
802 STMFD R13!,{R10,R12-R14} ;Save some registers
803 WSPACE thr__wSpace ;Locate my workspace
804 LDR R10,wsp__current ;Find the current thread
805 CMP R10,#0 ;Is it nonexistant?
806 BEQ %99thread_wait ;Yes -- this is an error
808 ; --- Do the messing about with the counter ---
810 LDR R14,[R0,#sem__counter] ;Get the semaphore counter
811 SUBS R14,R14,#1 ;Decrement the counter
812 STRGE R14,[R0,#sem__counter] ;If it was nonzero, store
813 LDMEQFD R13!,{R10,R12,R14,PC}^ ;And return to caller
815 ; --- Add the thread to the waiting list ---
817 STMFD R13!,{R0-R9} ;Save rest of the context
818 MOV R9,R0 ;Keep the semaphore handle
819 MOV R0,#sml__size ;Size of a link block
820 BL sub_alloc ;Allocate a link block
821 BVS %90thread_wait ;If it failed, return error
823 MOV R1,#0 ;No next waiting thread
824 STMIA R0,{R1,R10} ;Save information in block
825 LDR R1,[R9,#sem__blockEnd] ;Get the last waiting block
826 CMP R1,#0 ;Is there one at all?
827 STRNE R0,[R1,#sml__next] ;Yes -- store in its next
828 STREQ R0,[R9,#sem__blocked] ;No -- it's the first one
829 STR R0,[R9,#sem__blockEnd] ;It's certainly the last one
831 ; --- Now suspend the thread for a while ---
833 MOV R1,#1 ;Thread is currently active
834 STR R1,[R10,#thr__suspend] ;Not any more it isn't
835 STR R9,[R10,#thr__semaphore] ;Remember the blocking sem
837 ; --- Now switch back to main context ---
839 BL thr__end ;Remove all the handlers
840 STR R13,[R10,#thr__stackPtr] ;Save the current stackptr
841 LDR R13,wsp__stackPtr ;Find the system stack ptr
842 LDMFD R13!,{R0-R12,PC}^ ;And return to normality
844 ; --- An error occurred while suspending ---
846 90thread_wait ADD R2,R0,#4 ;Point to the error text
847 ADR R0,thr__noWaitSem ;Point to the error message
848 BL msgs_error ;Translate the error
849 ADD R13,R13,#4 ;Don't restore R0 on exit
850 LDMFD R13!,{R1-R12,R14} ;Restore all the registers
851 ORRS PC,R14,#V_flag ;Return the error back
856 ; --- The caller isn't a thread ---
858 99thread_wait ADR R0,thr__notAThrd ;Point to the error
859 BL msgs_error ;Translate the message
860 SWI OS_GenerateError ;And generate the error
867 ; --- thread_signal ---
869 ; On entry: R0 == semaphore handle
873 ; Use: Increments a semaphore's counter if no threads are waiting
874 ; for it, or releases a thread waiting for the semaphore.
876 ; The actual algorithm is shown below:
878 ; if semaphore.waitingList != 0 then
879 ; thread = removeFromWaitingList(semaphore)
882 ; semaphore.counter += 1;
885 ; See thread_createSem for suggestions on how to make use of
891 STMFD R13!,{R0-R3,R10,R12,R14} ;Save a load of registers
892 WSPACE thr__wSpace ;Locate my workspace
894 ; --- Find a thread to restore control to ---
896 MOV R3,R0 ;Look after the sem handle
897 MOV R2,#0 ;No previous block yet
898 LDR R0,[R3,#sem__blocked] ;Find blocked list head
900 10thread_signal CMP R0,#0 ;Is this the end?
901 BEQ %30thread_signal ;Yes -- increment counter
902 LDR R10,[R0,#sml__thread] ;Get the thread handle out
903 LDR R14,[R10,#thr__suspend] ;Find its suspended count
904 CMP R14,#1 ;Is it only blocked by sem?
905 MOVGT R2,R0 ;No -- this is now prev
906 LDRGT R0,[R0,#sml__next] ;Find the next one
907 BGT %10thread_signal ;And check that one out
909 ; --- Found a suitable thread ---
911 LDR R14,[R0,#sml__next] ;Get the next pointer out
912 CMP R2,#0 ;Is there a previous block?
913 STRNE R14,[R2,#sml__next] ;Yes -- fix its next ptr
914 STREQ R14,[R3,#sem__blocked] ;No -- this is now first one
915 CMP R14,#0 ;Is there a next block?
916 STREQ R2,[R3,#sem__blockEnd] ;Yes -- previous is now last
918 MOV R1,#sml__size ;The size of a link block
919 BL sub_free ;Don't need it any more
921 ; --- Let the new thread go ---
923 MOV R0,#0 ;Thread is no longer blocked
924 STR R0,[R10,#thr__suspend] ;Unblock the thread nicely
925 STR R0,[R10,#thr__semaphore] ;No blocking semaphore now
926 LDMFD R13!,{R0-R3,R10,R12,PC}^ ;Return to caller happily
928 ; --- No threads found -- increment the counter ---
930 30thread_signal LDR R14,[R3,#sem__counter] ;Get the current counter
931 ADD R14,R14,#1 ;Increment it a little bit
932 STR R14,[R3,#sem__counter] ;Store new counter back
933 LDMFD R13!,{R0-R3,R10,R12,PC}^ ;Return to caller happily
937 ; --- thread_init ---
943 ; Use: Initialises the thread system for use.
948 STMFD R13!,{R12,R14} ;Save some registers
950 ; --- Make sure we're not initialised ---
952 WSPACE thr__wSpace ;Find my workspace pointer
953 LDR R14,wsp__flags ;Load my flags word
954 TST R14,#thrFlag__inited ;Am I initialised yet?
955 LDMNEFD R13!,{R12,PC}^ ;Yes -- return right now
956 ORR R14,R14,#thrFlag__inited ;Set the flag
957 STR R14,wsp__flags ;Save the flags word back
959 ; --- Initialise the rest of the workspace ---
961 STMFD R13!,{R0-R4} ;Save some more registers
962 MOV R0,#0 ;No threads registered yet
963 MOV R1,#0 ;No active threads, then
964 MOV R2,#0 ;No thread running now
965 MOV R3,#0 ;No system stack pointer
966 STMIB R12,{R0-R3,R11} ;Save them in the workspace
968 ; --- Start up other things we need ---
970 BL alloc_init ;We need redirectble alloc
971 BL sub_init ;And allocating small blocks
973 LDMFD R13!,{R0-R4,R12,PC}^ ;Return to caller
979 ;----- Handling pre-emption of threads --------------------------------------
983 ; On entry: R0 == timeslice size in centiseconds
987 ; Use: Sets up handlers and a timer interrupt for starting a
992 STMFD R13!,{R0-R4,R14} ;Save some registers away
993 ADR R4,wsp__handlers ;Point to handlers save area
995 ; --- Set up the handlers properly ---
997 MOV R0,#7 ;Install CallBack handler
998 ADR R1,thr__callBack ;Point to handler routine
999 MOV R2,R12 ;Point to my workspace
1000 ADR R3,wsp__regBuff ;Point to the register block
1001 SWI OS_ChangeEnvironment ;Install the handler
1002 STMIA R4!,{R1-R3} ;Save the old handler
1004 MOV R0,#10 ;Install event handler
1005 ADR R1,thr__events ;Point to handler routine
1006 MOV R2,R12 ;Point to my workspace
1007 SWI OS_ChangeEnvironment ;Install the handler
1008 STMIA R4!,{R1-R3} ;Save the old handler
1010 MOV R0,#6 ;Install error handler
1011 ADR R1,thr__errors ;Point to handler routine
1012 MOV R2,R12 ;Point to my workspace
1013 MOV R3,R11 ;Use Scratchpad for error
1014 SWI OS_ChangeEnvironment ;Install the handler
1015 STMIA R4!,{R1-R3} ;Save the old handler
1017 MOV R0,#11 ;Install exit handler
1018 ADR R1,thr__exit ;Point to handler routine
1019 MOV R2,R12 ;Point to my workspace
1020 SWI OS_ChangeEnvironment ;Install the handler
1021 STMIA R4!,{R1-R3} ;Save the old handler
1023 ; --- Enable my event ---
1025 MOV R0,#14 ;Enable an event
1026 MOV R1,#9 ;I'll use the user event
1027 SWI OS_Byte ;Enable the event for me
1029 ; --- Set up the timer for me ---
1031 LDR R0,[R13,#0] ;Load the timeslice value
1032 CMP R0,#0 ;Do we set the timer?
1033 ADRNE R1,thr__timer ;Point to timer routine
1034 MOVNE R2,R12 ;Point to my workspace
1035 SWINE OS_CallAfter ;Install the handler then
1037 LDMFD R13!,{R0-R4,PC}^ ;Return to caller
1047 ; Use: Removes all the handlers set up when a thread starts
1051 STMFD R13!,{R0-R4,R14} ;Save some registers
1053 ; --- Restore handlers to their old values ---
1055 ADR R4,wsp__handlers ;Point to saved handlers
1056 MOV R0,#7 ;Restore CallBack handler
1057 LDMIA R4!,{R1-R3} ;Restore handler registers
1058 SWI OS_ChangeEnvironment ;Put them all back then
1059 MOV R0,#10 ;Restore Event handler
1060 LDMIA R4!,{R1-R3} ;Restore handler registers
1061 SWI OS_ChangeEnvironment ;Put them all back then
1062 MOV R0,#6 ;Restore Error handler
1063 LDMIA R4!,{R1-R3} ;Restore handler registers
1064 SWI OS_ChangeEnvironment ;Put them all back then
1065 MOV R0,#11 ;Restore Exit handler
1066 LDMIA R4!,{R1-R3} ;Restore handler registers
1067 SWI OS_ChangeEnvironment ;Put them all back then
1069 MOV R0,#13 ;Disable an event
1070 MOV R1,#9 ;I'm using the user event
1071 SWI OS_Byte ;Disable it nicely
1073 ; --- Get rid of my ticker event ---
1075 ; We try and remove it -- if it was never there anyway,
1076 ; or it's happened, we don't care.
1078 ADR R0,thr__timer ;Point to my timer handler
1079 MOV R1,R12 ;Get my workspace pointer
1080 SWI XOS_RemoveTickerEvent ;Remove the ticker event
1082 ; --- Kill off the `In error' flag ---
1084 LDR R14,wsp__flags ;Load my flags word
1085 BIC R14,R14,#thrFlag__inErr ;Clear the flag bit
1086 STR R14,wsp__flags ;Store it back again then
1088 LDMFD R13!,{R0-R4,PC}^ ;Return to caller
1092 ; --- thr__errors ---
1094 ; On entry: R0 == pointer to my workspace
1098 ; Use: Handles an error in a thread.
1102 SWI OS_IntOff ;Disable interrupts a while
1103 MOV R12,R0 ;Point to my workspace
1104 LDR R11,wsp__R11 ;Load scratchpad address
1105 LDR R10,wsp__current ;Find the current thread
1106 MOV R1,#1 ;Enter a critical section
1107 STR R1,[R10,#thr__critCount] ;Save critical counter
1108 SWI OS_IntOn ;Safe to interrupt again
1109 ADD R9,R10,#thr__errorHnd ;Locate the error handler
1110 LDR R5,[R9],#4 ;Get the handler address
1111 CMP R5,#0 ;Is there one set up?
1112 BEQ %50thr__errors ;No -- skip round next bit
1114 ; --- Am I already doing this? ---
1116 LDR R14,wsp__flags ;Load my flags word
1117 TST R14,#thrFlag__inErr ;Am I already going?
1118 BNE %50thr__errors ;Yes -- skip round next bit
1120 ; --- Handle the error and find resume point ---
1122 ORR R14,R14,#thrFlag__inErr ;Remember I'm doing this
1123 STR R14,wsp__flags ;Save it back again
1124 ADD R0,R11,#4 ;Point to error block
1125 LDMIA R9,{R1,R13} ;Get registers to pass out
1126 STMFD R13!,{R12} ;Save the old R12 away
1127 MOV R12,R1 ;Pass its workspace pointer
1128 MOV R14,PC ;Set up return address
1129 MOV PC,R5 ;Call the handler
1131 ; --- Call the resume point ---
1133 LDMFD R13!,{R12} ;Restore my workspace
1134 LDR R14,wsp__flags ;Find my flags again
1135 BIC R14,R14,#thrFlag__inErr ;We're coming out again
1136 STR R14,wsp__flags ;Store the flags back nicely
1137 BL thread_leaveCrit ;Leave the critical section
1138 MOV R12,R1 ;Set up resumer's workspace
1139 MOV PC,R0 ;Call the resumer
1141 ; --- It all went wrong ---
1143 50thr__errors LDR R13,wsp__stackPtr ;Get the system stack pointer
1144 MOV R0,#0 ;No current thread
1145 STR R0,wsp__current ;So clear the pointer
1146 BL thr__end ;Remove all its handlers
1147 MOV R0,R10 ;Point to the thread
1148 BL thread_destroy ;Kill off the thread
1149 ADD R0,R11,#4 ;Point to error block
1150 SWI OS_GenerateError ;Pass to main handler
1156 ; On entry: R12 == my workspace address
1160 ; Use: Handles OS_Exit calls in a thread. Closes down the thread
1161 ; and propagates the exit
1165 LDR R11,wsp__R11 ;Load scratchpad pointer
1166 SWI OS_IntOff ;Disable interrupts off
1167 LDR R13,wsp__stackPtr ;Get the system stack pointer
1168 BL thr__end ;Remove all its handlers
1169 SWI OS_IntOn ;Save to interrupt again
1170 LDR R0,wsp__current ;Get the current thread
1171 MOV R1,#0 ;No current thread
1172 STR R1,wsp__current ;So clear the pointer
1173 BL thread_destroy ;Destroy the thread
1178 ; --- thr__callBack ---
1180 ; On entry: R12 == pointer to my workspace
1184 ; Use: Performs appropriate operations on receipt of a timer
1185 ; interrupt. If the thread is in a critical section, it is
1186 ; marked `should be stopped'. Otherwise, the context is
1187 ; saved on the thread's stack and control returned to the
1192 LDR R10,wsp__current ;Get the current thread
1193 LDR R14,[R10,#thr__critCount] ;Is it in a critical bit?
1194 CMP R14,#0 ;If so, this is <>0
1195 BEQ %10thr__callBack ;Otherwise skip forwards
1197 ; --- Set the thread's flag and continue ---
1199 LDR R14,[R10,#thr__flags] ;Get the thread's flags word
1200 ORR R14,R14,#tFlag__timeUp ;Kill it on exit from crit
1201 STR R14,[R10,#thr__flags] ;Store flags word back again
1202 ADR R14,wsp__regBuff ;Point to save buffer
1203 LDMIA R14,{R0-R14}^ ;Get the user registers out
1204 MOV R0,R0 ;Otherwise ARM complains
1205 LDR R14,[R14,#15*4] ;Load the saved PC
1206 MOVS PC,R14 ;Return to the thread
1208 ; --- Save the thread's context ---
1210 10thr__callBack ADR R14,wsp__regBuff ;Point to save buffer
1211 LDR R11,[R14,#13*4] ;Find the thread's stack ptr
1212 SUB R11,R11,#15*4 ;Make way for the context
1213 STR R11,[R10,#thr__stackPtr] ;Save the thread's stack ptr
1217 STMIA R11!,{R0-R4,R6,R7} ;Don't transfer R13
1219 ; --- Go back to the main program ---
1221 LDR R11,wsp__stackPtr ;Find the system stack ptr
1222 LDR R14,[R11,#13*4] ;Get the saved link register
1223 TEQP R14,#0 ;Set this as the current mode
1224 MOV R0,R0 ;Stop ARM from being odd
1225 MOV R13,R11 ;Point to the stack properly
1226 BL thr__end ;Stop all the handlers
1227 LDMFD R13!,{R0-R12,PC}^ ;Rejoin the main thread
1231 ; --- thr__events ---
1233 ; On entry: R0 == event code (ideally this is 9)
1234 ; R1 == my magic identifier number (just this thing, y'know)
1236 ; On exit: R12 may contain 1
1238 ; Use: Handles events. If it's the magic timer event, then I get
1239 ; myself a CallBack.
1243 CMP R0,#9 ;Is it a user event?
1244 MOVNES PC,R14 ;No -- return right away
1245 STMFD R13!,{R14} ;Save the link away
1246 LDR R14,thr__myMagic ;Get my magic number
1247 CMP R1,R14 ;Does it match up?
1248 MOVEQ R12,#1 ;Yes -- request a CallBack
1249 LDMFD R13!,{PC}^ ;Return to caller
1253 thr__myMagic DCB "MDW!"
1255 ; --- thr__timer ---
1261 ; Use: Generates an event that can eventually wend its way to my
1266 STMFD R13!,{R0,R1,R14} ;Save some registers
1267 MOV R0,#9 ;Make a user event
1268 LDR R1,thr__myMagic ;My special identifier
1269 SWI OS_GenerateEvent ;Make the event happen
1270 LDMFD R13!,{R0,R1,PC}^ ;Return to caller
1274 ; --- thread_enterCrit ---
1280 ; Use: Declares that the current thread is about to enter a
1281 ; critical section and must not be interrupted.
1283 EXPORT thread_enterCrit
1284 thread_enterCrit ROUT
1286 STMFD R13!,{R10,R12,R14} ;Save some registers away
1287 WSPACE thr__wSpace ;Find my workspace
1288 LDR R10,wsp__current ;Get the current thread
1289 CMP R10,#0 ;Is there one?
1290 LDMEQFD R13!,{R10,R12,PC}^ ;No -- this is a no-op then
1291 LDR R14,[R10,#thr__critCount] ;Get the current counter
1292 ADD R14,R14,#1 ;Bump it along one
1293 STR R14,[R10,#thr__critCount] ;Save the counter back
1294 LDMFD R13!,{R10,R12,PC}^ ;Return to caller
1298 ; --- thread_leaveCrit ---
1304 ; Use: Declares that the current thread has left the critical
1305 ; section and can be interrupted again.
1307 EXPORT thread_leaveCrit
1308 thread_leaveCrit ROUT
1310 STMFD R13!,{R10,R12-R14} ;Save some registers away
1311 WSPACE thr__wSpace ;Find my workspace
1312 LDR R10,wsp__current ;Get the current thread
1313 CMP R10,#0 ;Is there one?
1314 LDMEQFD R13!,{R10,R12,R14,PC}^ ;No -- this is a no-op then
1315 LDR R14,[R10,#thr__critCount] ;Get the current counter
1316 SUBS R14,R14,#1 ;Chop one off it
1317 MOVLT R14,#0 ;Stop it going negative
1318 STR R14,[R10,#thr__critCount] ;Save the counter back
1319 LDMLEFD R13!,{R10,R12,R14,PC}^ ;If still critical, leave it
1321 ; --- Check to see if it should stop now ---
1323 LDR R14,[R10,#thr__flags] ;Get the thread's flags
1324 TST R14,#tFlag__timeUp ;Has the timer happened?
1325 LDMEQFD R13!,{R10,R12,R14,PC}^ ;No -- leave it to go then
1326 BL thr__end ;Stop the handlers and things
1327 STMFD R13!,{R0-R9} ;Save rest of the context
1328 STR R13,[R10,#thr__stackPtr] ;Save the stack pointer
1329 LDR R13,wsp__stackPtr ;Load the system stack ptr
1330 STMFD R13!,{R0-R12,PC}^ ;Return to main thread
1334 ; --- thread_errorHandler ---
1336 ; On entry: R0 == pointer to routine to call
1337 ; R1 == R12 value to call with
1338 ; R2 == R13 value to call with
1342 ; Use: Sets up the error handler for a thread.
1344 EXPORT thread_errorHandler
1346 STMFD R13!,{R12,R14} ;Save some registers
1347 WSPACE thr__wSpace ;Locate my workspace
1348 LDR R14,wsp__current ;Get the current thread
1349 ADD R14,R14,#thr__errorHnd ;Point to handler block
1350 STMIA R14,{R0-R2} ;Save the information away
1351 LDMFD R13!,{R12,PC}^ ;Return to caller
1355 ;----- Data structures ------------------------------------------------------
1357 ; --- The thread structure ---
1360 thr__next # 4 ;Pointer to next thread
1361 thr__prev # 4 ;Pointer to previous thread
1362 thr__suspend # 4 ;Thread's suspension counter
1363 thr__semaphore # 4 ;Pointer to blocking sem
1364 thr__priority # 4 ;Priority of this thread
1365 thr__timeSlice # 4 ;Size of timeslice for thread
1366 thr__critCount # 4 ;Critical section counter
1367 thr__stackPtr # 4 ;Thread's stack pointer
1368 thr__stack # 4 ;Pointer to thread's stack
1369 thr__flags # 4 ;Various flags for the thread
1370 thr__errorHnd # 4*3 ;The error handler for it
1371 thr__size # 0 ;Size of this block
1373 tFlag__timeUp EQU (1<<0) ;Pre-empt after critical sect
1375 ; --- The semaphore link structure ---
1378 sml__next # 4 ;Pointer to next link block
1379 sml__thread # 4 ;Pointer to suspended thread
1380 sml__size # 0 ;Size of this block
1382 ; --- The main semaphore block ---
1385 sem__counter # 4 ;Semaphore counter
1386 sem__blocked # 4 ;List of blocked threads
1387 sem__blockEnd # 4 ;Pointer to last link in list
1388 sem__size # 0 ;Size of this block
1390 ;----- Workspace ------------------------------------------------------------
1395 wsp__flags # 4 ;Various useful flags
1396 wsp__threads # 4 ;Pointer to list of threads
1397 wsp__active # 4 ;Counter of active threads
1398 wsp__current # 4 ;Pointer to current thread
1399 wsp__stackPtr # 4 ;The system stack pointer
1400 wsp__R11 # 4 ;Sapphire's R11 pointer
1401 wsp__handlers # 12*4 ;Old handlers to restore
1402 wsp__regBuff # 16*4 ;Register save buffer
1404 thr__wSize EQU {VAR}-thr__wStart
1406 thrFlag__inited EQU (1<<0) ;Are we initialised yet?
1407 thrFlag__inErr EQU (1<<1) ;We are handling an error
1409 AREA |Sapphire$$LibData|,CODE,READONLY
1416 ;----- That's all, folks ----------------------------------------------------