From a0a513209953915b8fab409d5d89d310fcea5f9a Mon Sep 17 00:00:00 2001 From: Peter Evans Date: Mon, 15 Jan 2018 23:50:33 -0600 Subject: [PATCH] Add pc file for peripheral card ROM --- include/apple2.h | 24 +++++++ include/apple2.pc.h | 14 ++++ sources.cmake | 1 + src/apple2.mem.c | 3 + src/apple2.pc.c | 162 ++++++++++++++++++++++++++++++++++++++++++-- tests/apple2.pc.c | 58 ++++++++++++++++ 6 files changed, 256 insertions(+), 6 deletions(-) create mode 100644 include/apple2.pc.h create mode 100644 tests/apple2.pc.c diff --git a/include/apple2.h b/include/apple2.h index 57159d2..2d5a15d 100644 --- a/include/apple2.h +++ b/include/apple2.h @@ -128,6 +128,30 @@ enum memory_mode { */ MEMORY_PAGE2 = 0x8, MEMORY_HIRES = 0x10, + + /* + * When this is high, expansion ROM is considered in use. That means + * that the $C800..$CFFF range will be mapped to the expansion ROM + * area of the rom segment (which is at the end), vs. the internal + * ROM area, which is at the $0800..$0FFF range within the rom + * segment. + */ + MEMORY_EXPROM = 0x20, + + /* + * When SLOTCXROM is high, the entire range of $C100..$C7FF will be + * mapped to the peripheral ROM area of the rom segment (which is in + * the $4100..$47FF address range there); otherwise, $C100...$C7FF + * is mapped to internal ROM, located at $0100..$07FF within the + * same rom segment. + * + * It's not possible to map a single peripheral ROM page, with the + * exception of slot 3 (via SLOTC3ROM). That page is special because + * of its use by the 80-column text card. You can have SLOTC3ROM + * high but SLOTCXROM low. + */ + MEMORY_SLOTCXROM = 0x40, + MEMORY_SLOTC3ROM = 0x80, }; enum bank_switch { diff --git a/include/apple2.pc.h b/include/apple2.pc.h new file mode 100644 index 0000000..d4f8bed --- /dev/null +++ b/include/apple2.pc.h @@ -0,0 +1,14 @@ +#ifndef _APPLE2_PC_H_ +#define _APPLE2_PC_H_ + +#include "apple2.h" +#include "vm_segment.h" + +extern SEGMENT_READER(apple2_pc_read); +extern SEGMENT_READER(apple2_pc_switch_read); +extern SEGMENT_WRITER(apple2_pc_switch_write); +extern SEGMENT_WRITER(apple2_pc_write); +extern size_t apple2_pc_rom_addr(size_t, vm_8bit); +extern void apple2_pc_map(vm_segment *); + +#endif diff --git a/sources.cmake b/sources.cmake index 4515b78..e28164e 100644 --- a/sources.cmake +++ b/sources.cmake @@ -5,6 +5,7 @@ set(erc_sources apple2.dd.c apple2.draw.c apple2.mem.c + apple2.pc.c log.c mos6502.c mos6502.addr.c diff --git a/src/apple2.mem.c b/src/apple2.mem.c index 17053bb..542d7c0 100644 --- a/src/apple2.mem.c +++ b/src/apple2.mem.c @@ -4,6 +4,7 @@ #include "apple2.bank.h" #include "apple2.dbuf.h" +#include "apple2.pc.h" #include "apple2.h" #include "apple2.mem.h" #include "objstore.h" @@ -25,6 +26,8 @@ apple2_mem_map(apple2 *mach, vm_segment *segment) // Here we handle the 80STORE bit for our display buffers. apple2_dbuf_map(segment); + apple2_pc_map(segment); + // We will do the mapping for the zero page and stack addresses. // Accessing those addresses can be affected by bank-switching, but // those addresses do not actually exist in the capital diff --git a/src/apple2.pc.c b/src/apple2.pc.c index ebae9dc..4c8114e 100644 --- a/src/apple2.pc.c +++ b/src/apple2.pc.c @@ -7,8 +7,19 @@ * (apple2.dd.c). */ -#include "apple2.h" -#include "vm_segment.h" +#include "apple2.pc.h" + +static size_t switch_reads[] = { + 0xC015, + 0xC017, +}; + +static size_t switch_writes[] = { + 0xC006, + 0xC007, + 0xC00A, + 0xC00B, +}; /* * Map all of the peripheral ROM space (and peripheral expansion ROM @@ -17,24 +28,163 @@ * $C800..$CFFF. */ void -apple2_pc_map(apple2 *mach, vm_segment *seg) +apple2_pc_map(vm_segment *seg) { size_t addr; + int i; + int rlen, wlen; for (addr = 0xC100; addr < 0xD000; addr++) { vm_segment_read_map(seg, addr, apple2_pc_read); vm_segment_write_map(seg, addr, apple2_pc_write); } + + rlen = sizeof(switch_reads) / sizeof(size_t); + wlen = sizeof(switch_writes) / sizeof(size_t); + + for (i = 0; i < rlen; i++) { + vm_segment_read_map(seg, switch_reads[i], apple2_pc_switch_read); + } + + for (i = 0; i < wlen; i++) { + vm_segment_write_map(seg, switch_writes[i], apple2_pc_switch_write); + } } +/* + * This mapper handles the entire potential peripheral ROM space; we + * either need to return an address from the beginning of memory, or + * near the end. + */ SEGMENT_READER(apple2_pc_read) { apple2 *mach = (apple2 *)_mach; - if (mach->memory_mode & MEMORY_EXPROM) { + // The address in the rom segment gets translated through a number + // of factors + addr = apple2_pc_rom_addr(addr, mach->memory_mode); + + // No matter what we do, the segment we return from will always be + // the rom segment. This part is non-negotiable. + return mach->rom->memory[addr]; +} + +/* + * Since all of our address spaces are mapped to ROM, we can't write + * anything! + */ +SEGMENT_WRITER(apple2_pc_write) +{ + return; +} + + +/* + * Given an address from program code, return the corresponding address + * in our rom segment. The machine's memory mode is also given as a + * factor. + */ +size_t +apple2_pc_rom_addr(size_t addr, vm_8bit mode) +{ + size_t rom_addr; + + // This mapper should only be called from the $C000..$CFFF range; + // in the rom segment, this group of pages is addressed at + // $0000..$0FFF. We can assume that regardless of what addr is, a + // subtraction of $C000 would result in a valid address. + rom_addr = addr - 0xC000; + + // However, if the EXPROM bit is high, then we want to use expansion + // ROM which is located at the _end_ of the rom segment; specifically + // in the $4800..$4FFF range. + if (rom_addr >= 0x0800 && rom_addr < 0x1000 && + mode & MEMORY_EXPROM + ) { + rom_addr += 0x4000; } - if (mach->memory_mode & MEMORY_SLOTCXROM) { - segment = mach->rom; + // If SLOTCXROM is high, then we want any byte addressed in this + // space to reference peripheral ROM (which is again located at the + // end of the segment, in exactly the same way that expansion ROM is + // placed). + if (rom_addr >= 0x0100 && rom_addr < 0x0800 && + mode & MEMORY_SLOTCXROM + ) { + rom_addr += 0x4000; + } + + // One final thing to account for is if the SLOTC3ROM bit is high. + // If it is, and if the address has not already been incremented + // because SLOTCXROM was _also_ high, then we need to increment just + // for this specific page of memory. + if (rom_addr >= 0x0300 && rom_addr < 0x0400 && + mode & MEMORY_SLOTC3ROM + ) { + rom_addr += 0x4000; + } + + return rom_addr; +} + +/* + * Handle reads to the slot / peripheral ROM switches. I'm not + * _entirely_ sure what Apple expects to be returned here, since they + * don't indicate as clearly as they did with bank-switch modes; there, + * they said you need to return a byte with the 7 bit high, which you + * can then use a BPL or a BMI to branch with. Here, they don't specify + * bit 7, but I'm going to go with that until I see otherwise. + */ +SEGMENT_READER(apple2_pc_switch_read) +{ + apple2 *mach = (apple2 *)_mach; + + switch (addr) { + case 0xC015: + return (mach->memory_mode & MEMORY_SLOTCXROM) + ? 0x80 + : 0x00; + case 0xC017: + return (mach->memory_mode & MEMORY_SLOTC3ROM) + ? 0x80 + : 0x00; + } + + // We shouldn't get here for any practical reason, but... + return 0; +} + +/* + * Handle writes to the slot switches. There are separate writes to turn + * these switches on and off, for each switch, respectively. The value + * being written doesn't matter (indeed--you won't see us reference the + * value parameter). + */ +SEGMENT_WRITER(apple2_pc_switch_write) +{ + apple2 *mach = (apple2 *)_mach; + + // We don't care what the value is; if a write happens to any of the + // following addresses, we must change the peripheral memory + // behavior accordingly. + switch (addr) { + case 0xC00B: + apple2_set_memory_mode(mach, + mach->memory_mode | MEMORY_SLOTC3ROM); + break; + case 0xC00A: + apple2_set_memory_mode(mach, + mach->memory_mode & ~MEMORY_SLOTC3ROM); + break; + + case 0xC006: + apple2_set_memory_mode(mach, + mach->memory_mode | MEMORY_SLOTCXROM); + break; + + case 0xC007: + apple2_set_memory_mode(mach, + mach->memory_mode & ~MEMORY_SLOTCXROM); + break; } } diff --git a/tests/apple2.pc.c b/tests/apple2.pc.c new file mode 100644 index 0000000..f218979 --- /dev/null +++ b/tests/apple2.pc.c @@ -0,0 +1,58 @@ +#include + +#include "apple2.h" +#include "apple2.pc.h" + +static apple2 *mach = NULL; + +static void +setup() +{ + mach = apple2_create(100, 100); + vm_segment_set_map_machine(mach); +} + +static void +teardown() +{ + apple2_free(mach); + vm_segment_set_map_machine(NULL); +} + +TestSuite(apple2_pc, .init = setup, .fini = teardown); + +Test(apple2_pc, map) +{ + size_t addr; + int i; + vm_segment *segments[2]; + + segments[0] = mach->main; + segments[1] = mach->aux; + + for (i = 0; i < 2; i++) { + for (addr = 0xC100; addr < 0xD000; addr++) { + cr_assert_eq(segments[i]->read_table[addr], apple2_pc_read); + cr_assert_eq(segments[i]->write_table[addr], apple2_pc_write); + } + + cr_assert_eq(segments[i]->read_table[0xC015], apple2_pc_switch_read); + cr_assert_eq(segments[i]->read_table[0xC017], apple2_pc_switch_read); + cr_assert_eq(segments[i]->write_table[0xC00B], apple2_pc_switch_write); + cr_assert_eq(segments[i]->write_table[0xC00A], apple2_pc_switch_write); + cr_assert_eq(segments[i]->write_table[0xC006], apple2_pc_switch_write); + cr_assert_eq(segments[i]->write_table[0xC007], apple2_pc_switch_write); + } +} + +Test(apple2_pc, rom_addr) +{ + cr_assert_eq(apple2_pc_rom_addr(0xC832, MEMORY_DEFAULT), 0x832); + cr_assert_eq(apple2_pc_rom_addr(0xC832, MEMORY_SLOTCXROM), 0x832); + cr_assert_eq(apple2_pc_rom_addr(0xC832, MEMORY_SLOTC3ROM), 0x832); + cr_assert_eq(apple2_pc_rom_addr(0xC232, MEMORY_EXPROM), 0x0232); + + cr_assert_eq(apple2_pc_rom_addr(0xC832, MEMORY_EXPROM), 0x4832); + cr_assert_eq(apple2_pc_rom_addr(0xC732, MEMORY_SLOTCXROM), 0x4732); + cr_assert_eq(apple2_pc_rom_addr(0xC332, MEMORY_SLOTC3ROM), 0x4332); +}