diff --git a/cfg/atari-asm-xex.cfg b/cfg/atari-asm-xex.cfg new file mode 100644 index 000000000..f0a6291db --- /dev/null +++ b/cfg/atari-asm-xex.cfg @@ -0,0 +1,24 @@ +FEATURES { + STARTADDRESS: default = $2E00; +} +SYMBOLS { + __STARTADDRESS__: type = export, value = %S; +} +MEMORY { + ZP: file = "", define = yes, start = $0082, size = $007E; + MAIN: file = %O, define = yes, start = %S, size = $BC20 - %S; +} +FILES { + %O: format = atari; +} +FORMATS { + atari: runad = start; +} +SEGMENTS { + ZEROPAGE: load = ZP, type = zp, optional = yes; + EXTZP: load = ZP, type = zp, optional = yes; # to enable modules to be able to link to C and assembler programs + CODE: load = MAIN, type = rw, define = yes; + RODATA: load = MAIN, type = ro optional = yes; + DATA: load = MAIN, type = rw optional = yes; + BSS: load = MAIN, type = bss, optional = yes, define = yes; +} diff --git a/cfg/atari-xex.cfg b/cfg/atari-xex.cfg index f0a6291db..5f390e12c 100644 --- a/cfg/atari-xex.cfg +++ b/cfg/atari-xex.cfg @@ -1,24 +1,56 @@ +# Sample linker configuration for C programs using the Atari binary file support. +# Use with: cl65 -tatari -Catari-c-xex.cfg prog.c -o prog.xex FEATURES { - STARTADDRESS: default = $2E00; + STARTADDRESS: default = $2000; } SYMBOLS { - __STARTADDRESS__: type = export, value = %S; + __SYSTEM_CHECK__: type = import; # force inclusion of "system check" load chunk + __STACKSIZE__: type = weak, value = $0800; # 2k stack + __STARTADDRESS__: type = export, value = %S; + __RESERVED_MEMORY__: type = weak, value = $0000; + __SYSCHKHDR__: type = export, value = 0; # Disable system check header + __SYSCHKTRL__: type = export, value = 0; # Disable system check trailer } MEMORY { - ZP: file = "", define = yes, start = $0082, size = $007E; - MAIN: file = %O, define = yes, start = %S, size = $BC20 - %S; + ZP: file = "", define = yes, start = $0082, size = $007E; +# "system check" load chunk + SYSCHKCHNK: file = %O, start = $2E00, size = $0300; +# "main program" load chunk + MAIN: file = %O, define = yes, start = %S, size = $BC20 - __STACKSIZE__ - __RESERVED_MEMORY__ - %S; } FILES { %O: format = atari; } FORMATS { - atari: runad = start; + atari: runad = start, + initad = SYSCHKCHNK: __SYSTEM_CHECK__; } SEGMENTS { - ZEROPAGE: load = ZP, type = zp, optional = yes; - EXTZP: load = ZP, type = zp, optional = yes; # to enable modules to be able to link to C and assembler programs - CODE: load = MAIN, type = rw, define = yes; - RODATA: load = MAIN, type = ro optional = yes; - DATA: load = MAIN, type = rw optional = yes; - BSS: load = MAIN, type = bss, optional = yes, define = yes; + ZEROPAGE: load = ZP, type = zp; + EXTZP: load = ZP, type = zp, optional = yes; + SYSCHK: load = SYSCHKCHNK, type = rw, define = yes, optional = yes; + STARTUP: load = MAIN, type = ro, define = yes; + LOWBSS: load = MAIN, type = rw, optional = yes; # not zero initialized + LOWCODE: load = MAIN, type = ro, define = yes, optional = yes; + ONCE: load = MAIN, type = ro, optional = yes; + CODE: load = MAIN, type = ro, define = yes; + RODATA: load = MAIN, type = ro; + DATA: load = MAIN, type = rw; + INIT: load = MAIN, type = rw, optional = yes; + BSS: load = MAIN, type = bss, define = yes; +} +FEATURES { + CONDES: type = constructor, + label = __CONSTRUCTOR_TABLE__, + count = __CONSTRUCTOR_COUNT__, + segment = ONCE; + 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/cfg/atarixl-xex.cfg b/cfg/atarixl-xex.cfg new file mode 100644 index 000000000..0b1fe9ca1 --- /dev/null +++ b/cfg/atarixl-xex.cfg @@ -0,0 +1,79 @@ +# Sample linker configuration for C programs using the Atari binary file support. +# Use with: cl65 -tatarixl -Catarixl-c-xex.cfg prog.c -o prog.xex +FEATURES { + STARTADDRESS: default = $2400; +} +SYMBOLS { + __SYSTEM_CHECK__: type = import; # force inclusion of "system check" load chunk + __STACKSIZE__: type = weak, value = $0800; # 2k stack + __STARTADDRESS__: type = export, value = %S; + __SYSCHKHDR__: type = export, value = 0; # Disable system check header + __SYSCHKTRL__: type = export, value = 0; # Disable system check trailer +} +MEMORY { + ZP: file = "", define = yes, start = $0082, size = $007E; + +# "system check" load chunk + SYSCHKCHNK: file = %O, start = $2E00, size = $0300; + +# "shadow RAM preparation" load chunk + SRPREPCHNK: file = %O, define = yes, start = %S, size = $7C20 - %S - $07FF; # $07FF: space for temp. chargen buffer, 1K aligned + +# "main program" load chunk + MAIN: file = %O, define = yes, start = %S + __LOWBSS_SIZE__, size = $D000 - __STACKSIZE__ - %S - __LOWBSS_SIZE__; + +# memory beneath the ROM preceeding the character generator + HIDDEN_RAM2: file = "", define = yes, start = $D800, size = $0800; + +# address of relocated character generator (same addess as ROM version) + CHARGEN: file = "", define = yes, start = $E000, size = $0400; + +# memory beneath the ROM + HIDDEN_RAM: file = "", define = yes, start = $E400, size = $FFFA - $E400; + +# UNUSED - hide + UNUSED: file = "", start = $0, size = $10; +} +FILES { + %O: format = atari; +} +FORMATS { + atari: runad = start, + initad = SYSCHKCHNK: __SYSTEM_CHECK__, + initad = SRPREPCHNK: sramprep; +} +SEGMENTS { + ZEROPAGE: load = ZP, type = zp; + EXTZP: load = ZP, type = zp, optional = yes; + + SYSCHK: load = SYSCHKCHNK, type = rw, define = yes, optional = yes; + LOWBSS: load = SRPREPCHNK, type = bss, define = yes; # shared btw. SRPREPCHNK and RAM, not zero initialized + SRPREP: load = SRPREPCHNK, type = rw, define = yes; + SHADOW_RAM: load = SRPREPCHNK, run = HIDDEN_RAM, type = rw, define = yes, optional = yes; + SHADOW_RAM2: load = SRPREPCHNK, run = HIDDEN_RAM2, type = rw, define = yes, optional = yes; + STARTUP: load = MAIN, type = ro, define = yes; + LOWCODE: load = MAIN, type = ro, define = yes, optional = yes; + ONCE: load = MAIN, type = ro, optional = yes; + CODE: load = MAIN, type = ro, define = yes; + RODATA: load = MAIN, type = ro; + DATA: load = MAIN, type = rw; + INIT: load = MAIN, type = rw, optional = yes; + BSS: load = MAIN, type = bss, define = yes; + SRPREPHDR: load = UNUSED, type = ro; + SRPREPTRL: load = UNUSED, type = ro; +} +FEATURES { + CONDES: type = constructor, + label = __CONSTRUCTOR_TABLE__, + count = __CONSTRUCTOR_COUNT__, + segment = ONCE; + 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 346377b36..42b06d83e 100644 --- a/doc/atari.sgml +++ b/doc/atari.sgml @@ -203,7 +203,7 @@ is + This config file allows writing multi segment binaries easily, without having to write the header explicitly on each segment. @@ -213,7 +213,8 @@ format support on LD65 instead of the standard binary output, so it does not have the @@ -242,6 +243,18 @@ 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 ( + +This config file shows how to write a binary using the ATARI (xex) file format +support on LD65, this simplifies the memory areas and allows to add new memory +areas easily without writing new headers and trailers. + +Note that the default C library includes the system-check chunk, so in this +linker configuration we suppress the importing of the header and trailer for +this chunk by defining the standard import symbols to a 0 value. For the +initialization address of the system-check chunk, the INITAD is set directly in +the configuration. + default config file ( @@ -265,6 +278,15 @@ The files generated by this config file include the load chunk. It can optionally be left out, see . + + +Similar to the Platform specific header files

diff --git a/doc/ld65.sgml b/doc/ld65.sgml index 9116eb442..3f159b39b 100644 --- a/doc/ld65.sgml +++ b/doc/ld65.sgml @@ -938,9 +938,24 @@ has several attributes that may be defined here. } -The Atari file format has only one attribute, + + RUNAD = symbol + + Specify a symbol as the run address of the binary, the loader will call this + address after all the file is loaded in memory. If the attribute is omitted, + no run address is included in the file. + + INITAD = memory_area : symbol + + Specify a symbol as the initialization address for the given memory area. + The binary loader will call this address just after the memory area is loaded + into memory, before continuing loading the rest of the file. + + + FORMATS { diff --git a/libsrc/atari/system_check.s b/libsrc/atari/system_check.s index 19efaf2e2..df7c433a4 100644 --- a/libsrc/atari/system_check.s +++ b/libsrc/atari/system_check.s @@ -16,8 +16,7 @@ ;DEBUG = 1 - .export __SYSTEM_CHECK__: absolute = 1 - .import __SYSCHK_LOAD__ + .export __SYSTEM_CHECK__, __SYSCHK_END__ .import __STARTADDRESS__ ; the following imports are only needed for the 'atari' target version @@ -25,10 +24,12 @@ .import __STACKSIZE__ .import __RESERVED_MEMORY__ + ; import our header and trailers + .forceimport __SYSCHKHDR__, __SYSCHKTRL__ + .include "zeropage.inc" .include "atari.inc" - .macro print_string text .local start, cont jmp cont @@ -229,25 +230,10 @@ delay1: ldx #0 .endproc -end: +__SYSTEM_CHECK__=syschk +__SYSCHK_END__: .ifndef __ATARIXL__ tmp: ; outside of the load chunk, some kind of poor man's .bss .endif -; ------------------------------------------------------------------------ -; Chunk header - -.segment "SYSCHKHDR" - - .word __SYSCHK_LOAD__ - .word end - 1 - -; ------------------------------------------------------------------------ -; Chunk "trailer" - sets INITAD - -.segment "SYSCHKTRL" - - .word INITAD - .word INITAD+1 - .word syschk diff --git a/libsrc/atari/system_check_hdr.s b/libsrc/atari/system_check_hdr.s new file mode 100644 index 000000000..1581ab918 --- /dev/null +++ b/libsrc/atari/system_check_hdr.s @@ -0,0 +1,16 @@ +; +; Atari startup system check headers +; +; Christian Groessler, chris@groessler.org, 2013 +; + .export __SYSCHKHDR__: absolute = 1 + .import __SYSCHK_LOAD__, __SYSCHK_END__ + +; ------------------------------------------------------------------------ +; Chunk header + +.segment "SYSCHKHDR" + + .word __SYSCHK_LOAD__ + .word __SYSCHK_END__ - 1 + diff --git a/libsrc/atari/system_check_trailer.s b/libsrc/atari/system_check_trailer.s new file mode 100644 index 000000000..312b83b1f --- /dev/null +++ b/libsrc/atari/system_check_trailer.s @@ -0,0 +1,17 @@ +; +; Atari startup system check headers +; +; Christian Groessler, chris@groessler.org, 2013 +; + .export __SYSCHKTRL__: absolute = 1 + .import __SYSTEM_CHECK__ + + .include "atari.inc" +; ------------------------------------------------------------------------ +; Chunk "trailer" - sets INITAD + +.segment "SYSCHKTRL" + + .word INITAD + .word INITAD+1 + .word __SYSTEM_CHECK__ diff --git a/src/ld65/config.c b/src/ld65/config.c index fafbed290..f8bff2ac0 100644 --- a/src/ld65/config.c +++ b/src/ld65/config.c @@ -1007,6 +1007,7 @@ static void ParseXex (void) { static const IdentTok Attributes [] = { { "RUNAD", CFGTOK_RUNAD }, + { "INITAD", CFGTOK_INITAD }, }; /* Remember the attributes read */ @@ -1017,6 +1018,8 @@ static void ParseXex (void) }; unsigned AttrFlags = atNone; Import *RunAd = 0; + Import *InitAd; + MemoryArea *InitMem; /* Read the attributes */ while (CfgTok == CFGTOK_IDENT) { @@ -1046,6 +1049,24 @@ static void ParseXex (void) CfgNextTok (); break; + case CFGTOK_INITAD: + /* We expect a memory area followed by a colon and an identifier */ + CfgAssureIdent (); + InitMem = CfgGetMemory (GetStrBufId (&CfgSVal)); + CfgNextTok (); + CfgConsumeColon (); + CfgAssureIdent (); + /* Generate an import for the symbol */ + InitAd = InsertImport (GenImport (GetStrBufId (&CfgSVal), ADDR_SIZE_ABS)); + /* Remember the file position */ + CollAppend (&InitAd->RefLines, GenLineInfo (&CfgErrorPos)); + /* Eat the identifier token */ + CfgNextTok (); + /* Add to XEX */ + if (XexAddInitAd (XexFmtDesc, InitMem, InitAd)) + CfgError (&CfgErrorPos, "INITAD already given for memory area"); + break; + default: FAIL ("Unexpected attribute token"); diff --git a/src/ld65/scanner.h b/src/ld65/scanner.h index 77fa91da8..aeabbdca8 100644 --- a/src/ld65/scanner.h +++ b/src/ld65/scanner.h @@ -94,6 +94,7 @@ typedef enum { CFGTOK_VERSION, CFGTOK_FORMAT, CFGTOK_RUNAD, + CFGTOK_INITAD, CFGTOK_LOAD, CFGTOK_RUN, diff --git a/src/ld65/xex.c b/src/ld65/xex.c index 18190c063..c57fa0a8c 100644 --- a/src/ld65/xex.c +++ b/src/ld65/xex.c @@ -58,6 +58,12 @@ /* Data */ /*****************************************************************************/ +/* Linked list of memory area initialization addresses */ +typedef struct XexInitAd { + MemoryArea *InitMem; + Import *InitAd; + struct XexInitAd *next; +} XexInitAd; struct XexDesc { @@ -65,13 +71,13 @@ struct XexDesc { FILE* F; /* Output file */ const char* Filename; /* Name of output file */ Import* RunAd; /* Run Address */ + XexInitAd* InitAds; /* List of Init Addresses */ unsigned long HeadPos; /* Position in the file of current header */ unsigned long HeadEnd; /* End address of current header */ unsigned long HeadSize; /* Last header size, can be removed if zero */ }; - /*****************************************************************************/ /* Code */ /*****************************************************************************/ @@ -89,6 +95,7 @@ XexDesc* NewXexDesc (void) D->F = 0; D->Filename = 0; D->RunAd = 0; + D->InitAds = 0; D->HeadPos = 0; D->HeadEnd = 0; D->HeadSize = 0; @@ -113,8 +120,35 @@ void XexSetRunAd (XexDesc* D, Import *RunAd) D->RunAd = RunAd; } +XexInitAd* XexSearchInitMem(XexDesc* D, MemoryArea *InitMem) +{ + XexInitAd* I; + for (I=D->InitAds; I != 0; I=I->next) + { + if (I->InitMem == InitMem) + return I; + } + return NULL; +} +int XexAddInitAd (XexDesc* D, MemoryArea *InitMem, Import *InitAd) +/* Sets and INITAD for the given memory area */ +{ + XexInitAd* I; + + /* Search for repeated entry */ + if (XexSearchInitMem (D, InitMem)) + return 1; + + I = xmalloc (sizeof (XexInitAd)); + I->InitAd = InitAd; + I->InitMem = InitMem; + I->next = D->InitAds; + D->InitAds = I; + return 0; +} + static unsigned XexWriteExpr (ExprNode* E, int Signed, unsigned Size, unsigned long Offs attribute ((unused)), void* Data) @@ -199,11 +233,14 @@ static void XexFakeSegment (XexDesc *D, unsigned long Addr) -static void XexWriteMem (XexDesc* D, MemoryArea* M) +static unsigned long XexWriteMem (XexDesc* D, MemoryArea* M) /* Write the segments of one memory area to a file */ { unsigned I; + /* Store initial position to get total file size */ + unsigned long StartPos = ftell (D->F); + /* Always write a segment header for each memory area */ D->HeadPos = 0; @@ -321,6 +358,8 @@ static void XexWriteMem (XexDesc* D, MemoryArea* M) if (D->HeadSize == 0 && D->HeadPos) { fseek (D->F, D->HeadPos, SEEK_SET); } + + return ftell (D->F) - StartPos; } @@ -369,8 +408,14 @@ void XexWriteTarget (XexDesc* D, struct File* F) for (I = 0; I < CollCount (&F->MemoryAreas); ++I) { /* Get this entry */ MemoryArea* M = CollAtUnchecked (&F->MemoryAreas, I); + /* See if we have an init address for this area */ + XexInitAd* I = XexSearchInitMem (D, M); Print (stdout, 1, " ATARI EXE Dumping `%s'\n", GetString (M->Name)); - XexWriteMem (D, M); + if (XexWriteMem (D, M) && I) { + Write16 (D->F, 0x2E2); + Write16 (D->F, 0x2E3); + Write16 (D->F, GetExportVal (I->InitAd->Exp)); + } } /* Write RUNAD at file end */ diff --git a/src/ld65/xex.h b/src/ld65/xex.h index c74f78eca..2eb80de86 100644 --- a/src/ld65/xex.h +++ b/src/ld65/xex.h @@ -69,6 +69,8 @@ void XexWriteTarget (XexDesc* D, File* F); void XexSetRunAd (XexDesc* D, Import *RunAd); /* Set the RUNAD export */ +int XexAddInitAd (XexDesc* D, MemoryArea *InitMem, Import *InitAd); +/* Sets and INITAD for the given memory area */ /* End of xex.h */ diff --git a/testcode/lib/atari/multi-xex.cfg b/testcode/lib/atari/multi-xex.cfg index 18dfff820..7558aa895 100644 --- a/testcode/lib/atari/multi-xex.cfg +++ b/testcode/lib/atari/multi-xex.cfg @@ -3,10 +3,12 @@ FEATURES { } MEMORY { ZP: file = "", define = yes, start = $0082, size = $007E; + # First memory segment in file, show message + LOADER: file = %O, start = $680, size = 128; # First memory segment in file, load over COLOR registers: COLOR: file = %O, start = $2C4, size = 5; # Second memory segment, load at page 6: - PAGE6: file = %O, start = $600, size = 256; + PAGE6: file = %O, start = $600, size = 128; # Third memory segment in file, load over SDLST register: SDLST: file = %O, start = $230, size = 2; # Main segment, load at "STARTADDRESS" @@ -16,11 +18,13 @@ FILES { %O: format = atari; } FORMATS { - atari: runad = start; + atari: runad = start, + initad = LOADER: show_load; } SEGMENTS { ZEROPAGE: load = ZP, type = zp, optional = yes; # Place segments in memory areas: + LOADER: load = LOADER, type = rw; COLOR: load = COLOR, type = rw; PAGE6: load = PAGE6, type = rw; SDLST: load = SDLST, type = rw; diff --git a/testcode/lib/atari/multi-xex.s b/testcode/lib/atari/multi-xex.s index 7957ddf64..cdf43469d 100644 --- a/testcode/lib/atari/multi-xex.s +++ b/testcode/lib/atari/multi-xex.s @@ -14,8 +14,25 @@ .macpack atari ; Default RUNAD is "start", export that: - .export start + .export start, show_load +; Loader + .segment "LOADER" +show_load: + ldx #0 ; channel 0 + lda #msg_load + sta ICBAH,x + lda #$FF + sta ICBLL,x ; length + sta ICBLH,x + lda #PUTREC + sta ICCOM,x + jmp CIOV + +msg_load: + .byte "Loading....", ATEOL ; We load color values directly into registers .segment "COLOR" diff --git a/testcode/lib/atari/multi.xex b/testcode/lib/atari/multi.xex new file mode 100644 index 000000000..7da39ad47 Binary files /dev/null and b/testcode/lib/atari/multi.xex differ