diff --git a/cfg/atari-cassette.cfg b/cfg/atari-cassette.cfg new file mode 100644 index 000000000..2116aecd0 --- /dev/null +++ b/cfg/atari-cassette.cfg @@ -0,0 +1,40 @@ +FEATURES { + STARTADDRESS: default = $0900; +} +SYMBOLS { + __STACKSIZE__: type = weak, value = $0800; # 2k stack + __RESERVED_MEMORY__: type = weak, value = $0000; + __STARTADDRESS__: type = export, value = %S; + _cas_hdr: type = import; +} +MEMORY { + ZP: file = "", define = yes, start = $0082, size = $007E; + RAM: file = %O, define = yes, start = %S, size = $BC20 - __STACKSIZE__ - __RESERVED_MEMORY__ - %S; +} +SEGMENTS { + CASHDR: load = RAM, type = ro; + STARTUP: load = RAM, type = ro, define = yes, optional = yes; + LOWCODE: load = RAM, type = ro, define = yes, optional = yes; + INIT: load = RAM, type = ro, optional = yes; + CODE: load = RAM, type = ro, define = yes; + RODATA: load = RAM, type = ro, optional = yes; + DATA: load = RAM, type = rw, optional = yes; + BSS: load = RAM, type = bss, define = yes, optional = yes; + ZEROPAGE: load = ZP, type = zp, optional = yes; + EXTZP: load = ZP, type = zp, optional = yes; +} +FEATURES { + CONDES: type = constructor, + label = __CONSTRUCTOR_TABLE__, + count = __CONSTRUCTOR_COUNT__, + segment = INIT; + CONDES: type = destructor, + label = __DESTRUCTOR_TABLE__, + count = __DESTRUCTOR_COUNT__, + segment = RODATA; + CONDES: type = interruptor, + label = __INTERRUPTOR_TABLE__, + count = __INTERRUPTOR_COUNT__, + segment = RODATA, + import = __CALLIRQ__; +} diff --git a/doc/atari.sgml b/doc/atari.sgml index 5e8bec726..51e24a239 100644 --- a/doc/atari.sgml +++ b/doc/atari.sgml @@ -221,6 +221,16 @@ that the cartridge doesn't prevent the booting of DOS. The option byte will be located at address $BFFD. For more information about its use, see e.g. "Mapping the Atari". + + +This config file can be used to create cassette boot files. It's suited both +for C and assembly language programs. + +The size of a cassette boot file is restricted to 32K. Larger programs +would need to be split in more parts and the parts to be loaded manually. + +To write the generated file to a cassette, a utility to run +on an Atari is provided in the diff --git a/libsrc/Makefile b/libsrc/Makefile index 3d8277ae5..89f952376 100644 --- a/libsrc/Makefile +++ b/libsrc/Makefile @@ -102,6 +102,7 @@ MKINC = $(GEOS) \ nes TARGETUTIL = apple2 \ + atari \ geos-apple GEOSDIRS = common \ diff --git a/libsrc/atari/cashdr.s b/libsrc/atari/cashdr.s new file mode 100644 index 000000000..99aefe68f --- /dev/null +++ b/libsrc/atari/cashdr.s @@ -0,0 +1,37 @@ +; +; Cassette boot file header +; +; Christian Groessler, chris@groessler.org, 2014 +; + +;DEBUG = 1 + +.ifndef __ATARIXL__ + + .include "atari.inc" + + .import __BSS_RUN__, __STARTADDRESS__, _cas_init + .export _cas_hdr + +.assert ((__BSS_RUN__ - __STARTADDRESS__ + 127) / 128) < $101, error, "File to big to load from cassette" + + +; for a description of the cassette header, see De Re Atari, appendix C + +.segment "CASHDR" + +_cas_hdr: + .byte 0 ; ignored + .byte <((__BSS_RUN__ - __STARTADDRESS__ + 127) / 128) ; # of 128-byte records to read + .word __STARTADDRESS__ ; load address + .word _cas_init ; init address + +.ifdef DEBUG + lda #33 + ldy #80 + sta (SAVMSC),y +.endif + clc + rts + +.endif ; .ifdef __ATARIXL__ diff --git a/libsrc/atari/casinit.s b/libsrc/atari/casinit.s new file mode 100644 index 000000000..c91989aad --- /dev/null +++ b/libsrc/atari/casinit.s @@ -0,0 +1,31 @@ +; +; Cassette boot file init routine +; +; Christian Groessler, chris@groessler.org, 2014 +; + +;DEBUG = 1 + +.ifndef __ATARIXL__ + + .include "atari.inc" + + .import start + .export _cas_init + +.segment "INIT" + +_cas_init: +.ifdef DEBUG + lda #34 + ldy #81 + sta (SAVMSC),y +.endif + + lda #start + sta DOSVEC+1 + rts + +.endif ; .ifdef __ATARIXL__ diff --git a/libsrc/atari/targetutil/Makefile.inc b/libsrc/atari/targetutil/Makefile.inc new file mode 100644 index 000000000..a54d96100 --- /dev/null +++ b/libsrc/atari/targetutil/Makefile.inc @@ -0,0 +1,14 @@ +ifeq ($(TARGET),atari) + +DEPS += ../wrk/$(TARGET)/w2cas.d + +../wrk/$(TARGET)/w2cas.o: CC65FLAGS:=-O -W error +../wrk/$(TARGET)/w2cas.o: $(SRCDIR)/targetutil/w2cas.c | ../wrk/$(TARGET) + $(COMPILE_recipe) + +../targetutil/w2cas.com: ../wrk/$(TARGET)/w2cas.o ../lib/$(TARGET).lib | ../targetutil + $(LD65) -o $@ -t $(TARGET) $^ + +$(TARGET): ../targetutil/w2cas.com + +endif diff --git a/libsrc/atari/targetutil/w2cas.c b/libsrc/atari/targetutil/w2cas.c new file mode 100644 index 000000000..050218cfe --- /dev/null +++ b/libsrc/atari/targetutil/w2cas.c @@ -0,0 +1,186 @@ +/* w2cas.c -- write file to cassette + * + * This program writes a boot file (typically linked with + * 'atari-cassette.cfg') to the cassette. + * Only files < 32K are supported, since the loading of + * larger files requires a special loader inside the program. + * + * Christian Groessler, chris@groessler.org, 2014 + */ + +#include +#include +#include +#include +#include <6502.h> +#include +#include + +static int verbose = 1; +static char C_dev[] = "C:"; + +static struct __iocb *findfreeiocb(void) +{ + struct __iocb *iocb = &IOCB; /* first IOCB (#0) */ + int i; + + for (i = 0; i < 8; i++) { + if (iocb->handler == 0xff) + return iocb; + iocb++; + } + return NULL; +} + +int main(int argc, char **argv) +{ + char *filename, *x; + char buf[20]; + FILE *file; + unsigned char *buffer; + size_t filen, buflen = 32768l + 1; + struct regs regs; + struct __iocb *iocb = findfreeiocb(); + int iocb_num; + + if (! iocb) { + fprintf(stderr, "couldn't find a free iocb\n"); + if (_dos_type != 1) + cgetc(); + return 1; + } + iocb_num = (iocb - &IOCB) * 16; + if (verbose) + printf("using iocb index $%02X ($%04X)\n", iocb_num, iocb); + + if (argc < 2) { + printf("\nfilename: "); + x = fgets(buf, 19, stdin); + printf("\n"); + if (! x) + return 1; + if (*x && *(x + strlen(x) - 1) == '\n') + *(x + strlen(x) - 1) = 0; + filename = x; + } + else { + filename = *(argv+1); + } + + /* allocate buffer */ + buffer = malloc(buflen); + if (! buffer) { + buflen = _heapmaxavail(); /* get as much as we can */ + buffer = malloc(buflen); + if (! buffer) { + fprintf(stderr, "cannot alloc %ld bytes -- aborting...\n", (long)buflen); + if (_dos_type != 1) + cgetc(); + return 1; + } + } + if (verbose) + printf("buffer size: %ld bytes\n", (long)buflen); + + /* open file */ + file = fopen(filename, "rb"); + if (! file) { + free(buffer); + fprintf(stderr, "cannot open '%s': %s\n", filename, strerror(errno)); + if (_dos_type != 1) + cgetc(); + return 1; + } + + /* read file -- file length must be < 32K */ + if (verbose) + printf("reading input file...\n"); + filen = fread(buffer, 1, buflen, file); + if (! filen) { + fprintf(stderr, "read error\n"); + file_err: + fclose(file); + free(buffer); + if (_dos_type != 1) + cgetc(); + return 1; + } + if (filen > 32767l) { + fprintf(stderr, "file is too large (must be < 32768)\n"); + goto file_err; + } + if (filen == buflen) { /* we have a buffer < 32768 and the file fits into it (and is most probably larger) */ + fprintf(stderr, "not enough memory\n"); + goto file_err; + } + if (verbose) + printf("file size: %ld bytes\n", (long)filen); + + /* close input file */ + fclose(file); + + /* open cassette */ + if (verbose) + printf("opening cassette...\n"); + iocb->buffer = C_dev; + iocb->aux1 = 8; /* open for output */ + iocb->aux2 = 128; /* short breaks and no stop between data blocks */ + iocb->command = IOCB_OPEN; + regs.x = iocb_num; + regs.pc = 0xe456; /* CIOV */ + + _sys(®s); + if (regs.y != 1) { + fprintf(stderr, "CIO call to open cassette returned %d\n", regs.y); + free(buffer); + if (_dos_type != 1) + cgetc(); + return 1; + } + + /* write file */ + if (verbose) + printf("writing to cassette...\n"); + iocb->buffer = buffer; + iocb->buflen = filen; + iocb->command = IOCB_PUTCHR; + regs.x = iocb_num; + regs.pc = 0xe456; /* CIOV */ + + _sys(®s); + if (regs.y != 1) { + fprintf(stderr, "CIO call to write file returned %d\n", regs.y); + free(buffer); + + iocb->command = IOCB_CLOSE; + regs.x = iocb_num; + regs.pc = 0xe456; /* CIOV */ + _sys(®s); + + if (_dos_type != 1) + cgetc(); + return 1; + } + + /* free buffer */ + free(buffer); + + /* close cassette */ + iocb->command = IOCB_CLOSE; + regs.x = iocb_num; + regs.pc = 0xe456; /* CIOV */ + _sys(®s); + + if (regs.y != 1) { + fprintf(stderr, "CIO call to close cassette returned %d\n", regs.y); + if (_dos_type != 1) + cgetc(); + return 1; + } + + /* all is fine */ + printf("success\n"); + if (_dos_type != 1) + cgetc(); + return 0; +}