-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathSOFTCARD80.ASM#040000
3008 lines (2679 loc) · 102 KB
/
SOFTCARD80.ASM#040000
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
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Z80 code running on Softcard
; Implements CP/M style BDOS interface
; Requires the companion SOFTCARD65 6502 code
; Assemble using Udo Munk's Z80asm.
; Tabstops every 4 chars.
; Bobbi 2019
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; References:
; 1) BDOS System Calls
; https://www.seasip.info/Cpm/bdos.html
; 2) Programmer's CP/M Handbook - Johnson-Laird
; 3) ProDOS 8 Technical Reference Manual
; http://www.easy68k.com/paulrsm/6502/PDOS8TRM.HTM
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; TODO: Look further down for CCP TODOs!!!!!!!
;
; BDOS TODOs
; ----------
; TODO: STAT B:*.* leaves current drive set to B! Probably other progs too.
; TODO: Size information from NAME2FCB seems to be a bit fishy! I think the
; issue here is that STAT needs to see all the extents, whereas NSWEEP
; uses the highest extent number it sees, and doesn't really care about
; the others.
; TODO: PIP has issues with multi file copy, and I think it is because it only
; closes the destination files, but not the source files. Maybe the
; solution is to close files after each read/write. The FCB keeps track
; of the position in any case and we always seek, so this should work.
; TODO: Need to implement the BIOS entry points and jump table (see MG's Ruby)
; TODO: Needs proper boot / warm boot entry points
; TODO: [ F_WRITE bug turns out to be bug in ProDOS 2.5.0a7 (SET_MARK) ??? ]
; TODO: Maybe I should eliminate use of "EX AF,AF'" in BDOS since CP/M apps
; may expect exclusive use of alternate register set.
; TODO: Implement missing system calls:
; - F_ATTRIB (needs to support wildcards, leave FCB at DMAADDR)
; - RS232 (A_READ, A_WRITE)
; - Printer (LWRITE)
; TODO: IOBYTE doesn't do anything
; TODO: User number doesn't do anything
; TODO: Software R/O disk setting is not respected
; TODO: C_WRITE - handle tabs
; Other Random TODO comments in the code
;
BDOSADDR EQU 08400H ;
STCKTOP EQU 097FFH ; Top of Z80 stack (below IOBUFs)
SOFTCARD EQU 0E400H ; Softcard in slot 4 ($C400)
OFFSET EQU 01000H ; Offset to add to Z80 addr to get 6502
; address. Correct for Z80 addr <= 0AFFFH
; 6502 zero page, in Z80 address space
CMD EQU 0F006H ; 6502 $06
AREG EQU 0F007H ; 6502 $07
XREG EQU 0F008H ; 6502 $08
YREG EQU 0F009H ; 6502 $09
ADDR EQU 0F0EBH ; 6502 $EB (LSB)
ADDRH EQU 0F0ECH ; 6502 $EC (MSB)
; Addresses of 6502 routines, in 6502 address space
COUT EQU 0FDEDH ; Print char in A
RDKEY EQU 0FD0CH ; Read key, return in A
BELL EQU 0FBE4H ; Sound the bell
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Entry point when Z80 cold starts is 0000H
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
ORG 0000H
JP BDOSINIT ; Initialize BDOS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; See obsolescence.wix.com/obsolescence/cpm-internals for info
; on low storage usage ...
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
IOBYTE DEFB 0 ; Maps virtual to real devices
CURDRV DEFB 0 ; LS 4 bits; Current drive 0=A: etc
; MS 4 bits: Current user number
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; BDOS entry point must be at address 0005H for CP/M compatibility
; Function to invoke is passed in C
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
BDOS JP BDOSIMP ; BDOS code is at top of memory
RVEC1 DEFW 0000H ; Restart vector 1
RVEC2 DEFW 0000H ; Restart vector 2
RVEC3 DEFW 0000H ; Restart vector 3
RVEC4 DEFW 0000H ; Restart vector 4
RVEC5 DEFW 0000H ; Restart vector 5
RVEC6 DEFW 0000H ; Restart vector 6
RVEC7 DEFW 0000H ; Restart vector 7
; Space for private BDOS data (implementation dependent) up to 005BH
DMAADDR DEFW 0080H ; DMA address defaults to FILEBUF (0080H)
LOGVEC DEFW 0000H ; Vector of logged in drives
ROVEC DEFW 0000H ; Vector of read-only drives
TEMPWORD DEFW 0000H ; Used by routines as a scratch space
TEMPBYTE DEFB 0 ; Used by routines as a scratch space
; End of private, implementation dependent space
ORG 005CH ; Standard addr of 32 byte FCB1
FCB1 ; File control block #1
FCB1DRV DEFB 00H ; FCB Drive (0 current, 1 A:, 2 B: etc)
FCB1NAM DEFM 'FILENAMEEXT' ; FCB filename and extension
FCB1EX DEFB 00H ; FCB extent field
FCB1S1 DEFB 00H ; FCB S1 field
FCB1S2 DEFB 00H ; FCB S2 field
FCB1RC DEFB 00H ; FCB RC field (# recs used this extent)
FCB1MAP ; Map of blocks in file (overlaps FCB2)
ORG 006CH ; Standard addr of 32 byte FCB2
FCB2 ; File control block #2
FCB2DRV DEFB 00H ; FCB Drive (0 current, 1 A:, 2 B: etc)
FCB2NAM DEFM 'FILENAMEEXT' ; FCB filename and extension
FCB2EX DEFB 00H ; FCB extent field
FCB2S1 DEFB 00H ; FCB S1 field
FCB2S2 DEFB 00H ; FCB S2 field
FCB2RC DEFB 00H ; FCB RC field (# recs used this extent)
FCB2MAP ; Map of blocks in file (overlaps buffer)
ORG 0080H ; Standard addr of 128 byte File Buffer
FILEBUF DEFS 128 ; Command args go here too (Pascal string)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; The application program proper starts at 0100H
; in order to be compatible with CP/M .COM programs
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
ORG 0100H
; Print signon message using C_WRITESTR
PROGSTRT LD DE,WELCOME ; Address of string
LD C,B_C_WRTSTR ;
CALL BDOS ;
CALL CCP ; Run the CCP
; Print the alphabet using C_WRITE
LD B,'A' ; First character
L1 LD E,B ; Character to print
LD C,B_C_WRITE ;
PUSH BC ; Preserve B (and C)
CALL BDOS ;
POP BC ; Restore B (and C)
INC B ;
LD A,'Z' ; Last character
CP B ;
JP Z,S1 ;
JP L1 ;
; Loop until there is a keystroke waiting using C_STAT
S1 LD C,B_C_STAT ;
CALL BDOS ;
CP 0 ; Anything?
JR Z,S1 ; If not, loop
; Print a couple of asterisks
LD E,'*' ;
LD C,B_C_WRITE ;
CALL BDOS ;
LD E,'*' ;
LD C,B_C_WRITE ;
CALL BDOS ;
; Create FCB1 'A:TEST.TXT'
LD A,1 ; A: drive
LD (FCB1DRV),A ;
LD A,'T' ; Filename
LD (FCB1NAM),A ;
LD A,'E' ; Filename
LD (FCB1NAM+1),A ;
LD A,'S' ; Filename
LD (FCB1NAM+2),A ;
LD A,'T' ; Filename
LD (FCB1NAM+3),A ;
LD A,' ' ; Filename
LD (FCB1NAM+4),A ;
LD A,' ' ; Filename
LD (FCB1NAM+5),A ;
LD A,' ' ; Filename
LD (FCB1NAM+6),A ;
LD A,' ' ; Filename
LD (FCB1NAM+7),A ;
LD A,'T' ; Extension
LD (FCB1NAM+8),A ;
LD A,'X' ; Extension
LD (FCB1NAM+9),A ;
LD A,'T' ; Extension
LD (FCB1NAM+10),A ;
; Create FCB2 'A:????????.???'
LD A,1 ; A: drive
LD (FCB2DRV),A ;
LD A,'?' ; Filename
LD (FCB2NAM),A ;
LD (FCB2NAM+1),A ;
LD (FCB2NAM+2),A ;
LD (FCB2NAM+3),A ;
LD (FCB2NAM+4),A ;
LD (FCB2NAM+5),A ;
LD (FCB2NAM+6),A ;
LD (FCB2NAM+7),A ;
LD (FCB2NAM+8),A ;
LD (FCB2NAM+9),A ;
LD (FCB2NAM+10),A ;
; Create and open a file using ProDOS MLI
; Creates 'A/TEST.TXT'
; Directory 'A' needs to exist already
LD DE,CMSG ; Address of string
LD C,B_C_WRTSTR ;
CALL BDOS ;
LD DE,FCB1 ; Default FCB address
LD C,B_F_MAKE ;
CALL BDOS ;
CALL CHECKOK
; Set the DMA buffer to point to our text
LD DE,TEXTBUF ;
LD C,B_F_DMAOFF ;
CALL BDOS ;
; Write to the file
LD DE,WMSG ; Address of string
LD C,B_C_WRTSTR ;
CALL BDOS ;
LD DE,FCB1 ; Default FCB address
LD C,B_F_WRITE ;
CALL BDOS ;
CALL CHECKOK
LD DE,FCB1 ; Default FCB address
LD C,B_F_WRITE ;
CALL BDOS ;
CALL CHECKOK
LD DE,FCB1 ; Default FCB address
LD C,B_F_WRITE ;
CALL BDOS ;
CALL CHECKOK
LD DE,FCB1 ; Default FCB address
LD C,B_F_WRITE ;
CALL BDOS ;
CALL CHECKOK
LD DE,FCB1 ; Default FCB address
LD C,B_F_WRITE ;
CALL BDOS ;
CALL CHECKOK
LD DE,FCB1 ; Default FCB address
LD C,B_F_WRITE ;
CALL BDOS ;
CALL CHECKOK
; Close the file
LD DE,CLMSG ; Address of string
LD C,B_C_WRTSTR ;
CALL BDOS ;
LD DE,FCB1 ; Default FCB address
LD C,B_F_CLOSE ;
CALL BDOS ;
CALL CHECKOK
; Set the DMA buffer to point to FILEBUF (0080H)
LD DE,FILEBUF ;
LD C,B_F_DMAOFF ;
CALL BDOS ;
; Search for the file in the directory
LD DE,SFMSG ; Address of string
LD C,B_C_WRTSTR ;
CALL BDOS ;
LD DE,FCB1 ; Default FCB address
LD C,B_F_SFIRST ;
CALL BDOS ;
CP 0 ;
JP Z,FOUND ;
LD DE,SFMSGNF ;
JP PRFNF ;
FOUND LD DE,SFMSGF ;
PRFNF LD C,B_C_WRTSTR ;
CALL BDOS ;
;CALL CHECKOK
; Search for all files in the directory using wildcards
LD DE,SFMSG2 ; Address of string
LD C,B_C_WRTSTR ;
CALL BDOS ;
LD DE,FCB2 ; Default FCB address 2
LD C,B_F_SFIRST ;
CALL BDOS ;
CP 0 ;
JP Z,FOUND2 ;
LD DE,SFMSGNF ;
JP PRFNF2 ;
FOUND2 LD A,13 ; HACK to terminate string
LD (FILEBUF+12),A ;
LD A,'$' ;
LD (FILEBUF+13),A ;
LD DE,FILEBUF+1 ;
LD C,B_C_WRTSTR ;
CALL BDOS ;
JP DIRLOOP ; Jump forwards to DIR loop
PRFNF2 LD C,B_C_WRTSTR ;
CALL BDOS ;
DIRLOOP LD DE,FCB2 ; Default FCB address 2
LD C,B_F_SNEXT ;
CALL BDOS ;
CP 0 ;
JP Z,FOUND3 ;
LD DE,SFMSGNF ;
JP PRFNF3 ;
FOUND3 LD A,13 ; HACK to terminate string
LD (FILEBUF+12),A ;
LD A,'$' ;
LD (FILEBUF+13),A ;
LD DE,FILEBUF+1 ;
LD C,B_C_WRTSTR ;
CALL BDOS ;
JP DIRLOOP ; Loop for all files in dir
PRFNF3 LD C,B_C_WRTSTR ;
CALL BDOS ;
;CALL CHECKOK
; Overwrite DMA buffer just to be sure it is read
LD A,'X' ;
LD HL,(DMAADDR) ;
LD (HL),A ;
INC HL
LD (HL),A ;
INC HL
LD (HL),A ;
; Open the file
LD DE,OMSG ; Address of string
LD C,B_C_WRTSTR ;
CALL BDOS ;
LD DE,FCB1 ; Default FCB address
LD C,B_F_OPEN ;
CALL BDOS ;
CALL CHECKOK
; Read from the file
LD DE,RMSG ; Address of string
LD C,B_C_WRTSTR ;
CALL BDOS ;
LD DE,FCB1 ; Default FCB address
LD C,B_F_READ ;
CALL BDOS ;
CALL CHECKOK
; Print out what we just read
LD DE,(DMAADDR) ;
LD C,B_C_WRTSTR ;
CALL BDOS ;
END
; Close the file
LD DE,CLMSG ; Address of string
LD C,B_C_WRTSTR ;
CALL BDOS ;
LD DE,FCB1 ; Default FCB address
LD C,B_F_CLOSE ;
CALL BDOS ;
CALL CHECKOK
; Delete the file
LD DE,DMSG ; Address of string
LD C,B_C_WRTSTR ;
CALL BDOS ;
LD DE,FCB1 ; Default FCB address
LD C,B_F_DELETE ;
CALL BDOS ;
CALL CHECKOK
; Read keyboard and echo to screen C_READ, C_WRITE
L2 LD C,B_C_READ ;
CALL BDOS ;
LD E,A ; Prepare to echo keystroke
LD C,B_C_WRITE ;
CALL BDOS ;
JP L2 ; Forever and ever
; Check an MLI call was successful and print out message accordingly
CHECKOK LD A,(AREG) ; Look at the return code
CP 0 ; Success?
JP Z,COKS1 ;
PUSH AF ; Preserve A
LD DE,FAILMSG1 ; Fail message
CALL C_WRITESTR ;
POP AF ; Restore A
LD L,A ; Copy to HL for NUM2HEX
LD H,0 ; ...
LD DE,HEXBUF ; Generate hex string to HEXBUF
CALL NUM2HEX ; ...
LD A,0FFH ; 0FFH for error
LD L,A ; Return code in L also
LD DE,HEXBUF+2 ; Write hex value to console
CALL C_WRITESTR ;
LD DE,FAILMSG2 ; Fail message
CALL C_WRITESTR ;
JP COKS2 ;
COKS1 LD DE,SUCCMSG ; Success message
CALL C_WRITESTR ;
COKS2 LD (TEMPWORD),SP ; Print out stack pointer
LD HL,(TEMPWORD) ;
LD DE,HEXBUF ; Generate hex string to HEXBUF
CALL NUM2HEX ;
LD DE,HEXBUF ;
CALL C_WRITESTR ;
LD DE,CRMSG ; Carriage return
CALL C_WRITESTR ;
RET
WELCOME DEFB 13
DEFM 'Zapple-II Test Stub...'
DEFB 13, '$'
CMSG DEFB 13
DEFM 'Creating & opening A/TEST.TXT'
DEFB 13, '$'
WMSG DEFB 13
DEFM 'Writing record to A/TEST.TXT'
DEFB 13, '$'
CLMSG DEFB 13
DEFM 'Closing A/TEST.TXT'
DEFB 13, '$'
SFMSG DEFB 13
DEFM 'Searching directory for TEST.TXT'
DEFB 13, '$'
SFMSGF DEFB 'Found'
DEFB 13, '$'
SFMSGNF DEFB 'NOT found'
DEFB 13, '$'
SFMSG2 DEFB 13
DEFM 'Searching directory for ????????.???'
DEFB 13, '$'
OMSG DEFB 13
DEFM 'Opening A/TEST.TXT'
DEFB 13, '$'
RMSG DEFB 13
DEFM 'Reading record from A/TEST.TXT'
DEFB 13, '$'
DMSG DEFB 13
DEFM 'Deleting A/TEST.TXT'
DEFB 13, '$'
SUCCMSG DEFM 'Success! SP=$'
FAILMSG1 DEFM 'FAIL (0x$'
FAILMSG2 DEFM ') SP=$'
CRMSG DEFB 13, '$'
TEXTBUF DEFM 'Mary had a little lamb. Its fleece was white as snow. '
DEFM 'And everywhere that Mary went, that lamb was sure to go.$'
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Implementation of CP/M STYLE BDOS
; Function to invoke is passed in C, as follows:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
B_C_TERMCPM EQU 00H ; System reset
B_C_READ EQU 01H ; Console read
B_C_WRITE EQU 02H ; Console write
B_C_RAWIO EQU 06H ; Direct console I/O
B_GET_IOB EQU 07H ; Get IOBYTE
B_SET_IOB EQU 08H ; Set IOBYTE
B_C_WRTSTR EQU 09H ; Console write string
B_C_RDSTR EQU 0AH ; Read console string
B_C_STAT EQU 0BH ; Console status
B_S_BDOSVER EQU 0CH ; Return version number
B_DRV_ALLRST EQU 0DH ; Reset disks
B_DRV_SET EQU 0EH ; Select disk
B_F_OPEN EQU 0FH ; Open file
B_F_CLOSE EQU 10H ; Close file
B_F_SFIRST EQU 11H ; Search for first match in directory
B_F_SNEXT EQU 12H ; Search for next match in directory
B_F_DELETE EQU 13H ; Delete file
B_F_READ EQU 14H ; Read file sequentially
B_F_WRITE EQU 15H ; Write file sequentially
B_F_MAKE EQU 16H ; Create and open file
B_F_RENAME EQU 17H ; Rename file
B_DRV_LOGVEC EQU 18H ; Return bitmap of logged-in drives
B_DRV_GET EQU 19H ; Return current drive
B_F_DMAOFF EQU 1AH ; Set DMA address
B_DRV_AVEC EQU 1BH ; Return address of allocation map
B_DRV_SRO EQU 1CH ; Software write-protect current drive
B_DRV_ROVEC EQU 1DH ; Return bitmap of read-only drives
B_DRV_DPB EQU 1FH ; Get Drive Parameter Block address
B_F_USERNUM EQU 20H ; Get/set user number
B_F_RDRAND EQU 21H ; Random access read record
B_F_WRTRAND EQU 22H ; Random access write record
B_F_SIZE EQU 23H ; Compute file size
B_F_RANDREC EQU 24H ; Update random access pointer
B_DRV_RESET EQU 25H ; Selectively reset disk drives
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
ORG BDOSADDR
BDOSINIT DI ; Make sure interrupts are off
LD SP,STCKTOP ; Initialize SP
XOR A ; A=0
LD (IOBYTE),A ; Initialize IOBYTE
LD (CURDRV),A ; Drive A:, User 0
LD (FILEBUF),A ; Zero chars in command tail
XOR A ; A=0 means close all files
LD (FCMLIN),A ; Store in parameter list
LD HL,FCMLI ; Pass address of 6502 JSR instruction
CALL PRODOS ; Invoke ProDOS MLI to close all files
LD (FRN1),A ; Initialize FRNs to zero
LD (FRN2),A ; ...
LD (FRN3),A ; ...
LD (FRN4),A ; ...
LD HL,FILEBUF ; Initialize DMAADDR to 0080H
LD (DMAADDR),HL ; ...
LD HL,0000H ; Initialize LOGVEC & ROVEC to 0000H
LD (LOGVEC),HL ; ...
LD (ROVEC),HL ; ...
JP CCP ; Start the CCP
BDOSIMP
;; CALL PRHEX ; Print sys call number
LD A,C ; Prepare to check C is in range
CP 41 ; Max syscall# for CP/M 2.2 is 40
JP NC,UNIMP ; If >41 then call UNIMP
LD HL,BDOSVEC ; Start of vector table
SLA C ; Multiply C by 2
LD B,0 ; MSB of BC is zero
ADD HL,BC ; Address of vector in HL
LD C,(HL) ; Read LSB of address to jump to
INC HL ; Read MSB of address to jump to
LD H,(HL) ; ...
LD L,C ; Address needs to be in HL
JP (HL) ; Jump to it!
; Vector table
BDOSVEC DEFW C_TERMCPM ; C=00H
DEFW C_READ ; C=01H
DEFW C_WRITE ; C=02H
DEFW UNIMP ; C=03H (A_READ) AUX
DEFW UNIMP ; C=04H (A_WRITE) AUX
DEFW UNIMP ; C=05H (L_WRITE) PRN
DEFW C_RAWIO ; C=06H
DEFW GET_IOB ; C=07H
DEFW SET_IOB ; C=08H
DEFW C_WRITESTR ; C=09H
DEFW C_READSTR ; C=0AH
DEFW C_STAT ; C=0BH
DEFW S_BDOSVER ; C=0CH
DEFW DRV_ALLRST ; C=0DH
DEFW DRV_SET ; C=0EH
DEFW F_OPEN ; C=0FH
DEFW F_CLOSE ; C=10H
DEFW F_SFIRST ; C=11H
DEFW F_SNEXT ; C=12H
DEFW F_DELETE ; C=13H
DEFW F_READ ; C=14H
DEFW F_WRITE ; C=15H
DEFW F_MAKE ; C=16H
DEFW F_RENAME ; C=17H
DEFW DRV_LOGVEC ; C=18H
DEFW DRV_GET ; C=19H
DEFW F_DMAOFF ; C=1AH
DEFW DRV_AVEC ; C=1BH
DEFW DRV_SETRO ; C=1CH
DEFW DRV_ROVEC ; C=1DH
DEFW UNIMP ; C=1EH (F_ATTRIB)
DEFW DRV_DPB ; C=1FH
DEFW F_USERNUM ; C=20H
DEFW F_READRAND ; C=21H
DEFW F_WRITERAND ; C=22H
DEFW F_SIZE ; C=23H
DEFW F_RANDREC ; C=24H
DEFW DRV_RESET ; C=25H
DEFW UNIMP ; C=26H (*nothing* in CP/M 2.2)
DEFW UNIMP ; C=27H (*nothing* in CP/M 2.2)
DEFW F_WRITERAND ; C=28H (F_WRITEZF)
; Unimplemented BDOS call, just ring the bell
UNIMP LD HL,BELL ; We are going to call BELL
LD (ADDR),HL ; ...
LD A,1 ; CMD=1 means call 6502 sub
LD (CMD),A ; ...
LD (SOFTCARD),A ; Do it!
RET ; Return to calling program
; System reset. Jump to $0000 - doesn't return
C_TERMCPM RST 0 ; Quick jump to zero
; Wait for a character from the console, return it in A and L
; Also echoes the char to the console
C_READ LD HL,RDKEY ; We are going to call RDKEY
LD (ADDR),HL ; ...
LD A,1 ; CMD=1 means call 6502 sub
LD (CMD),A ; ...
LD (SOFTCARD),A ; Do it!
LD A,(AREG) ; Grab the return value
CP 83H ; See if it is Ctrl-C
JP Z,BDOSINIT ; If Ctrl-C quit user program, go to CCP
PUSH AF ; Preserve A (and F)
LD HL,COUT ; Echo the character using COUT
LD (ADDR),HL ; ...
LD A,1 ; CMD=1 means call 6502 sub
LD (CMD),A ; ...
LD (SOFTCARD),A ; Do it!
POP AF ; Restore A (and F)
AND 7FH ; Mask high bit
LD L,A ; Copy A to L
RET ; Return to calling program
; Write character in E to the console
; TODO: Handle tabs
C_WRITE LD A,80H ; Set high bit
OR E ; ...
CP 8AH ; Check for linefeed
RET Z ; If LF, don't print it
LD (AREG),A ; Pass char to COUT in 6502 A
LD HL,COUT ; We are going to call COUT
LD (ADDR),HL ; ...
LD A,1 ; CMD=1 means call 6502 sub
LD (CMD),A ; ...
LD (SOFTCARD),A ; Do it!
RET ; Return to calling program
; If E if 0FFH then input a character from console and return it in A and L
; without echoing the input character. Otherwise output char in E to the
; console (no tabs, ^S or ^Q supported)
C_RAWIO LD A,E ; See if E if 0FFH
CP 0FFH ; ...
JP Z,RIS1 ; If so, then read
; Write to console
LD A,80H ; Set high bit
OR E ; ...
CP 8AH ; Check for linefeed
RET Z ; If LF, don't print it
LD (AREG),A ; Pass char to COUT in 6502 A
LD HL,COUT ; We are going to call COUT
LD (ADDR),HL ; ...
LD A,1 ; CMD=1 means call 6502 sub
LD (CMD),A ; ...
LD (SOFTCARD),A ; Do it!
RET
; If character is waiting, read from console & return in A
; Otherwise, return 00H in A
RIS1 LD A,3 ; CMD=3 means peek at keyboard
LD (CMD),A ; ...
LD (SOFTCARD),A ; Do it
LD A,(AREG) ; Grab the return value
CP 0 ; If zero, no chars are waiting
JP Z,RIS2 ; ...
LD HL,RDKEY ; We are going to call RDKEY
LD (ADDR),HL ; ...
LD A,1 ; CMD=1 means call 6502 sub
LD (CMD),A ; ...
LD (SOFTCARD),A ; Do it!
LD A,(AREG) ; Grab the return value
AND 7FH ; Mask high bit
LD L,A ; Copy A to L
RET ;
RIS2 XOR A ; No chars waiting, A=0
LD L,A ; Return in L also
RET
; Get the IOBYTE in A and L
GET_IOB LD A,(IOBYTE) ;
LD L,A ; Copy to L
RET
; Set the IOBYTE
; E contains the IOBYTE value to set
SET_IOB LD A,E ;
LD (IOBYTE),A ;
RET
; Write ASCII string to console. '$' is the terminator
; DE contains the address of the string
C_WRITESTR LD A,(DE) ; Fetch character from string
CP '$' ; Is it '$'?
RET Z ; If so, we are done
PUSH DE ; We are gonna need E
LD E,A ; For C_WRITE
CALL C_WRITE ; Sent char to console
POP DE ; Recover the pointer
INC DE ; Advance pointer
JP C_WRITESTR ; Handle the next char
; Read console string
; DE points to the string buffer. First byte of the buffer is the capacity
; of the buffer. This function writes the number of bytes written in second
; byte. Entry finishes on CR or when the buffer is filled up.
; Supports ^H, DEL for deleting a character
; Supports ^Z for deleting entire line (since ^X doesn't work with RDKEY
C_READSTR LD H,D ; HL will be the working pointer
LD L,E ; ...
INC HL ; Advance to first character ...
INC HL ; ... 3rd byte of the buffer
PUSH DE ; Put DE into IX
POP IX ; ...
XOR A ; Set number of chars read to zero
LD (IX+1),A ; ...
CRSL1 PUSH HL ; Preserve HL
CALL C_READ ; Read a character into A
POP HL ; Restore HL
CP 13 ; Carriage return ^M pressed?
RET Z ; If so, we are done
CP 10 ; Line feed ^J pressed?
RET Z ; If so, we are done
CP 8 ; Backspace ^H pressed?
JP Z,CRSS2 ; Handle backspace
CP 7FH ; Delete key pressed?
JP Z,CRSS1 ; Handle delete
CP 26 ; ^X pressed?
JP Z,CRSS3 ; Handle ^X
LD B,A ; Stash character in B
LD A,(IX+0) ; Buffer capacity -> A
SUB (IX+1) ; Subtract characters read
CP 0 ; If no space left ...
RET Z ; ... we are done
LD (HL),B ; Write character to buffer
INC HL ; Advance to next character
INC (IX+1) ; Increment character count
JP CRSL1 ; Loop
RET ;
CRSS1 PUSH HL
LD E,8 ; Print two backspaces (^H)
CALL C_WRITE ; ...
CALL C_WRITE ; ...
LD E,' ' ; Print two spaces to erase chars
CALL C_WRITE ; ...
CALL C_WRITE ; ...
LD E,8 ; Print two backspaces (^H)
CALL C_WRITE ; ...
CALL C_WRITE ; ...
POP HL
CRSS2 PUSH HL
LD E,' ' ; Print space to erase character
CALL C_WRITE ; ...
LD E,8 ; Print backspace (^H)
CALL C_WRITE ; ...
LD A,(IX+1) ; Get character count
CP 0 ; See if it is zero
POP HL
JP Z,CRSL1 ; If so, back to top of loop
DEC HL ; Delete previously-entered character
DEC (IX+1) ; Decrement character count
JP CRSL1 ; Back to top of loop
CRSS3 LD A,0 ; Set character count to zero to ...
LD (IX+1),A ; ... cancel the entire entry
LD E,13 ; Print a carriage return
CALL C_WRITE ; ...
RET ; Done
; Returns 0 in A and L if no chars waiting, non zero otherwise
C_STAT LD A,3 ; CMD=3 means peek at keyboard
LD (CMD),A ; ...
LD (SOFTCARD),A ; Do it
LD A,(AREG) ; Grab the return value
LD L,A ; Copy A to L
RET
; Returns system type in B and H, BDOS version in A and L
S_BDOSVER LD B,0 ; System is 8080 CP/M
LD H,B ; Also in H
LD A,22H ; Pretend to v2.2
LD L,A ; Also in A
RET
; Reset disks
; Makes A: drive the default
DRV_ALLRST LD A,(CURDRV) ; Contains both user & current drive
AND 0F0H ; Set drive to 0, meaning A:
LD (CURDRV),A ; Store in CURDRV
LD BC,FILEBUF ; FILEBUF is at 0080H
LD (DMAADDR),BC ; Reset DMA address
LD HL,FLMLI ; Pass address of 6502 JSR instruction
CALL PRODOS ; Invoke ProDOS MLI to flush all files
RET
; Select disk
; Disk to select is passed in E (A: is 0, B: is 1 etc.)
; Return 00 for success, 0FFH for error in A and L
DRV_SET LD A,E ; Prepare to compare disk number
CP 16 ; Support 16 'drives' A: - P:
JP NC,DSERR ; If A>15 ... error
LD B,A ; Stash in B for now
LD A,(CURDRV) ; Has both user number & current drive
AND 0F0H ; Mask out old drive number
OR B ; Replace with new drive number
LD (CURDRV),A ; Store the requested drive number
XOR A ; A=0: Return code meaning success
JP DSRET ;
DSERR LD A,0FFH ; Return code for error
DSRET LD L,A ; Return code in L too
RET
; Open file
; DE is the address of the FCB describing the file to open
; Returns error codes in A and L:
; Returns 0 for success. The FCB for the file opened is left at DMAADDR (slot 0)
; Returns 0FFH if file not found
F_OPEN PUSH DE ; Preserve pointer to FCB
CALL F_SFIRST ; Find first matching directory entry
POP DE ; Restore pointer to FCB
; Alternative entrypoint used for opening ProDOS directory files
; and used by the CCP to load .COM files. No directory lookup.
_F_OPEN LD IX,PATHBUF ; Destination buffer
CALL FCB2PATH ; Populate PATHLEN and PATH
PUSH DE ; Copy pointer to FCB ...
POP IY ; ... into IY
; Work out which IOBUF to allocate for this file
XOR A ; Looking for FRN slot with value 0
CALL GETIOADDR ; Returns FRN slot in A, IOBUF in HL
CP 0FFH ; Check for error
JP Z,FOERR ; If no slots available, error out
LD (TEMPBYTE),A ; Record the buffer index in local var
LD BC,OFFSET ; Add offset to convert to 6502 address
ADD HL,BC ; ...
LD (FOMLII),HL ; Store in parameter list
LD HL,FOMLI ; Pass address of 6502 JSR instruction
CALL PRODOS ; Invoke ProDOS MLI
CP 0 ; See if there was an error
JP NZ,FOERR ; Handle error
; Store ProDOS FRN in S2 field of FCB
LD A,(FOMLIN) ; Get ProDOS file reference number
LD (IY+0EH),A ; Store file reference number in S2 field
; ProDOS GET_EOF call
; Assumes no files > 64K on ProDOS filesystem
LD (GEMLIN),A ; Store file ref num in param list
LD HL,GEMLI ; Pass address of 6502 JSR instruction
CALL PRODOS ; Invoke ProDOS MLI (GET_EOF)
; Convert length in bytes to length in records
LD HL,(GEMLIE2) ; Load 16 bit length
CALL LEN2RECS ; Leaves number of records in A
; TODO If >16K bytes should set num records to 128 I think
; Store records used
LD (IY+0FH),A ; Set records used field
; Set sequential record number to zero
XOR A ; Zero the sequential record number
LD (IY+20H),A ; ...
; Store ProDOS FRN in slot FRN1 - FRN4
LD A,(TEMPBYTE) ; Obtain IOBUF idx (1,2,3,4)
LD HL,FRN1-1 ; Compute address of FRN slot to use
LD B,0 ; ...
LD C,A ; ...
ADD HL,BC ; ...
LD A,(FOMLIN) ; Get ProDOS file reference number
LD (HL),A ; Store in FRN slot
XOR A ; Success
LD L,A ; Copy to L
RET ; Done
FOERR LD A,0FFH ; Error return status
LD L,A ; Copy to L
RET ; Done (error)
; Close file
; DE is the address of the FCB describing the file to close
; Returns error codes in A and L:
F_CLOSE LD H,D ; Pointer to FCB ...
LD L,E ; ... into HL
LD BC,0EH ; Offset to S2 field (reserved field)
ADD HL,BC ; Compute address
LD A,(HL) ; Obtain file reference num from FCB S2
CP 0 ; If file reference number is zero ...
JP Z,FCSUCC ; ... Nothing to do, just return
LD (FCMLIN),A ; Store in parameter list
LD HL,FCMLI ; Pass address of 6502 JSR instruction
CALL PRODOS ; Invoke ProDOS MLI
CP 0 ; See if there was an error
JP NZ,FCERR ; Handle error
LD A,(FCMLIN) ; Obtain file reference number again
CALL GETIOADDR ; Returns FRN slot in A, IOBUF in HL
CP 0FFH ; Check for error
JP Z,FCERR ; If FRN not found, error out
LD HL,FRN1-1 ; Compute addr of FRN slot to set to zero
LD B,0 ; ...
LD C,A ; ...
ADD HL,BC ; ...
XOR A ; And zero it
LD (HL),A ; ...
PUSH DE ; Copy pointer to FCB ...
POP IX ; ... into IX
XOR A ; Zero out the S2 field
LD (IX+0EH),A ; ...
FCSUCC XOR A ; Return zero for error
LD L,A ; Return in L also
RET
FCERR LD A,0FFH ; 0FFH for error
LD L,A ; Return code in L also
RET
DIRHDSZ EQU 2BH ; Size of ProDOS directory header
FILEENTSZ EQU 27H ; Size of ProDOS file entry
ENTPERBLK EQU 0DH ; Number of file entries per block
; Search for first match of filename in directory
; DE is the address of the FCB describing the file to look for
; Returns error codes in A and L: 0 for success, 0FFH for not found
; The matching FCB is always in slot 0, so success return code always 0
F_SFIRST LD (TEMPWORD),DE ; Store pointer to search FCB
LD A,(DE) ; Obtain drive number
LD (DFCBDRV),A ; Copy to directory FCB
LD DE,DFCB ; Use this FCB to open the directory
CALL F_CLOSE ; Close the directory, if open
LD DE,DFCB ; Use this FCB to open the directory
CALL _F_OPEN ; Open the directory (avoiding recursion!)
FSFL1 LD A,0FFH ; Init CDBEXT to 0FFH (see CHKDIRBLK)
LD (CDBEXT),A ; ...
CALL RDDIRBLK ; Read first 512 byte block
CP 0 ; See if it was an error
JP NZ,FSFS2 ; If error, assume EOF & just return
LD HL,DIRBUF ; Skip over directory header
LD BC,DIRHDSZ ; ...
ADD HL,BC ; ...
LD (CDBPTR),HL ; Start out at first file entry
XOR A ; Set file count to zero
LD (CDBCOUNT),A ; ...
LD DE,(TEMPWORD) ; Get ptr to search FCB back
CALL CHKDIRBLK ; Search directory block
CP 0 ; See if it was a match
JP Z,FSFS1 ; If so, return