-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathmzkernel.asm
666 lines (589 loc) · 15.6 KB
/
mzkernel.asm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
; Multithreaded dispatcher kernel for Z80
;
; Copyright (c) 2014, Michael Borisov
; All rights reserved.
;
; Redistribution and use in source and binary forms, with or without
; modification, are permitted provided that the following conditions are met:
;
; 1. Redistributions of source code must retain the above copyright notice, this
; list of conditions and the following disclaimer.
; 2. Redistributions in binary form must reproduce the above copyright notice,
; this list of conditions and the following disclaimer in the documentation
; and/or other materials provided with the distribution.
;
; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
; ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
; DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
; ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
; (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
; ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
;===========================
; Data structure definitions
;===========================
;==================
; Dispatcher Object
;==================
; enum DISPATCHER_OBJECT_TYPE {EventNotificationObject = 0,
; EventSynchronizationObject, MutexObject}
;
EVENT_NOTIFICATION_OBJECT EQU 0
EVENT_SYNCHRONIZATION_OBJECT EQU 1
; struct DISPATCHER_OBJECT
; {
; BYTE Type;
; BYTE SignalState;
; KTHREAD* WaitListHead;
; }
DISPOBJ_TYPE EQU 0
DISPOBJ_SIGNALSTATE EQU 1
DISPOBJ_WAITLISTHEAD EQU 2
DISPOBJ_SIZE EQU 4
; struct CONTEXT
; {
; WORD IY;
; WORD IX;
; WORD BC';
; WORD DE';
; WORD HL';
; WORD AF';
; WORD BC;
; WORD DE;
; WORD HL;
; WORD AF;
; WORD SP;
; }
CONTEXT_IY EQU 0
CONTEXT_IX EQU 2
CONTEXT_BCS EQU 4
CONTEXT_DES EQU 6
CONTEXT_HLS EQU 8
CONTEXT_AFS EQU 10
CONTEXT_BC EQU 12
CONTEXT_DE EQU 14
CONTEXT_HL EQU 16
CONTEXT_AF EQU 18
CONTEXT_SP EQU 20
CONTEXT_SIZE EQU 22
;
; THREAD_FLAGS bit map
;
THREAD_FLAG_READY EQU 0
THREAD_FLAG_MINCONTEXT EQU 1
;
; struct THREAD
; {
; BYTE flags;
; BYTE ThreadPriority;
; KTHREAD* WaitListEntry;
; KTHREAD* NextThread;
; CONTEXT context;
; }
;
THREAD_FLAGS EQU 0
THREAD_PRIORITY EQU 1
THREAD_WAIT_LIST_ENTRY EQU 2
THREAD_NEXT_THREAD EQU 4
THREAD_CONTEXT EQU 6
THREAD_SIZE EQU THREAD_CONTEXT+CONTEXT_SIZE
; Stack for the ISRs
isr_stack EQU 0
;===========================
; Configuration constants
;===========================
; Stack for the threads
;-------- Thread 0 init data
THREAD_0_STACK_SIZE EQU 32
;-------- Thread 1 init data
THREAD_1_STACK_SIZE EQU 32
;-------- Thread 2 init data
THREAD_2_STACK_SIZE EQU 4
device zxspectrum48
org 8080h-18h
;======================
; OS Kernel entry point
start: di
im 2
ld hl,0FE00h ;Initialize Interrupt vector table (conservative)
ld a,h
ld i,a
ld de,0FE01h
ld bc,0100h
ld (hl),080h
ldir
; Jump to thread 0
ld sp,thread_0_stack+THREAD_0_STACK_SIZE-2
ei
ret
;======================
; Interrupt service routine
; Full context saving into a global buffer
; (it will be copied to a thread context area later if necessary)
; R register is never saved or restored
; I register is never supposed to be changed by user code
ISR: ld (isr_stack-2),sp
ld sp,isr_stack-2
push af
push hl
push de
push bc
ex af,af'
exx
push af
push hl
push de
push bc
push ix
push iy
; For possible thread preemption, put current thread as the first return candidate
; this is semantically KeRaiseIrql
ld a,(current_thread_priority)
ld (request_thread),a
; <------------------------ User ISR starts here
ld ix,event_1
call KeSetSynchrEventFromIsr
; <------------------------ User ISR ends here
; User ISR is finished. Check if a context switch is necessary (KeLowerIrql)
isrend: ld hl,current_thread_priority
ld a,(request_thread)
cp (hl)
jr nc,irncs ;No higher-priority threads are ready
; Switch context. Copy saved registers from ISR stack to current thread's context area
ld (hl),a ;Set a new current thread
ld hl,(current_thread_ptr)
ld de,THREAD_CONTEXT
add hl,de
ex de,hl
ld hl,isr_stack-CONTEXT_SIZE
ld bc,CONTEXT_SIZE
ldir
ld iy,(current_thread_ptr)
ld ix,(request_thread_ptr)
res THREAD_FLAG_MINCONTEXT,(iy+THREAD_FLAGS) ;Interrupt preemption, full context restoration
jp KiRestoreContext
; Return from ISR without changing thread context
irncs: pop iy
pop ix
pop bc
pop de
pop hl
pop af
exx
ex af,af'
pop bc
pop de
pop hl
pop af
ld sp,(isr_stack-2)
ei
ret
;-------------------------
; KeExitThread
; Ends a thread and removes it from the system thread list
; Yields execution to the next ready thread
; Must be called or jumped to at IRQL=PASSIVE from the thread that exits
; Does not return
KeExitThread:
di
; Search for the current thread in the system thread list
ld de,(current_thread_ptr)
ld hl,(thread_list_head)
or a
sbc hl,de
jr nz,kexth0
; A separate path for deletion of the first thread
ld ix,(thread_list_head)
ld l,(ix+THREAD_NEXT_THREAD)
ld h,(ix+THREAD_NEXT_THREAD+1)
ld (thread_list_head),hl
push de
pop iy
jr KiSwapThread
kexth0: add hl,de
push hl
pop ix
ld l,(ix+THREAD_NEXT_THREAD)
ld h,(ix+THREAD_NEXT_THREAD+1)
or a
sbc hl,de
jr nz,kexth0
; The current thread reference is found somewhere in the list, IX=ref_thread, DE=cur_thread
ld yh,d
ld yl,e
ld c,(iy+THREAD_NEXT_THREAD)
ld b,(iy+THREAD_NEXT_THREAD+1)
ld (ix+THREAD_NEXT_THREAD),c
ld (ix+THREAD_NEXT_THREAD+1),b
jr KiSwapThread
;=========================
; KeWaitForObject
; <IX - Address of object
; must be called at IRQL=PASSIVE
; uses all registers
KeWaitForObject:
; We potentially need to touch the dispatcher database.
di
;Check if the object is already signaled
bit 0,(ix+DISPOBJ_SIGNALSTATE)
jr z,WFS_0
; If it is not a synchronization event, return
ld a,(ix+DISPOBJ_TYPE)
cp EVENT_SYNCHRONIZATION_OBJECT
jr nz,eiret0
; Unsignal it before returning
res 0,(ix+DISPOBJ_SIGNALSTATE)
eiret0: ei
ret
; Setup of the waiting thread
WFS_0: ld iy,(current_thread_ptr)
; Insert the thread into the wait list for this object
ld l,(ix+DISPOBJ_WAITLISTHEAD)
ld h,(ix+DISPOBJ_WAITLISTHEAD)
ld a,yl
ld (ix+DISPOBJ_WAITLISTHEAD),a
ld a,yh
ld (ix+DISPOBJ_WAITLISTHEAD+1),a
ld (iy+THREAD_WAIT_LIST_ENTRY),l
ld (iy+THREAD_WAIT_LIST_ENTRY+1),h
;-----------------------
; KiSwapThread
; Picks a lower priority thread to run when the current one gets blocked.
; Saves a minimal context of the current thread (SP, and PC on the thread's stack)
; Restores context of the ready thread
; Must be called at IRQL=DISPATCH_LEVEL
; <IY - Address of the current thread
KiSwapThread:
; Search for a thread with a lower priority to run
; (no threads with higher priority are guaranteed to be ready,
; otherwise the current thread would have been preeempted)
push iy
pop ix
SWT_0: ld e,(ix+THREAD_NEXT_THREAD)
ld d,(ix+THREAD_NEXT_THREAD+1)
ld xh,d
ld xl,e
bit THREAD_FLAG_READY,(ix+THREAD_FLAGS)
jr z,SWT_0
; A thread is found. Make it current
ld a,(ix+THREAD_PRIORITY)
ld (current_thread_priority),a
res THREAD_FLAG_READY,(iy+THREAD_FLAGS)
;======================
; KiSwitchContext
; Switch thread context
; Saves a minimal context of the current thread (SP, and PC on the thread's stack)
; Restores context of the target thread
; Must be called at IRQL=DISPATCH_LEVEL
; Returns when the current thread gets a CPU time slice again, at PASSIVE_LEVEL
; <IY - address of the current thread
; <IX - address of the target thread
KiSwitchContext:
set THREAD_FLAG_MINCONTEXT,(iy+THREAD_FLAGS) ;Minimal context restoration after waiting
; Minimal context saving
ld hl,0
add hl,sp
ld (iy+THREAD_CONTEXT+CONTEXT_SP),l
ld (iy+THREAD_CONTEXT+CONTEXT_SP+1),h
;======================
; KiRestoreContext
; Restores context of the target thread
; Must be jumped to at IRQL>=DISPATCH_LEVEL
; Does not return
; <IX - address of the target thread
KiRestoreContext:
ld (current_thread_ptr),ix
; Context restoration: restore minimal or full context depending on thread flags
bit THREAD_FLAG_MINCONTEXT,(ix+THREAD_FLAGS)
jr nz,kscmin
; Full context restoration
ld de,THREAD_CONTEXT
add ix,de
ld sp,ix
pop iy
pop ix
pop bc
pop de
pop hl
pop af
ex af,af'
exx
pop bc
pop de
pop hl
ld (rgsav),hl
pop af
pop hl
ld sp,hl
ld hl,(rgsav)
ei
ret
; Minimal context restoration
kscmin: ld l,(ix+THREAD_CONTEXT+CONTEXT_SP)
ld h,(ix+THREAD_CONTEXT+CONTEXT_SP+1)
ld sp,hl
eiret: ei
ret
;--------------------------
; KeResetEvent
; <IX - address of the event
; can be called at any IRQL
; uses none
KeResetEvent:
res 0,(ix+DISPOBJ_SIGNALSTATE)
ret
;--------------------------
; KeSetNotifEvent
; Must be called at IRQL=PASSIVE
; <IX - address of event
; uses all registers
KeSetNotifEvent:
ld a,(current_thread_priority)
di
ld (request_thread),a
call KeSetNotifEventFromIsr
; Check if we need to switch to a higher priority thread right now
jr KiLowerIrqlFromDispatch
;---------------------------
; KeSetNotifEventFromIsr
; common code for notification event setting
; must be called at IRQL>=DISPATCH_LEVEL
; <IX - address of event
; uses IY, DE, HL, A
KeSetNotifEventFromIsr:
set 0,(ix+DISPOBJ_SIGNALSTATE)
; Unwait all threads that may be waiting for this event
ld e,(ix+DISPOBJ_WAITLISTHEAD)
ld d,(ix+DISPOBJ_WAITLISTHEAD+1)
ksne0: ld a,d
or e
jr z,ksne1 ;Exit loop if no (more) waiters
ld yh,d
ld yl,e
call KiUnwaitThread
; Move on to the next thread in the wait list
ld e,(iy+THREAD_WAIT_LIST_ENTRY)
ld d,(iy+THREAD_WAIT_LIST_ENTRY+1)
jr ksne0
; Clear the object's wait list. DE guaranteed to be 0000
ksne1: ld (ix+DISPOBJ_WAITLISTHEAD),e
ld (ix+DISPOBJ_WAITLISTHEAD+1),d
ret
;---------------------------
; KiLowerIrqlFromDispatch
; After satisfying waits, check if any of the woken
; threads can preempt the current thread
; Must be called at IRQL=DISPATCH_LEVEL
; Returns at IRQL=passive, possibly after switching to another thread and back
; uses all registers
KiLowerIrqlFromDispatch:
ld hl,current_thread_priority
ld a,(request_thread)
cp (hl)
jr nc,eiret ;No higher-priority threads are waiting
; The requesting thread preempts us. Switch the context to it
ld (hl),a
ld iy,(current_thread_ptr)
ld ix,(request_thread_ptr)
jp KiSwitchContext ;Non-interrupt preemption, min context restoration
;---------------------------
; KiUnwaitThread
; Must be called at IRQL>=DISPATCH_LEVEL
; <IY - address of thread
; uses A, HL
KiUnwaitThread:
; Set the thread's state to Ready
set THREAD_FLAG_READY,(iy+THREAD_FLAGS)
; Check if the thread's priority is higher than of any other requesters
ld a,(iy+THREAD_PRIORITY)
ld hl,request_thread
cp (hl)
ret nc
ld (hl),a ;the new thread has a higher priority
ld (request_thread_ptr),iy
ret
;--------------------------
; KeSetSynchrEvent
; Must be called at IRQL=PASSIVE
; <IX - address of event
; uses all registers
KeSetSynchrEvent:
di
set 0,(ix+DISPOBJ_SIGNALSTATE)
; Check if anybody was waiting for this event
ld e,(ix+DISPOBJ_WAITLISTHEAD)
ld d,(ix+DISPOBJ_WAITLISTHEAD+1)
ld a,d
or e
jp z,eiret ;If no waiters, quick return
ld a,(current_thread_priority)
ld (request_thread),a
call KiRemoveUnwaitSingleThread
; Check if we need to switch to that thread right now
jr KiLowerIrqlFromDispatch
;---------------------------
; KeSetSynchrEventFromIsr
; Must be called at IRQL>=DISPATCH_LEVEL
; <IX - address of event
; uses IY, DE, HL, A
KeSetSynchrEventFromIsr:
set 0,(ix+DISPOBJ_SIGNALSTATE)
; Check if anybody was waiting for this event
ld e,(ix+DISPOBJ_WAITLISTHEAD)
ld d,(ix+DISPOBJ_WAITLISTHEAD+1)
ld a,d
or e
ret z ;If no waiters, quick return
;----------------------------
; KiRemoweUnwaitSingleThread
; Removes a single thread from the object's waiting list
; and unwaits it. Also resets the objet's signal state
; Must be called at IRQL>=DISPATCH_LEVEL
; <IX - address of dispatcher object
; <DE - address of a thread waiting on this object
; >IY - address of thread (same as DE)
; uses A, HL
KiRemoveUnwaitSingleThread:
res 0,(ix+DISPOBJ_SIGNALSTATE)
ld yh,d
ld yl,e
; Remove the thread from the event's wait list
ld e,(iy+THREAD_WAIT_LIST_ENTRY)
ld d,(iy+THREAD_WAIT_LIST_ENTRY+1)
ld (ix+DISPOBJ_WAITLISTHEAD),e
ld (ix+DISPOBJ_WAITLISTHEAD+1),d
jr KiUnwaitThread
;-------------------------
; KeStartThread
; Starts a new thread and schedules it for execution
; New threads always start in Ready state
; Can preempt current thread in favor of the new thread
; if the new thread's priority is higher
; Must be called at IRQL=PASSIVE
; <IY - address of the new thread
; uses all registers
KeStartThread:
di
ld a,(current_thread_priority)
ld (request_thread),a
call KiStartThread
jr KiLowerIrqlFromDispatch
;-------------------------
; KiStartThread
; Starts a new thread by inserting it in the system thread list sorted by priority
; Must be called at IRQL=DISPATCH_LEVEL
; <IY - address of the new thread
; uses HL,DE,BC,A,IX
KiStartThread:
ld ix,(thread_list_head)
ld a,(iy+THREAD_PRIORITY)
cp (ix+THREAD_PRIORITY)
jr nc,kisth0
; A separate path for insertion in the list head
ld b,xh
ld c,xl
ld (iy+THREAD_NEXT_THREAD),c
ld (iy+THREAD_NEXT_THREAD+1),b
ld (thread_list_head),ix ;The new list head
jr kisth1
; Move on to the next thread
kisth0: ld c,(ix+THREAD_NEXT_THREAD)
ld b,(ix+THREAD_NEXT_THREAD+1)
; Save the thread pointer for the next iteration
ld d,xh
ld e,xl
ld xh,b
ld xl,c
cp (ix+THREAD_PRIORITY)
jr nc,kisth0
; A place for insertion is found
ld b,xh
ld c,xl
ld (iy+THREAD_NEXT_THREAD),c
ld (iy+THREAD_NEXT_THREAD+1),b
ld xh,d
ld xl,e
ld b,yh
ld c,yl
ld (ix+THREAD_NEXT_THREAD),c
ld (ix+THREAD_NEXT_THREAD+1),b
; After insertion, compete for the highest priority
kisth1: ld hl,request_thread
cp (hl)
ret nc
; We are the highest requester
ld (hl),a
ld (request_thread_ptr),iy
ret
;====================================
; Example threads
;====================================
thread_0_entry:
ld iy,thread_1
call KeStartThread
thr00: ld ix,event_0
call KeWaitForObject
ld ix,event_0
call KeResetEvent
jr thr00
thread_1_entry:
ld ix,event_1
call KeWaitForObject
ld ix,event_0
call KeSetSynchrEvent
jp KeExitThread
thread_2_entry:
halt
jr thread_2_entry
;====================================
; Data segment
;====================================
current_thread_priority: db 0
current_thread_ptr: dw thread_0
thread_list_head: dw thread_0
;-- Thread 0 (priority 0 = highest)
thread_0:
db 3 ;Flags: Ready, min-context
db 0 ;Thread priority (lower=higher)
dw 0 ;WaitListEntry
dw thread_idle ;NextThread
ds CONTEXT_SIZE-2
dw thread_0_stack+THREAD_0_STACK_SIZE-2
;-- Thread 1 (priority 1)
thread_1:
db 3 ;Flags: Ready, min-context
db 1 ;Thread priority (lower=higher)
dw 0 ;WaitListEntry
dw 0 ;NextThread
ds CONTEXT_SIZE-2
dw thread_1_stack+THREAD_1_STACK_SIZE-2
;-- System Idle Thread (priority 255, lowest, must not wait or exit)
thread_idle:
db 3 ;Flags: Ready, min-context
db 2 ;Thread priority (lower=higher)
dw 0 ;WaitListEntry
dw 0 ;NextThread - has no meaning
ds CONTEXT_SIZE-2
dw thread_2_stack+THREAD_2_STACK_SIZE-2
thread_0_stack: ds THREAD_0_STACK_SIZE-2
dw thread_0_entry
thread_1_stack: ds THREAD_1_STACK_SIZE-2
dw thread_1_entry
thread_2_stack: ds THREAD_2_STACK_SIZE-2
dw thread_2_entry
event_0:
db EVENT_NOTIFICATION_OBJECT
db 0
dw 0
event_1:
db EVENT_SYNCHRONIZATION_OBJECT
db 0
dw 0
last_addr_sav:
request_thread: ds 1
request_thread_ptr: ds 2
rgsav: ds 2
savebin "mzkernel.bin",start,last_addr_sav-start