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:
commit
6fe2ce7269
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
|
||||
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>
|
||||
|
||||
|
@ -102,6 +102,7 @@ MKINC = $(GEOS) \
|
||||
nes
|
||||
|
||||
TARGETUTIL = apple2 \
|
||||
atari \
|
||||
geos-apple
|
||||
|
||||
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;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user