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!
+
@@ -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