1
0
mirror of https://github.com/cc65/cc65.git synced 2025-02-06 12:31:12 +00:00

Merge pull request #85 from groessler/cassette

Atari: add support to create cassette boot files
This commit is contained in:
Oliver Schmidt 2014-03-03 20:19:32 +01:00
commit 6fe2ce7269
7 changed files with 319 additions and 0 deletions

40
cfg/atari-cassette.cfg Normal file
View 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__;
}

View File

@ -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".
<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>

View File

@ -102,6 +102,7 @@ MKINC = $(GEOS) \
nes
TARGETUTIL = apple2 \
atari \
geos-apple
GEOSDIRS = common \

37
libsrc/atari/cashdr.s Normal file
View 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
View 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__

View 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

View 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(&regs);
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(&regs);
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(&regs);
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(&regs);
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;
}