diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..161ce714 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +*~ +.obj +apple2e.po +atari800.atr +atari800hd.atr +bbcmicro.ssd +bin +c64.d64 +pet.d64 +vic20.d64 +x16.zip diff --git a/Makefile b/Makefile index efe9840f..18e1b4a0 100644 --- a/Makefile +++ b/Makefile @@ -13,14 +13,33 @@ APPS = \ $(OBJDIR)/asm.com \ $(OBJDIR)/apps/bedit.com \ $(OBJDIR)/apps/dump.com \ + $(OBJDIR)/apps/dinfo.com \ + $(OBJDIR)/apps/cls.com \ + $(OBJDIR)/apps/ls.com \ $(OBJDIR)/apps/capsdrv.com \ apps/bedit.asm \ apps/dump.asm \ + apps/dinfo.asm \ + apps/cls.asm \ + apps/ls.asm \ cpmfs/asm.txt \ cpmfs/bedit.txt \ cpmfs/demo.sub \ cpmfs/hello.asm \ +MINIMAL_APPS = \ + $(OBJDIR)/submit.com \ + $(OBJDIR)/stat.com \ + $(OBJDIR)/copy.com \ + $(OBJDIR)/asm.com \ + $(OBJDIR)/apps/bedit.com \ + $(OBJDIR)/apps/dump.com \ + $(OBJDIR)/apps/dinfo.com \ + $(OBJDIR)/apps/cls.com \ + $(OBJDIR)/apps/ls.com \ + $(OBJDIR)/apps/capsdrv.com \ + apps/dump.asm \ + LIBCPM_OBJS = \ $(OBJDIR)/lib/printi.o \ $(OBJDIR)/lib/bdos.o \ @@ -41,7 +60,7 @@ CPMEMU_OBJS = \ $(OBJDIR)/tools/cpmemu/biosbdos.o \ $(OBJDIR)/third_party/lib6502/lib6502.o \ -all: apple2e.po c64.d64 bbcmicro.ssd x16.zip pet.d64 vic20.d64 bin/cpmemu +all: apple2e.po c64.d64 bbcmicro.ssd x16.zip pet.d64 vic20.d64 bin/cpmemu atari800.atr atari800hd.atr $(OBJDIR)/multilink: $(OBJDIR)/tools/multilink.o @mkdir -p $(dir $@) @@ -220,8 +239,43 @@ vic20.d64: $(OBJDIR)/vic20.exe $(OBJDIR)/bdos.img Makefile $(APPS) \ cpmcp -f c1541 $@ $(OBJDIR)/ccp.sys $(APPS) 0: cpmchattr -f c1541 $@ s 0:cbmfs.sys 0:ccp.sys +# Atari targets call /usr/bin/printf directly because 'make' calls /bin/sh +# which might be the Defective Annoying SHell which has a broken printf +# implementation. + +$(OBJDIR)/src/bios/atari800.o: CFLAGS65 += -DATARI_SD +$(OBJDIR)/atari800.exe: +atari800.atr: $(OBJDIR)/atari800.exe $(OBJDIR)/bdos.img Makefile \ + $(MINIMAL_APPS) $(OBJDIR)/ccp.sys + dd if=/dev/zero of=$@ bs=128 count=720 + mkfs.cpm -f atari90 $@ + cpmcp -f atari90 $@ $(OBJDIR)/ccp.sys $(MINIMAL_APPS) 0: + cpmchattr -f atari90 $@ s 0:ccp.sys + dd if=$(OBJDIR)/atari800.exe of=$@ bs=128 conv=notrunc + dd if=$(OBJDIR)/bdos.img of=$@ bs=128 seek=8 conv=notrunc + mv $@ $@.raw + /usr/bin/printf '\x96\x02\x80\x16\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' > $@ + cat $@.raw >> $@ + rm $@.raw + +$(OBJDIR)/src/bios/atari800hd.o: CFLAGS65 += -DATARI_HD +$(OBJDIR)/atari800hd.exe: +atari800hd.atr: $(OBJDIR)/atari800hd.exe $(OBJDIR)/bdos.img Makefile \ + $(APPS) $(OBJDIR)/ccp.sys + dd if=/dev/zero of=$@ bs=128 count=8190 + mkfs.cpm -f atarihd $@ + cpmcp -f atarihd $@ $(OBJDIR)/ccp.sys $(APPS) 0: + cpmchattr -f atarihd $@ s 0:ccp.sys + dd if=$(OBJDIR)/atari800hd.exe of=$@ bs=128 conv=notrunc + dd if=$(OBJDIR)/bdos.img of=$@ bs=128 seek=10 conv=notrunc + mv $@ $@.raw + /usr/bin/printf '\x96\x02\xf0\xff\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' > $@ + cat $@.raw >> $@ + rm $@.raw + + clean: - rm -rf $(OBJDIR) bin apple2e.po c64.d64 bbcmicro.ssd x16.zip pet.d64 vic20.d64 + rm -rf $(OBJDIR) bin apple2e.po c64.d64 bbcmicro.ssd x16.zip pet.d64 vic20.d64 atari800.atr atari800hd.atr .DELETE_ON_ERROR: .SECONDARY: diff --git a/README.md b/README.md index 8a0b8e7f..3c767cef 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ No, it won't let you run 8080 programs on the 6502! CP/M-65 running on an Apple IIe CP/M-65 running on a Commodore PET 4032 CP/M-65 running on a Commodore VIC-20 +CP/M-65 running on an Atari 800XL @@ -149,6 +150,21 @@ drive) and BBC Micro (producing a 200kB SSSD DFS disk). - This port runs completely bare-metal and does not use any ROM routines. +### Atari 800 XL/XE notes + + - Standard 720 sectors single-sided single-density disk. The first three + tracks are reserved (54 sectors). The rest contains a standard CP/M 2.2 + filesystem. Boot with BASIC disabled. + + - Uses the Atari OS boot loader to load both BIOS and BDOS to avoid needing + Atari DOS 2.0 DOS.SYS which would consumes precious memory and is useless + afterwards. CCP.SYS is loaded from the CP/M filesystem. + + - Console is standard 40x24 and it uses the Atari OS routines to read the + keyboard and write to the screen. + + - There are 41728 bytes of TPA memory. + ### Supported programs You don't get a lot right now. As transients, you get `DUMP`, `STAT`, `COPY`, diff --git a/apps/cls.asm b/apps/cls.asm new file mode 100644 index 00000000..a005d5ff --- /dev/null +++ b/apps/cls.asm @@ -0,0 +1,8 @@ +\ cls - clear screen +BDOS_CONOUT = 2 +BDOS = start-3 +start: + ldy #BDOS_CONOUT + lda #26 + jmp BDOS + diff --git a/apps/dinfo.asm b/apps/dinfo.asm new file mode 100644 index 00000000..8898ac94 --- /dev/null +++ b/apps/dinfo.asm @@ -0,0 +1,369 @@ +\ dinfo - info about current drive + +\ Copyright © 2023 by David Given and Ivo van Poorten + +\ This file is licensed under the terms of the 2-clause BSD license. Please +\ see the COPYING file in the root project directory for the full text. + +\ C version: 727 bytes +\ asm version: 512 bytes + +.bss pblock, 165 +cpm_fcb = pblock + +BDOS = start-3 + +BDOS_CONOUT = 2 +BDOS_PRINTSTRING = 9 +BDOS_SELECT_DRIVE = 14 +BDOS_GET_CURRENT_DRIVE = 25 +BDOS_GET_ALLOC_VECTOR = 27 +BDOS_GET_DPB = 31 + +DPB_BSH = 2 +DPB_EXM = 4 +DPB_DSM = 5 +DPB_DRM = 7 +DPB_CKS = 11 +DPB_OFF = 13 + +FCB_DR = 0 + +.zp dpb, 2 + +.zp ptr1, 2 \ clobbered by print +.zp pad, 2 \ idem +.zp val1, 2 +.zp dsm, 2 \ actually dsm+1 which is needed in calculations two times + \ and as an endpoint during free space calculations +.zp bsh, 1 +.zp count, 2 + +start: + ldy #BDOS_GET_CURRENT_DRIVE + jsr BDOS + sta val1 + + lda cpm_fcb + beq keepcur + + sta val1 + dec val1 \ fcb.dr - 1 + +keepcur: + lda val1 + ldy #BDOS_SELECT_DRIVE + jsr BDOS + + ldy #BDOS_GET_CURRENT_DRIVE + jsr BDOS + + cmp val1 + beq success + + lda #error + ldy #BDOS_PRINTSTRING + jsr BDOS + rts + +success: + ldy #BDOS_GET_DPB + jsr BDOS + sta dpb + stx dpb+1 + +\ drive capacity in sectors + +\ store dsm+1 as we need it again later + + ldy #DPB_DSM + lda (dpb),y + clc + adc #1 + sta val1 + sta dsm + iny + lda (dpb),y + adc #0 + sta val1+1 + sta dsm+1 + +\ shift by BSH + + ldy #DPB_BSH + lda (dpb),y + sta bsh + tax + + jsr shift_val1_left_by_x + + lda val1 + ldx val1+1 + jsr print16padded + + lda #capacity + jsr printstring + +\ directory entries + + ldy #DPB_DRM + lda (dpb),y + clc + adc #1 + ldx #0 + jsr print16padded + + lda #direntries + jsr printstring + +\ checked entries + + ldy #DPB_CKS + lda (dpb),y + ldx #0 + jsr print16padded + + lda #chkentries + jsr printstring + +\ records per entry +\ instead of (dpb->exm+1)*128, we do (dpb->exm+1)*256/2 + + ldy #DPB_EXM + lda (dpb),y + clc + adc #1 + ror A + tax \ no intermediate store + lda #0 + ror A + jsr print16padded + + lda #recperentry + jsr printstring + +\ reserved sectors + + ldy #DPB_OFF + lda (dpb),y + ldx #0 + jsr print16padded + + lda #ressect + jsr printstring + +\ free space + + lda #free + jsr printstring + +\ count unused blocks in val1 + + ldy #BDOS_GET_ALLOC_VECTOR + jsr BDOS + sta ptr1 + stx ptr1+1 + + lda #0 + sta count + sta count+1 + sta val1 + sta val1+1 + tay + +.label stop + + .zrepeat + lda (ptr1),y + ldx #7 + .zrepeat + lsr A + bcs used + inc val1 + .zif eq + inc val1+1 + .zendif + used: + inc count + .zif eq + inc count+1 + .zendif + pha + lda count+1 + cmp dsm+1 + bne notyet + lda count + cmp dsm + beq stop \ stop condition + notyet: + pla + dex + .zuntil mi + + inc ptr1 + .zif eq + inc ptr1+1 + .zendif + .zuntil eq \ which is never + +stop: + pla \ leftover byte + + ldx bsh + dex + dex + dex + stx count \ save bsh-3 for later + beq no_shift + + jsr shift_val1_left_by_x + +no_shift: + lda val1 + ldx val1+1 + ldy #0 + jsr print16paddedY + + lda #kilobyte + jsr printstring + + lda #'/' + ldy #BDOS_CONOUT + jsr BDOS + + lda dsm + sta val1 + lda dsm+1 + sta val1+1 + ldx count + beq no_shift2 + + jsr shift_val1_left_by_x + +no_shift2: + lda val1 + ldx val1+1 + ldy #0 + jsr print16paddedY + + lda #kilobyte + jsr printstring + + lda #crlf + jmp printstring \ finished + +.zproc shift_val1_left_by_x + clc + .zrepeat + rol val1 + rol val1+1 + dex + .zuntil eq + rts +.zendproc + +.zproc printstring + ldy #BDOS_PRINTSTRING + jmp BDOS +.zendproc + +\ Prints XA in decimal. Y is the padding char or 0 for no padding. + +.zproc print16padded + ldy #' ' \ always pad with ' ' +.zendproc + +.zproc print16paddedY + sta ptr1+0 + stx ptr1+1 + sty pad + + .label dec_table + .label skip + .label justprint + + ldy #8 + .zrepeat + ldx #0xff + sec + .zrepeat + lda ptr1+0 + sbc dec_table+0, y + sta ptr1+0 + + lda ptr1+1 + sbc dec_table+1, y + sta ptr1+1 + + inx + .zuntil cc + + lda ptr1+0 + adc dec_table+0, y + sta ptr1+0 + + lda ptr1+1 + adc dec_table+1, y + sta ptr1+1 + + tya + pha + txa + pha + + .zif eq \ if digit is zero, check padding + lda pad \ get the padding character + beq skip \ if zero, no padding + bne justprint \ otherwise, use it + .zendif + + ldx #'0' \ printing a digit, so reset padding + stx pad + ora #'0' \ convert to ASCII + justprint: + ldy #BDOS_CONOUT + jsr BDOS + skip: + pla + tax + pla + tay + + dey + dey + .zuntil mi + rts + +dec_table: + .word 1, 10, 100, 1000, 10000 +.zendproc + +error: + .byte "drive error" +crlf: + .byte "\r\n$" +capacity: + .byte " : Sectors Capacity\r\n$" +direntries: + .byte " : Directory Entries\r\n$" +chkentries: + .byte " : Checked Entries\r\n$" +recperentry: + .byte " : Records Per Entry\r\n$" +ressect: + .byte " : Reserved Sectors\r\n$" +free: + .byte "Free space: $" +kilobyte: + .byte "kB$" + diff --git a/apps/ls.asm b/apps/ls.asm new file mode 100644 index 00000000..0d461597 --- /dev/null +++ b/apps/ls.asm @@ -0,0 +1,610 @@ +\ ls - list files + +\ Copyright © 2023 by Ivo van Poorten and David Given + +\ This file is licensed under the terms of the 2-clause BSD license. Please +\ see the COPYING file in the root project directory for the full text. + +\ C version: 2051 bytes +\ asm version: 640 bytes (w/o sort) +\ asm version: 896 bytes (w/ sort) + +.bss pblock, 165 +cpm_fcb = pblock +cpm_default_dma = pblock + 0x25 + +BDOS = start-3 + +BDOS_CONOUT = 2 +BDOS_PRINTSTRING = 9 +BDOS_SELECT_DRIVE = 14 +BDOS_FINDFIRST = 17 +BDOS_FINDNEXT = 18 +BDOS_GET_CURRENT_DRIVE = 25 +BDOS_SET_DMA = 26 + +FCB_DR = 0 + +MAXFILES = 255 + +OFFSET_NAME = 0 +OFFSET_RECS = 11 +OFFSET_LAST = 13 +FULL_SIZE = 14 + +.bss files, 3570 \ MAXFILES * 14 +.bss idx, 510 \ MAXFILES * 2 + +.zp ptr1, 2 \ clobbered by print +.zp ptr2, 2 \ idem + +.zp tmp, 4 +.zp nfiles, 1 +.zp ret, 1 +count=ret +endcondx=tmp+3 + +.zp pentry, 2 + +.zp pidx, 2 +.zp pidx2, 2 +.zp pfile, 2 +.zp pfile2, 2 + +start: +.expand 1 + + ldy #BDOS_GET_CURRENT_DRIVE + jsr BDOS + sta tmp + + lda cpm_fcb + beq keepcur + + sta tmp + dec tmp \ fcb.dr - 1 + +keepcur: + lda tmp + ldy #BDOS_SELECT_DRIVE + jsr BDOS + + ldy #BDOS_GET_CURRENT_DRIVE + jsr BDOS + + cmp tmp + beq success + + lda #error + ldy #BDOS_PRINTSTRING + jsr BDOS + rts + +success: + lda cpm_fcb+1 + cmp #' ' + bne no_fill_wildcards + + ldy #10 + lda #'?' +fill: + sta cpm_fcb+1,y + dey + bpl fill + +no_fill_wildcards: + lda #0 + sta nfiles + + ldy #BDOS_SET_DMA + lda #cpm_default_dma + jsr BDOS + +\ collect_files + + ldy #BDOS_FINDFIRST + lda #cpm_fcb + jsr BDOS + + jmp test_ff + + .label too_many_files + + .zrepeat + asl A + asl A + asl A + asl A + asl A \ * 32 + clc + adc #cpm_default_dma + sta pentry+1 + + \ find file in list + + lda #files-1 + sta pfile+1 + + ldx nfiles + jmp test_nfiles + + .label found_name + .label inc_pfile + + .zrepeat + + ldy #11 + + .zrepeat + lda (pfile),y + cmp (pentry),y + bne inc_pfile \ decide it is not equal + dey + .zuntil eq + beq found_name \ bra + + inc_pfile: + lda pfile + clc + adc #14 + sta pfile + .zif cs + inc pfile+1 + .zendif + + dex + + test_nfiles: + + .zuntil eq + + \ not found, add to end of list. + \ note that pfile still points to list entry - 1 + + ldy #11 + .zrepeat + lda (pentry),y + sta (pfile),y + dey + .zuntil eq + + ldy #12 + lda #0 \ init to zero + sta (pfile),y + iny + sta (pfile),y + iny + sta (pfile),y + + inc nfiles + lda nfiles + cmp #MAXFILES + bne found_name + + ldy #BDOS_PRINTSTRING + lda #too_many_files + jsr BDOS + rts + + found_name: + + \ update records and last record + + ldy #15 \ RC + lda (pentry),y + clc + ldy #12 + adc (pfile),y + sta (pfile),y + iny + lda (pfile),y \ there's no inc (pfile),y + adc #0 + sta (pfile),y + + \ ldy #13 \ S1 Y is already 13 + lda (pentry),y + iny \ and happens to need to be 14 here + sta (pfile),y + + ldy #BDOS_FINDNEXT + lda #cpm_fcb + jsr BDOS + + test_ff: + cmp #$ff + .zuntil eq + +\ sorting with simple bubble sort which fast enough + + jsr sort + +\ print to screen + + lda #idx-2 + sta pidx+1 + + jmp check_nfiles \ start at end of loop in case it's an empty disk + +print_next: + lda pidx + clc + adc #2 + sta pidx + .zif cs + inc pidx+1 + .zendif + + ldy #0 + lda (pidx),y + sta pfile + iny + lda (pidx),y + sta pfile+1 + + ldy #11 + lda (pfile),y + sta tmp + iny + lda (pfile),y + sta tmp+1 + iny + lda #0 + sta tmp+2 + + ldx #7 +mul128: \ 24-bit mul, files can be larger than 65535 + clc + rol tmp + rol tmp+1 + rol tmp+2 + dex + bne mul128 + + lda (pfile),y + tax \ save in x + beq no_adjust + + lda tmp + sec + sbc #128 + sta tmp + lda tmp+1 + sbc #0 + sta tmp+1 + lda tmp+2 + sbc #0 + sta tmp+2 + + txa \ get back from x + clc + adc tmp + sta tmp + .zif cs + inc tmp+1 + .zif eq + inc tmp+2 + .zendif + .zendif + +no_adjust: + lda tmp+2 \ we cannot print a value larger than 65535 + beq no_adjust2 + +test: + lda #$ff + sta tmp + lda #$ff + sta tmp+1 + +no_adjust2: + lda tmp + ldx tmp+1 + jsr print16padded + + ldy #BDOS_CONOUT + lda #' " + jsr BDOS + +\ we cannot use BDOS_PRINTSTRING because filenames can contain '$' + + ldy #0 + + .zrepeat + lda (pfile),y + and #$7f \ 7-bit ASCII + sty tmp + + ldy #BDOS_CONOUT + jsr BDOS + + ldy tmp + iny + cpy #11 + .zuntil eq + + ldy #9 + lda (pfile),y + and #$80 + beq no_sysfile + + ldy #BDOS_PRINTSTRING + lda #sysfile + jsr BDOS + +no_sysfile: + + ldy #8 + lda (pfile),y + and #$80 + beq no_rofile + + ldy #BDOS_PRINTSTRING + lda #rofile + jsr BDOS + +no_rofile: + ldy #BDOS_CONOUT + lda #13 + jsr BDOS + ldy #BDOS_CONOUT + lda #10 + jsr BDOS + +check_nfiles: + lda nfiles + beq done + dec nfiles + jmp print_next + +done: + rts + +\ ---------------------------------------------- + +.zproc sort + +\ init index + + lda #files + sta pfile+1 + + lda #idx + sta pidx+1 + + ldy #0 + ldx nfiles + .zrepeat + lda pfile + sta (pidx),y + iny + lda pfile+1 + sta (pidx),y + dey + lda pidx + clc + adc #2 + sta pidx + .zif cs + inc pidx+1 + .zendif + lda pfile + clc + adc #14 + sta pfile + .zif cs + inc pfile+1 + .zendif + dex + .zuntil eq + +\ sort index + + ldx nfiles + beq no_files + dex \ repeat nfiles - 1 + beq just_one_file + stx endcondx + + ldx #0 + .zrepeat + nop + nop + nop + + stx count + + lda endcondx + sec + sbc #1 + sbc count + sta count + + lda #idx + sta pidx+1 + lda #idx+2 + sta pidx2+1 + + \ compare + + jloop: + + ldy #0 + lda (pidx),y + sta pfile + lda (pidx2),y + sta pfile2 + iny + lda (pidx),y + sta pfile+1 + lda (pidx2),y + sta pfile2+1 + dey + + php \ instead of unrolling 11 compares + .label further_checks + .zrepeat + plp + lda (pfile),y + cmp (pfile2),y + bne further_checks + php + iny + cpy #11 + .zuntil eq + + plp + + further_checks: + bcc lower + beq same + higher: + + \ SWAP + + ldy #0 + lda (pidx),y + sta tmp + lda (pidx2),y + sta (pidx),y + lda tmp + sta (pidx2),y + + iny + lda (pidx),y + sta tmp + lda (pidx2),y + sta (pidx),y + lda tmp + sta (pidx2),y + + same: + lower: + lda pidx + clc + adc #2 + sta pidx + .zif cs + inc pidx+1 + .zendif + lda pidx2 + clc + adc #2 + sta pidx2 + .zif cs + inc pidx2+1 + .zendif + + lda count + beq ready + dec count + jmp jloop + + ready: + inx + cpx endcondx + .zuntil eq + +no_files: +just_one_file: + rts +.zendproc + +\ Prints XA in decimal. Y is the padding char or 0 for no padding. + +.zproc print16padded + ldy #' ' \ always pad with ' ' + sta ptr1+0 + stx ptr1+1 + sty ptr2+0 + + .label dec_table + .label skip + .label justprint + + ldy #8 + .zrepeat + ldx #0xff + sec + .zrepeat + lda ptr1+0 + sbc dec_table+0, y + sta ptr1+0 + + lda ptr1+1 + sbc dec_table+1, y + sta ptr1+1 + + inx + .zuntil cc + + lda ptr1+0 + adc dec_table+0, y + sta ptr1+0 + + lda ptr1+1 + adc dec_table+1, y + sta ptr1+1 + + tya + pha + txa + pha + + .zif eq \ if digit is zero, check padding + lda ptr2+0 \ get the padding character + beq skip \ if zero, no padding + bne justprint \ otherwise, use it + .zendif + + ldx #'0' \ printing a digit, so reset padding + stx ptr2+0 + ora #'0' \ convert to ASCII + justprint: + ldy #BDOS_CONOUT + jsr BDOS + skip: + pla + tax + pla + tay + + dey + dey + .zuntil mi + rts + +dec_table: + .word 1, 10, 100, 1000, 10000 +.zendproc + +error: + .byte "drive error\r\n$" +too_many_files: + .byte "too many files\r\n$" +sysfile: + .byte " (s)$" +rofile: + .byte " (r/o)$" + diff --git a/diskdefs b/diskdefs index 2d2ff9e4..c4b39a5b 100644 --- a/diskdefs +++ b/diskdefs @@ -52,3 +52,28 @@ diskdef generic-1m os 2.2 end +# Simple SSSD disk on the Atari 810 or 1050 diskdrive +# Reserve three tracks for BIOS+BDOS +diskdef atari90 + seclen 128 + tracks 40 + sectrk 18 + blocksize 1024 + maxdir 64 + boottrk 3 + os 2.2 +end + +# Large ATR for use with emulators, hardware drive emulators or mounting +# with AVG/SIDE cartridges on real hardware. seclen stil 128 so it uses +# the same BIOS code as before. 8190 sectors. Almost 1MB. +diskdef atarihd + seclen 128 + tracks 455 + sectrk 18 + blocksize 2048 + maxdir 128 + boottrk 3 + os 2.2 +end + diff --git a/doc/atari800.png b/doc/atari800.png new file mode 100644 index 00000000..6e3687c3 Binary files /dev/null and b/doc/atari800.png differ diff --git a/include/atari800.inc b/include/atari800.inc new file mode 100644 index 00000000..ca222ce8 --- /dev/null +++ b/include/atari800.inc @@ -0,0 +1,76 @@ +; Atari 800 OS defines + +MEMTOP = $02e5 + +; CIO + +CIOV = $e456 + +SHFLOK = $02be + +ICHID = $0340 +ICDNO = $0341 +ICCOM = $0342 +ICSTA = $0343 +ICBAL = $0344 +ICBAH = $0345 +ICPTL = $0346 +ICPTH = $0347 +ICBLL = $0348 +ICBLH = $0349 +ICAX1 = $034a +ICAX2 = $034b +ICAX3 = $034c +ICAX4 = $034d +ICAX5 = $034e +ICAX6 = $034f + +COPEN = 3 +CCLSE = 12 +CGTXT = 5 +CPTXT = 9 +CGBIN = 7 +CPBIN = 11 + +ATARI_CLS = 125 +ATARI_BS = 126 +ATARI_TAB = 127 +ATARI_EOL = 155 +ATARI_DEL_LINE = 156 +ATARI_INS_LINE = 157 +ATARI_BEL = 253 +ATARI_DEL = 254 +ATARI_INS = 255 + +TERM_BEL = 7 +TERM_BS = 8 +TERM_TAB = 9 +TERM_LF = 10 +TERM_CLS = 26 +TERM_CR = 13 +TERM_DEL = 127 + +LMARGN = $52 +COLCRS = $55 +COLOR1 = $02c5 +COLOR2 = $02c6 + +; SIO Device Control Block + +DDEVIC = $0300 +DUNIT = $0301 +DCOMND = $0302 +DSTATS = $0303 +DBUFLO = $0304 +DBUFHI = $0305 +DTIMLO = $0306 +DBYTLO = $0308 +DBYTHI = $0309 +DAUX1 = $030a +DAUX2 = $030b + +SIO_READ_SECTOR = $52 +SIO_WRITE_SECTOR = $50 + +SIOV = $e459 + diff --git a/scripts/atari800.ld b/scripts/atari800.ld new file mode 100644 index 00000000..dee6232f --- /dev/null +++ b/scripts/atari800.ld @@ -0,0 +1,28 @@ +MEMORY { + zp : ORIGIN = 0x80, LENGTH = 0x7e + ram (rw) : ORIGIN = 0x600, LENGTH = 0x800 +} + +SECTIONS { + .zp : { + *(.zp .zp.*) + __USERZEROPAGE_START__ = .; + __USERZEROPAGE_END__ = 0xfe; + } >zp + + .text : { + *(.text .text.*) + } >ram + .data : { *(.data .data.* .rodata .rodata.*) } > ram + .noinit (NOLOAD) : { + *(.noinit .noinit.*) + . = ALIGN(256); + __USERTPA_START__ = .; + __USERTPA_END__ = 0xbc00; + } >ram +} + +OUTPUT_FORMAT { + TRIM(ram) +} + diff --git a/scripts/atari800hd.ld b/scripts/atari800hd.ld new file mode 120000 index 00000000..dba14334 --- /dev/null +++ b/scripts/atari800hd.ld @@ -0,0 +1 @@ +atari800.ld \ No newline at end of file diff --git a/src/bdos.S b/src/bdos.S index 2070b057..ae97082b 100644 --- a/src/bdos.S +++ b/src/bdos.S @@ -540,7 +540,7 @@ zproc entry_READLINE cmp #3 zif_eq ldy buffer_pos - cpy #1 + cpy #2 zif_eq jmp entry_EXIT zendif diff --git a/src/bios/atari800.S b/src/bios/atari800.S new file mode 100644 index 00000000..1b2c0ba0 --- /dev/null +++ b/src/bios/atari800.S @@ -0,0 +1,428 @@ +; CP/M-65 Copyright © 2022 David Given +; This file is licensed under the terms of the 2-clause BSD license. Please +; see the COPYING file in the root project directory for the full text. + +; Atari 800 XL/XE Port Copyright © 2023 Ivo van Poorten + +; Disk layout SS SD 40 tracks x 18 sectors = 720 sectors. +; or +; 455 tracks x 18 sectors = 8190 sectors (CP/M FS uses 8176) + +; First three tracks are reserved for BIOS and BDOS so there is room to grow. +; Use Atari OS bootloader to load BDOS instead of requiring DOS.SYS, which +; is pretty big and useless afterwards as it has no ftell()/fseek() + +#include "zif.inc" +#include "cpm65.inc" +#include "atari800.inc" +#include "driver.inc" + +ZEROPAGE + +.global ptr +.global ptr1 +save_y: +ptr: .word 0 +ptr1: .word 0 + + .text + +.global _start +_start: + + ; Boot sector + + .byte 0 +#ifdef ATARI_HD + .byte 10 + 28 ; 8 + 28 (BIOS+BDOS) (BIOS must be multiple of 2, one page) +#else + .byte 8 + 28 ; 8 + 28 (BIOS+BDOS) (BIOS must be multiple of 2, one page) +#endif + .word $0600 + .word init ; just use init and never return + +init: + lda MEMTOP+1 + sta mem_end + + lda #0 + sta LMARGN + sta COLCRS ; leftover cursor will be overwritten by banner + sta COLCRS+1 + sta SHFLOK ; start lower case + + lda #$b0 ; green + sta COLOR2 ; background + lda #$0f ; luma only + sta COLOR1 ; foreground + + ; IOCB0 E: is already open + + ; Print banner. + + ldy #banner_end - banner + zrepeat + lda banner - 1, y + sty save_y + jsr tty_conout + ldy save_y + dey + zuntil_eq + + ; Open IOCB1 for K device (no direct OS call hack) + + ldx #1*16 + lda #4 + sta ICAX1,x + lda #0 + sta ICAX2,x + lda #COPEN + sta ICCOM,x + lda #kdevice + sta ICBAH,x + + jsr CIOV + + ; Figure out the start and end of the TPA. + ; mem_base, mem_end are already set correctly for where BDOS loads + + ; BIOS initialisation + + jsr initdrivers + + ; Load the BDOS image to mem_base + ; BDOS is already loaded at mem_base by the Atari OS bootloader + + ; Relocate it. + + lda mem_base + ldx zp_base + jsr entry_RELOCATE + + ; Compute the entry address and jump. + + lda mem_base + pha + lda #COMHDR_ENTRY-1 ; rts addresses are one before the target + pha + + lda #biosentry + rts ; indirect jump to BDOS +zendproc + +banner: ; reversed! + .byte 10,13 + .ascii "008 iratA eht rof 56-M/PC" +banner_end: + +.data +.global drvtop +drvtop: .word drv_TTY + +; --- BIOS entrypoints ------------------------------------------------------ + +defdriver TTY, DRVID_TTY, drvstrat_TTY, 0 + +; TTY driver strategy routine. +; Y=TTY opcode. +zproc drvstrat_TTY + pha + lda jmptable_lo, y + sta ptr+0 + lda jmptable_hi, y + sta ptr+1 + pla + jmp (ptr) + +jmptable_lo: + .byte tty_const@mos16lo + .byte tty_conin@mos16lo + .byte tty_conout@mos16lo +jmptable_hi: + .byte tty_const@mos16hi + .byte tty_conin@mos16hi + .byte tty_conout@mos16hi +zendproc + +; Blocks and waits for the next keypress; returns it in A. + +zproc tty_conin + ldx #1*16 + lda #CGBIN + sta ICCOM,x + lda #1 + sta ICBLL,x + lda #0 + sta ICBLH,x + lda #charin + sta ICBAH,x + + jsr CIOV + + lda charin + + cmp #ATARI_EOL + zif_eq + lda #13 + rts + zendif + + cmp #ATARI_BS + zif_eq + lda #8 + rts + zendif + + cmp #ATARI_TAB + zif_eq + lda #9 +; rts [[fallthrough]] + zendif + + rts +zendproc + +zproc tty_const + lda #$00 + clc + rts +zendproc + +; Output character in A + +zproc tty_conout + + cmp #TERM_LF + zif_eq + lda #ATARI_EOL + bne conout + zendif + + cmp #TERM_CR + zif_eq + lda #0 + sta COLCRS + rts + zendif + + cmp #TERM_BS + zif_eq + lda #ATARI_BS + bne conout + zendif + + cmp #TERM_TAB + zif_eq + lda #ATARI_TAB + bne conout + zendif + + cmp #TERM_DEL + zif_eq +; lda #ATARI_DEL + lda #ATARI_BS ; DEL=BS apparently + bne conout + zendif + + cmp #TERM_CLS + zif_eq + lda #ATARI_CLS + bne conout + zendif + +; The following two ATARI_ codes need to be escaped to actually print the +; associated glyph. The glyph of 127 (the third that needs escaping) is +; not available because that's ASCII DEL. + + cmp #ATARI_CLS + zif_eq + lda #27 ; ESC + jsr tty_conout + lda #ATARI_CLS + bne conout + zendif + + cmp #ATARI_BS + zif_eq + lda #27 + jsr tty_conout + lda #ATARI_BS +; bne conout ; fallthrough + zendif + +conout: + sta charout + + ldx #0 ; IOCB number $00, $10, etc... + lda #CPBIN + sta ICCOM,x + lda #1 ; length + sta ICBLL,x + lda #0 + sta ICBLH,x + lda #charout + sta ICBAH,x + jmp CIOV +zendproc + +; Sets the current DMA address. + +zproc entry_SETDMA + sta dma+0 + stx dma+1 + rts +zendproc + +; Select a disk. +; A is the disk number. +; Returns the DPH in XA. +; Sets carry on error. + +zproc entry_SELDSK + cmp #0 + zif_ne + sec ; invalid drive + rts + zendif + + sta drive_number + + lda #dph + clc + rts +zendproc + +; Set the current absolute sector number. +; XA is a pointer to a three-byte number. + +; BDOS looks at DPH to see how many sectors to skip. + +zproc entry_SETSEC + sta ptr+0 + stx ptr+1 + ldy #2 + zrepeat + lda (ptr), y + sta sector_num, y + dey + zuntil_mi + rts +zendproc + +zproc entry_READ + lda #$40 ; data direction receive + sta DSTATS + lda #SIO_READ_SECTOR + bne do_SIO +zendproc ; fallthrough + +zproc entry_WRITE + lda #$80 ; data direction send + sta DSTATS + lda #SIO_WRITE_SECTOR + +do_SIO: + sta DCOMND + lda #$31 + clc + adc drive_number + sta DDEVIC + and #$0f + sta DUNIT + + lda dma + sta DBUFLO + lda dma+1 + sta DBUFHI + + lda #128 + sta DBYTLO + lda #0 + sta DBYTHI + + lda sector_num ; Atari starts counting at sector 1 + clc + adc #1 + sta DAUX1 + lda sector_num+1 + adc #0 + sta DAUX2 + + jsr SIOV + + lda #0 + rts +zendproc + +zproc entry_GETTPA + lda mem_base + ldx mem_end + rts +zendproc + +zproc entry_SETTPA + sta mem_base + stx mem_end + rts +zendproc + +zproc entry_GETZP + lda zp_base + ldx zp_end + rts +zendproc + +zproc entry_SETZP + sta zp_base + stx zp_end + rts +zendproc + +zproc entry_SETBANK + rts +zendproc + + .data +zp_base: .byte __USERZEROPAGE_START__ +zp_end: .byte __USERZEROPAGE_END__ + +mem_base: .byte __USERTPA_START__@mos16hi +mem_end: .byte __USERTPA_END__@mos16hi + +kdevice: .ascii "K" + .byte 155 + +; DPH for drive 0 (our only drive) + +; number of sectors, blocksize, direntries, reserved _sectors_ + +; 54 reserved sectors = 6912 bytes for BIOS and BDOS + +#ifdef ATARI_HD +define_drive dph, 8190, 2048, 128, 54 +#else +define_drive dph, 720, 1024, 64, 54 +#endif + +NOINIT + +charin: +charout: .byte 0 + +drive_number: .byte 0 + +dma: .word 0 ; current DMA +sector_num: .fill 3 ; current 24-bit sector number + +directory_buffer: .fill 128 + +; vim: filetype=asm sw=4 ts=4 et + diff --git a/src/bios/atari800hd.S b/src/bios/atari800hd.S new file mode 120000 index 00000000..c14d3dda --- /dev/null +++ b/src/bios/atari800hd.S @@ -0,0 +1 @@ +atari800.S \ No newline at end of file