code itself an allows us to fix up the banking semi-automatically. Currently
it's not smart enough to lay out the banks itself.
+The compiler is also modified to add an option --external-banker which
+causes it to do two things
+
+1. Calls to functions are generated with push af/pop af either side
+ of the invocation (except calls to literals)
+
+2. Functions assume there are four bytes of pushed address
+
How it works
binmunge reads the relocs and banks and converts as follows
Code
- 0xCD (CALL) in the byte before a relocation is patched to an RST
- RST8 - bank 1
- RST10 - bank 2
- RST18 - bank 3
+ PUSH AF
+ CALL xx
+ POP AF
+
+ is turned into
+
+ CALL __bank_n_from_m ; for inter-bank calls
+ DEFW xx
0xC3 (JUMP) is turned into a jump into a stub
Anything else is considered an error
Data
- Each entry is turned into a stub. Identical entries are turned into
- the same stub call
+ Each code entry is turned into a stub. Identical entries are turned
+ into the same stub call from the same bank (They are assumed to be
+ function pointers)
Only 16bit relocations are processed. Weird tricks like &function >> 8 may
break. Hopefully SDCC never decides to create one behind our back.
+Stubs:
-Stubs
-
-The scripts scan for a _STUBS section. This is overwritten by the linker
-with 4 bytes per stub needed. Each stub takes the form
-
- RSTxx
- .dw address ; real target address
+ CALL __bank_n_from_m
+ defw xx
RET
-and the address in the data is replaced with the address of the stub.
-
+The stubs may live in the bank of the invoking function, while __bank_x is
+common
This is done to process tables. It means that function tables like device or
syscall tables correctly generate inter bank calls.
-
-
-RST Handlers
-
-SDCC is completely unaware of the banking. While this makes near calls
-faster and avoids all the far stack crap, it means that some stack games are
-needed in the stubs in order to restore the previous bank on return. In
-particular because SDCC always uses stack based argument passing the stack
-needs shuffling.
-
-We have two options, one is to shuffle the stack around (and hide the extra
-info in the stack), the other is to keep a separate bank stack, remembering
-however that we regularly use other stacks and reload sp -> not fun.
-
-
-So we end up needing something like
-
-;
-; On entry A is the target bank number, all other registers are
-; cannon fodder except for IX (although we can trash IX providing
-; we restore it on retv)
-;
-rstbank:
- pop hl ; return address (pointer to real call vector)
- pop bc ; first argument
- exx
- pop hl ; arguments 2-4 (potentially)
- pop de
- pop bc
- exx
- ld e, (hl) ; get the call target
- inc hl
- ld d, (hl)
- inc hl
- push hl ; save the true return address
-;
-; We could take an IRQ after loading a with rombank but the IRQ
-; return path will always put us back in the same bank, so the
-; answer will still be correct at the end of the interrupt handler
-;
- ld hl, rombank ; save the banking setting
- ld h, (hl) ; doing it this way around we can
- push hl ; preserve A
- exx ; push up to 4 arguments back
- push bc
- push de
- push hl
- exx
- push bc
- ld hl, #retv ; push our return address
- push hl
- ex de, hl
-;
-; The ordering here is critical. If we take an IRQ as we bank the
-; irq restore may see the rombank write before we do the out. This way
-; around it will restore the new bank, and our out will be a no-op
-;
- ld (rombank), a
- out (bank), a
- jp (hl) ; invoke the banked C method
-
-retv: pop de ; pull off 4 arguments in DE and HLDEBC'
- exx
- pop hl
- pop de
- pop bc
- exx
- pop af ; pull off the banking info
- ; Ordering here is also vital for IRQ
- ld bc, #0x7ffd
- ld (rombank), a ; unbank
- out (c), a
- pop bc ; recover real return address in BC
- push de ; now push the 4 potential arguments back
- exx
- push bc
- push de
- push hl
- exx
- push bc ; hl must be preserved so return via push bc/ret
- ret
-
- .org 8
-rst8: ld a, #0x18 ; Screen in bank 7, 48K rom present, bank 0
- jp rstbank
-
- .org 0x10
-rst10: ld a, #0x19 ; Screen in bank 7, 48K rom present, bank 1
- jp rstbank
-
- .org 0x18
-rst18: ld a, #23 ; Screen in bank 7, 48K rom present, bank 7
- jp rstbank
-
-
-
-
+Banking Handlers
+
+
+__bank_0_from_1:
+ POP HL ; return
+ LD DE, (HL)
+ INC HL
+ LD D, (HL)
+ INC HL
+ PUSH HL ; true return
+ LD A, #bank0op
+ OUT (port), A
+ EX DE, HL
+ CALL __callhl
+__bank1_ret:
+ LD A, #1
+ OUT (port), A
+ RET
+__callhl:
+ JP (HL)
\ No newline at end of file
/*
* Fix up the given binary block with the reloc data
- *
- * Patch RST lines into the relevant places, generate stub info
*/
#include <stdio.h>
static int lastfix;
static int stub_all;
+struct symbol {
+ struct symbol *next;
+ char *name;
+ unsigned int val;
+};
+
+struct symbol *symbols;
+
struct stubmap {
struct stubmap *next;
- int bank;
+ int sbank;
+ int dbank;
uint16_t addr;
uint16_t target;
};
return size[b];
}
+void add_symbol(char *name, unsigned int val)
+{
+ struct symbol *s = malloc(sizeof(struct symbol) + strlen(name) + 1);
+ if (s == NULL) {
+ fprintf(stderr, "Out of memory.\n");
+ exit(1);
+ }
+ s->name = (char *)(s + 1);
+ s->val = val;
+ strcpy(s->name, name);
+ s->next = symbols;
+ symbols = s;
+}
+
+unsigned int get_bank_function(int sbank, int dbank)
+{
+ struct symbol *s = symbols;
+ char name[32];
+ sprintf(name, "__bank_%d_%d", sbank, dbank);
+ while (s) {
+ if (strcmp(s->name, name) == 0)
+ return s->val;
+ s = s->next;
+ }
+ fprintf(stderr, "Symbol '%s' is missing for banked use.\n", name);
+ exit(1);
+}
+
/* FIXME: */
-int stubmap(uint16_t v, int bank)
+int stubmap(uint16_t v, int sbank, int dbank)
{
struct stubmap *s = stubs;
struct stubmap **p = &stubs;
+ uint16_t da;
while(s) {
- if (s->bank == bank && s->addr == v) {
+ if (s->sbank == sbank && s->addr == v && s->dbank == dbank) {
stubdup++;
return s->target;
}
}
*p = s;
s->next = NULL;
- s->bank = bank;
+ s->sbank = sbank;
+ s->dbank = dbank;
s->addr = v;
s->target = nextfix;
if (nextfix == lastfix) {
fprintf(stderr, "Out of fix space (%d stubs used).\n", stubct);
exit(1);
}
- buf[0][nextfix++] = 0xC7 + 8 * bank; /* RST */
+ /* FIXME: we could have per bank stubs in ROM and not waste precious
+ common memory here */
+ da = get_bank_function(sbank, dbank);
+ /* Call */
+ buf[0][nextfix++] = 0xCD;
+ /* Bank function */
+ buf[0][nextfix++] = da & 0xFF;
+ buf[0][nextfix++] = da >> 8;
+ /* Defw address */
buf[0][nextfix++] = v & 0xFF; /* Target */
buf[0][nextfix++] = v >> 8;
- buf[0][nextfix++] = 0xC9; /* RET */
+ /* Ret */
+ buf[0][nextfix++] = 0xC9;
stubct++;
return s->target;
}
switch(buf[sbank][ptr-1]) {
case 0xC3: /* JP - needs stub */
if (v)
- printf("Converting JP at %04x to RST/RET stub\n", ptr);
- da = stubmap(buf[sbank][ptr] + (buf[sbank][ptr+1] << 8), dbank);
+ printf("Converting JP at %04x to stub\n", ptr);
+ da = stubmap(buf[sbank][ptr] + (buf[sbank][ptr+1] << 8), sbank, dbank);
buf[sbank][ptr] = da & 0xFF;
buf[sbank][ptr+1] = da >> 8;
break;
- case 0xCD: /* CALL */
+ case 0xF5: /* PUSH AF CALL POP AF */
+ if (buf[sbank][ptr] != 0xCD) {
+ fprintf(stderr, "Bad format for relocated long call at%04x\n", ptr);
+ exit(1);
+ }
if (v)
- printf("Converting CALL at %04x to RST\n", ptr);
- buf[sbank][ptr-1] = 0xC7 + 8 * dbank; /* RST 8/16/24 */
+ printf("Converting CALL at %04x\n", ptr);
+ /* Turn the push af into a call */
+ buf[sbank][ptr-1] = 0xCD;
+ /* Move the address along */
+ buf[sbank][ptr+3] = buf[sbank][ptr+2];
+ buf[sbank][ptr+2] = buf[sbank][ptr+1];
+ /* Fit in the actual call target */
+ da = get_bank_function(sbank, dbank);
+ /* Sequence is now CALL __bank_sbank_dbank DW target */
+ buf[sbank][ptr] = da & 0xFF;
+ buf[sbank][ptr+1] = da >> 8;
break;
- case 0xC7: /* RST */
- case 0xCF:
- case 0xD7:
- case 0xDF:
- fprintf(stderr, "File already processed!\n");
+ case 0xCD:
+ fprintf(stderr, "File already processed or short call at %04x!\n", ptr);
exit(1);
default:
fprintf(stderr, "Bad relocation in code %04X: %02X\n",
if (v)
printf("Stubhooking %04x for data reference.\n", ptr);
/* Find the stub for it */
- na = stubmap(n, dbank);
+ na = stubmap(n, sbank, dbank);
if (na == -1) {
fprintf(stderr, "No stub match: stubs stale\n");
exit(1);
data_reloc(b1, addr, b2);
}
+static void scan_symbols(FILE *f)
+{
+ char buf[256];
+ char *sym;
+ unsigned int addr;
+
+ while(fgets(buf, 255, stdin)) {
+ if (memcmp(buf, " 0000", 10))
+ continue;
+ /* Looks like a symbol */
+ if (sscanf(buf+6, "%x", &addr) != 1)
+ continue;
+ /* Smells like a symbol */
+ sym = strtok(buf+16, " \n\t");
+ if (!sym)
+ continue;
+ /* Guess it's a symbol then */
+ add_symbol(sym, addr);
+ }
+}
int main(int argc, char *argv[])
{
int sb, ss;
int opt;
- while ((opt = getopt(argc, "av")) != -1) {
+ while ((opt = getopt(argc, argv, "av")) != -1) {
switch (opt) {
case 'a':
stub_all = 1;
nextfix = sb;
lastfix = sb + (ss & ~3);
+ r = fopen("fuzix.map", "r");
+ if (r == NULL) {
+ perror("fuzix.map");
+ exit(1);
+ }
+ scan_symbols(r);
+ fclose(r);
+
+
r = fopen("relocs.dat", "r");
if (r == NULL) {
perror("relocs.dat");