Merge pull request #85 from groessler/cassette
Atari: add support to create cassette boot files
This commit is contained in:
40
cfg/atari-cassette.cfg
Normal file
40
cfg/atari-cassette.cfg
Normal file
@@ -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__;
|
||||||
|
}
|
||||||
@@ -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
|
The option byte will be located at address $BFFD. For more information
|
||||||
about its use, see e.g. "Mapping the Atari".
|
about its use, see e.g. "Mapping the Atari".
|
||||||
|
|
||||||
|
<sect2><tt/atari-cassette.cfg/<p>
|
||||||
|
|
||||||
|
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 <tt/targetutil/ directory (<tt/w2cas.com/).
|
||||||
|
|
||||||
<sect1><tt/atarixl/ config files<p>
|
<sect1><tt/atarixl/ config files<p>
|
||||||
|
|
||||||
|
|||||||
@@ -102,6 +102,7 @@ MKINC = $(GEOS) \
|
|||||||
nes
|
nes
|
||||||
|
|
||||||
TARGETUTIL = apple2 \
|
TARGETUTIL = apple2 \
|
||||||
|
atari \
|
||||||
geos-apple
|
geos-apple
|
||||||
|
|
||||||
GEOSDIRS = common \
|
GEOSDIRS = common \
|
||||||
|
|||||||
37
libsrc/atari/cashdr.s
Normal file
37
libsrc/atari/cashdr.s
Normal file
@@ -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__
|
||||||
31
libsrc/atari/casinit.s
Normal file
31
libsrc/atari/casinit.s
Normal file
@@ -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
|
||||||
|
lda #>start
|
||||||
|
sta DOSVEC+1
|
||||||
|
rts
|
||||||
|
|
||||||
|
.endif ; .ifdef __ATARIXL__
|
||||||
14
libsrc/atari/targetutil/Makefile.inc
Normal file
14
libsrc/atari/targetutil/Makefile.inc
Normal file
@@ -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
|
||||||
186
libsrc/atari/targetutil/w2cas.c
Normal file
186
libsrc/atari/targetutil/w2cas.c
Normal file
@@ -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 <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <6502.h>
|
||||||
|
#include <atari.h>
|
||||||
|
#include <conio.h>
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user