From a3c7dd136bf22e4f6517046849d7f1d1eb2311a6 Mon Sep 17 00:00:00 2001 From: Will Scullin Date: Sat, 13 Mar 2021 13:18:32 -0800 Subject: [PATCH] Port language card and smartport to TypeScript (#62) Port language card and smartport to TypeScript. --- js/cards/disk2.ts | 10 +- js/cards/langcard.js | 210 ----------------- js/cards/langcard.ts | 194 ++++++++++++++++ js/cards/smartport.js | 445 ----------------------------------- js/cards/smartport.ts | 464 +++++++++++++++++++++++++++++++++++++ js/formats/format_utils.ts | 62 +++-- js/main2.js | 4 +- js/main2e.js | 2 +- js/mmu.ts | 94 ++++---- js/types.ts | 2 +- 10 files changed, 754 insertions(+), 733 deletions(-) delete mode 100644 js/cards/langcard.js create mode 100644 js/cards/langcard.ts delete mode 100644 js/cards/smartport.js create mode 100644 js/cards/smartport.ts diff --git a/js/cards/disk2.ts b/js/cards/disk2.ts index 16e269b..7c4f466 100644 --- a/js/cards/disk2.ts +++ b/js/cards/disk2.ts @@ -12,7 +12,7 @@ import { base64_decode, base64_encode} from '../base64'; import { bit, byte, Card, DiskFormat, MemberOf, memory, nibble, rom } from '../types'; import { debug, toHex } from '../util'; -import { Disk, jsonDecode, jsonEncode, readSector } from '../formats/format_utils'; +import { Disk, JSONDisk, jsonDecode, jsonEncode, readSector } from '../formats/format_utils'; import { BOOTSTRAP_ROM_16, BOOTSTRAP_ROM_13 } from '../roms/cards/disk2'; @@ -337,7 +337,7 @@ export default class DiskII implements Card { this.init(); } - private debug(..._args: any) { + private debug(..._args: any[]) { // debug.apply(this, arguments); } @@ -728,8 +728,8 @@ export default class DiskII implements Card { /** Sets the data for `drive` from `disk`, which is expected to be JSON. */ // TODO(flan): This implementation is not very safe. - setDisk(drive: DriveNumber, disk: any) { - const fmt = disk.type as DiskFormat; + setDisk(drive: DriveNumber, disk: JSONDisk) { + const fmt = disk.type; const readOnly = disk.readOnly; const name = disk.name; @@ -795,7 +795,7 @@ export default class DiskII implements Card { return jsonEncode(cur, pretty); } - setJSON(drive: DriveNumber, data: any) { + setJSON(drive: DriveNumber, data: string) { const cur = this.drives[drive - 1]; Object.assign(cur, jsonDecode(data)); return true; diff --git a/js/cards/langcard.js b/js/cards/langcard.js deleted file mode 100644 index 5962df5..0000000 --- a/js/cards/langcard.js +++ /dev/null @@ -1,210 +0,0 @@ -/* Copyright 2010-2019 Will Scullin - * - * Permission to use, copy, modify, distribute, and sell this software and its - * documentation for any purpose is hereby granted without fee, provided that - * the above copyright notice appear in all copies and that both that - * copyright notice and this permission notice appear in supporting - * documentation. No representations are made about the suitability of this - * software for any purpose. It is provided "as is" without express or - * implied warranty. - */ - -import RAM from '../ram'; -import { debug } from '../util'; - -export default function LanguageCard(io, rom) { - var _rom = rom; - var _bank1 = null; - var _bank2 = null; - var _ram = null; - - var _readbsr = false; - var _writebsr = false; - var _bsr2 = false; - var _prewrite = false; - - var _read1 = null; - var _read2 = null; - - var _write1 = null; - var _write2 = null; - - function _init() { - debug('Language card'); - - _bank1 = new RAM(0xd0, 0xdf); - _bank2 = new RAM(0xd0, 0xdf); - _ram = new RAM(0xe0, 0xff); - - _write1 = _rom; - _write2 = _rom; - - _read1 = _rom; - _read2 = _rom; - } - - function _debug() { - // debug.apply(null, arguments); - } - - _init(); - - var LOC = { - // Bank 2 - READBSR2: 0x80, - WRITEBSR2: 0x81, - OFFBSR2: 0x82, - READWRBSR2: 0x83, - - // Bank 1 - READBSR1: 0x88, - WRITEBSR1: 0x89, - OFFBSR1: 0x8a, - READWRBSR1: 0x8b, - }; - - function _updateBanks() { - if (_readbsr) { - _read1 = _bsr2 ? _bank2 : _bank1; - _read2 = _ram; - } else { - _read1 = _rom; - _read2 = _rom; - } - - if (_writebsr) { - _write1 = _bsr2 ? _bank2 : _bank1; - _write2 = _ram; - } else { - _write1 = rom; - _write2 = rom; - } - } - - function _access(off, val) { - var readMode = val === undefined; - var result = 0; - switch (off & 0x8B) { - case LOC.READBSR2: // 0xC080 - _readbsr = true; - _writebsr = false; - _bsr2 = true; - _prewrite = false; - _debug('Bank 2 Read'); - break; - case LOC.WRITEBSR2: // 0xC081 - _readbsr = false; - if (readMode) { - _writebsr = _prewrite; - } - _bsr2 = true; - _prewrite = readMode; - _debug('Bank 2 Write'); - break; - case LOC.OFFBSR2: // 0xC082 - _readbsr = false; - _writebsr = false; - _bsr2 = true; - _prewrite = false; - _debug('Bank 2 Off'); - break; - case LOC.READWRBSR2: // 0xC083 - _readbsr = true; - if (readMode) { - _writebsr = _prewrite; - } - _bsr2 = true; - _prewrite = readMode; - _debug('Bank 2 Read/Write'); - break; - - case LOC.READBSR1: // 0xC088 - _readbsr = true; - _writebsr = false; - _bsr2 = false; - _prewrite = false; - _debug('Bank 1 Read'); - break; - case LOC.WRITEBSR1: // 0xC089 - _readbsr = false; - if (readMode) { - _writebsr = _prewrite; - } - _bsr2 = false; - _prewrite = readMode; - _debug('Bank 1 Write'); - break; - case LOC.OFFBSR1: // 0xC08A - _readbsr = false; - _writebsr = false; - _bsr2 = false; - _prewrite = false; - _debug('Bank 1 Off'); - break; - case LOC.READWRBSR1: // 0xC08B - _readbsr = true; - if (readMode) { - _writebsr = _prewrite; - } - _bsr2 = false; - _prewrite = readMode; - _debug('Bank 1 Read/Write'); - break; - default: - break; - } - - _updateBanks(); - - return result; - } - - return { - start: function() { - return 0xd0; - }, - end: function() { - return 0xff; - }, - ioSwitch: function(off, val) { - return _access(off, val); - }, - read: function(page, off) { - var result = 0; - if (page < 0xe0) { - result = _read1.read(page, off); - } else { - result = _read2.read(page, off); - } - return result; - }, - write: function(page, off, val) { - if (page < 0xe0) { - _write1.write(page, off, val); - } else { - _write2.write(page, off, val); - } - }, - getState: function() { - return { - readbsr: _readbsr, - writebsr: _writebsr, - bsr2: _bsr2, - prewrite: _prewrite, - ram: _ram.getState(), - bank1: _bank1.getState(), - bank2: _bank2.getState() - }; - }, - setState: function(state) { - _readbsr = state.readbsr; - _writebsr = state.writebsr; - _bsr2 = state.bsr2; - _prewrite = state.prewrite; - _ram.setState(state.ram); - _bank1.setState(state.bank1); - _bank2.setState(state.bank2); - _updateBanks(); - } - }; -} diff --git a/js/cards/langcard.ts b/js/cards/langcard.ts new file mode 100644 index 0000000..52ee79b --- /dev/null +++ b/js/cards/langcard.ts @@ -0,0 +1,194 @@ +/* Copyright 2010-2019 Will Scullin + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + */ + +import RAM, { RAMState } from '../ram'; +import { debug } from '../util'; +import { Card, Memory, byte, Restorable } from '../types'; + +export interface LanguageCardState { + bank1: RAMState; + bank2: RAMState; + ram: RAMState; + + readbsr: boolean; + writebsr: boolean; + bsr2: boolean; + prewrite: boolean; +} + +export default class LanguageCard implements Card, Restorable { + private bank1: RAM; + private bank2: RAM; + private ram: RAM; + + private readbsr = false; + private writebsr = false; + private bsr2 = false; + private prewrite = false; + + private read1: Memory; + private read2: Memory; + + private write1: Memory; + private write2: Memory; + + constructor(private rom: Memory) { + debug('Language card'); + + this.bank1 = new RAM(0xd0, 0xdf); + this.bank2 = new RAM(0xd0, 0xdf); + this.ram = new RAM(0xe0, 0xff); + + this.write1 = this.rom; + this.write2 = this.rom; + + this.read1 = this.rom; + this.read2 = this.rom; + } + + private debug(...args: any[]) { + debug.apply(null, args); + } + + private updateBanks() { + if (this.readbsr) { + this.read1 = this.bsr2 ? this.bank2 : this.bank1; + this.read2 = this.ram; + } else { + this.read1 = this.rom; + this.read2 = this.rom; + } + + if (this.writebsr) { + this.write1 = this.bsr2 ? this.bank2 : this.bank1; + this.write2 = this.ram; + } else { + this.write1 = this.rom; + this.write2 = this.rom; + } + } + + // Bank 2 + // READBSR2: 0x80 + // WRITEBSR2: 0x81 + // OFFBSR2: 0x82 + // READWRBSR2: 0x83 + + // Bank 1 + // READBSR1: 0x88 + // WRITEBSR1: 0x89 + // OFFBSR1: 0x8a + // READWRBSR1: 0x8b + + private access(off: byte, val?: byte) { + const readMode = val === undefined; + const result = readMode ? 0 : undefined; + + const writeSwitch = off & 0x01; + const offSwitch = off & 0x02; + const bank1Switch = off & 0x08; + + let bankStr; + let rwStr; + + if (writeSwitch) { // $C081, $C083, $C089, $C08B + if (readMode) { + this.writebsr = this.prewrite; + } + this.prewrite = readMode; + + if (offSwitch) { // $C083, $C08B + this.readbsr = true; + rwStr = 'Read/Write'; + } else { // $C081, $C089 + this.readbsr = false; + rwStr = 'Write'; + } + } else { // $C080, $C082, $C088, $C08A + this.writebsr = false; + this.prewrite = false; + + if (offSwitch) { // $C082, $C08A + this.readbsr = false; + rwStr = 'Off'; + } else { // $C080, $C088 + this.readbsr = true; + rwStr = 'Read'; + } + } + + if (bank1Switch) { // C08[8-C] + this.bsr2 = false; + bankStr = 'Bank 1'; + } else { // C08[0-3] + this.bsr2 = true; + bankStr = 'Bank 2'; + } + + this.debug(bankStr, rwStr); + this.updateBanks(); + + return result; + } + + start() { + return 0xd0; + } + + end() { + return 0xff; + } + + ioSwitch(off: byte, val?: byte) { + return this.access(off, val); + } + + read(page: byte, off: byte): byte { + let result: number = 0; + if (page < 0xe0) { + result = this.read1.read(page, off); + } else { + result = this.read2.read(page, off); + } + return result; + } + + write(page: byte, off: byte, val: byte) { + if (page < 0xe0) { + this.write1.write(page, off, val); + } else { + this.write2.write(page, off, val); + } + } + + getState() { + return { + readbsr: this.readbsr, + writebsr: this.writebsr, + bsr2: this.bsr2, + prewrite: this.prewrite, + ram: this.ram.getState(), + bank1: this.bank1.getState(), + bank2: this.bank2.getState() + }; + } + + setState(state: LanguageCardState) { + this.readbsr = state.readbsr; + this.writebsr = state.writebsr; + this.bsr2 = state.bsr2; + this.prewrite = state.prewrite; + this.ram.setState(state.ram); + this.bank1.setState(state.bank1); + this.bank2.setState(state.bank2); + this.updateBanks(); + } +} diff --git a/js/cards/smartport.js b/js/cards/smartport.js deleted file mode 100644 index 1095cea..0000000 --- a/js/cards/smartport.js +++ /dev/null @@ -1,445 +0,0 @@ -/* Copyright 2010-2019 Will Scullin - * - * Permission to use, copy, modify, distribute, and sell this software and its - * documentation for any purpose is hereby granted without fee, provided that - * the above copyright notice appear in all copies and that both that - * copyright notice and this permission notice appear in supporting - * documentation. No representations are made about the suitability of this - * software for any purpose. It is provided "as is" without express or - * implied warranty. - */ - -import { debug, toHex } from '../util'; -import { rom } from '../roms/cards/smartport'; - -export default function SmartPort(io, cpu, options ) { - var COMMAND = 0x42; - var UNIT = 0x43; - var ADDRESS_LO = 0x44; - // var ADDRESS_HI = 0x45; - var BLOCK_LO = 0x46; - // var BLOCK_HI = 0x47; - - var disks = []; - - function _init() { - if (options && options.block) { - rom[0x07] = 0x3C; - debug('DumbPort card'); - } else { - debug('SmartPort card'); - } - } - - function decodeDisk(unit, disk) { - disks[unit] = []; - for (var idx = 0; idx < disk.blocks.length; idx++) { - disks[unit][idx] = new Uint8Array(disk.blocks[idx]); - } - } - - function _debug() { - // debug.apply(this, arguments); - } - - function Address() { - var lo; - var hi; - - if (arguments.length == 1) { - lo = arguments[0] & 0xff; - hi = arguments[0] >> 8; - } else if (arguments.length == 2) { - lo = arguments[0]; - hi = arguments[1]; - } - - return { - loByte: function() { - return lo; - }, - - hiByte: function() { - return hi; - }, - - inc: function(val) { - return new Address(((hi << 8 | lo) + val) & 0xffff); - }, - - readByte: function() { - return cpu.read(hi, lo); - }, - - readWord: function() { - var readLo = this.readByte(); - var readHi = this.inc(1).readByte(); - - return readHi << 8 | readLo; - }, - - readAddress: function() { - var readLo = this.readByte(); - var readHi = this.inc(1).readByte(); - - return new Address(readLo, readHi); - }, - - writeByte: function(val) { - cpu.write(hi, lo, val); - }, - - writeWord: function(val) { - this.writeByte(val & 0xff); - this.inc(1).writeByte(val >> 8); - }, - - writeAddress: function(val) { - this.writeByte(val.loByte()); - this.inc(1).writeByte(val.hiByte()); - }, - - toString: function() { - return '$' + toHex(hi) + toHex(lo); - } - }; - } - - /* - * dumpBlock - */ - /* - function dumpBlock(drive, block) { - var result = ''; - var b; - var jdx; - - for (var idx = 0; idx < 32; idx++) { - result += toHex(idx << 4, 4) + ': '; - for (jdx = 0; jdx < 16; jdx++) { - b = disks[drive][block][idx * 16 + jdx]; - if (jdx == 8) { - result += ' '; - } - result += toHex(b) + ' '; - } - result += ' '; - for (jdx = 0; jdx < 16; jdx++) { - b = disks[drive][block][idx * 16 + jdx] & 0x7f; - if (jdx == 8) { - result += ' '; - } - if (b >= 0x20 && b < 0x7f) { - result += String.fromCharCode(b); - } else { - result += '.'; - } - } - result += '\n'; - } - return result; - } -*/ - /* - * getDeviceInfo - */ - - function getDeviceInfo(state, drive) { - if (disks[drive]) { - var blocks = disks[drive].length; - state.x = blocks & 0xff; - state.y = blocks >> 8; - - state.a = 0; - state.s &= 0xfe; - } else { - state.a = 0x28; - state.s |= 0x01; - } - } - - /* - * readBlock - */ - - function readBlock(state, drive, block, buffer) { - _debug('read drive=' + drive); - _debug('read buffer=' + buffer); - _debug('read block=$' + toHex(block)); - - if (!disks[drive] || !disks[drive].length) { - debug('Drive', drive, 'is empty'); - return; - } - - // debug('read', '\n' + dumpBlock(drive, block)); - - for (var idx = 0; idx < 512; idx++) { - buffer.writeByte(disks[drive][block][idx]); - buffer = buffer.inc(1); - } - - state.a = 0; - state.s &= 0xfe; - } - - /* - * writeBlock - */ - - function writeBlock(state, drive, block, buffer) { - _debug('write drive=' + drive); - _debug('write buffer=' + buffer); - _debug('write block=$' + toHex(block)); - - if (!disks[drive] || !disks[drive].length) { - debug('Drive', drive, 'is empty'); - return; - } - - // debug('write', '\n' + dumpBlock(drive, block)); - - for (var idx = 0; idx < 512; idx++) { - disks[drive][block][idx] = buffer.readByte(); - buffer = buffer.inc(1); - } - state.a = 0; - state.s &= 0xfe; - } - - /* - * formatDevice - */ - - function formatDevice(state, drive) { - for (var idx = 0; idx < disks[drive].length; idx++) { - disks[drive][idx] = []; - for (var jdx = 0; jdx < 512; jdx++) { - disks[drive][idx][jdx] = 0; - } - } - - state.a = 0; - state.s &= 0xfe; - } - - function _access(off, val) { - var result; - var readMode = val === undefined; - - switch (off & 0x8f) { - case 0x80: - if (readMode) { - result = 0; - for (var idx = 0; idx < disks.length; idx++) { - result <<= 1; - if (disks[idx]) { - result |= 0x01; - } - } - } - break; - } - - return result; - } - - _init(); - - /* - * Interface - */ - - return { - ioSwitch: function (off, val) { - return _access(off, val); - }, - - read: function(page, off) { - var state = cpu.getState(); - var cmd; - var unit; - var buffer; - var block; - var blockOff = rom[0xff]; - var smartOff = blockOff + 3; - - if (off === blockOff && cpu.getSync()) { // Regular block device entry POINT - _debug('block device entry'); - cmd = cpu.read(0x00, COMMAND); - unit = cpu.read(0x00, UNIT); - var bufferAddr = new Address(ADDRESS_LO); - var blockAddr = new Address(BLOCK_LO); - var drive = (unit & 0x80) ? 2 : 1; - var driveSlot = (unit & 0x70) >> 4; - - buffer = bufferAddr.readAddress(); - block = blockAddr.readWord(); - - _debug('cmd=' + cmd); - _debug('unit=$' + toHex(unit)); - - _debug('slot=' + driveSlot + ' drive=' + drive); - _debug('buffer=' + toHex(buffer) + ' block=' + toHex(block)); - - switch (cmd) { - case 0: // INFO - getDeviceInfo(state, drive); - break; - - case 1: // READ - readBlock(state, drive, block, buffer); - break; - - case 2: // WRITE - writeBlock(state, drive, block, buffer); - break; - - case 3: // FORMAT - formatDevice(state, unit); - break; - } - } else if (off == smartOff && cpu.getSync()) { - _debug('smartport entry'); - var retVal = {}; - var stackAddr = new Address(state.sp + 1, 0x01); - - retVal = stackAddr.readAddress(); - - _debug('return=' + retVal); - - var cmdBlockAddr = retVal.inc(1); - cmd = cmdBlockAddr.readByte(); - var cmdListAddr = cmdBlockAddr.inc(1).readAddress(); - - _debug('cmd=' + cmd); - _debug('cmdListAddr=' + cmdListAddr); - - stackAddr.writeAddress(retVal.inc(3)); - - var parameterCount = cmdListAddr.readByte(); - unit = cmdListAddr.inc(1).readByte(); - buffer = cmdListAddr.inc(2).readAddress(); - var status; - - _debug('parameterCount=' + parameterCount); - switch (cmd) { - case 0x00: // INFO - status = cmdListAddr.inc(4).readByte(); - _debug('info unit=' + unit); - _debug('info buffer=' + buffer); - _debug('info status=' + status); - switch (unit) { - case 0: - switch (status) { - case 0: - buffer.writeByte(1); // one device - buffer.inc(1).writeByte(1 << 6); // no interrupts - buffer.inc(2).writeByte(0); // reserved - buffer.inc(3).writeByte(0); // reserved - buffer.inc(4).writeByte(0); // reserved - buffer.inc(5).writeByte(0); // reserved - buffer.inc(6).writeByte(0); // reserved - buffer.inc(7).writeByte(0); // reserved - state.x = 8; - state.y = 0; - state.a = 0; - state.s &= 0xfe; - break; - } - break; - default: // Unit 1 - switch (status) { - case 0: - var blocks = disks[unit].length; - buffer.writeByte(0xf0); // W/R Block device in drive - buffer.inc(1).writeByte(blocks & 0xff); // 1600 blocks - buffer.inc(2).writeByte((blocks & 0xff00) >> 8); - buffer.inc(3).writeByte((blocks & 0xff0000) >> 16); - state.x = 4; - state.y = 0; - state.a = 0; - state.s &= 0xfe; - break; - } - break; - } - state.a = 0; - state.s &= 0xfe; - break; - - case 0x01: // READ BLOCK - block = cmdListAddr.inc(4).readWord(); - readBlock(state, unit, block, buffer); - break; - - case 0x02: // WRITE BLOCK - block = cmdListAddr.inc(4).readWord(); - writeBlock(state, unit, block, buffer); - break; - - case 0x03: // FORMAT - formatDevice(state, unit); - break; - - case 0x04: // CONTROL - break; - - case 0x05: // INIT - break; - - case 0x06: // OPEN - break; - - case 0x07: // CLOSE - break; - - case 0x08: // READ - break; - - case 0x09: // WRITE - break; - } - } - - cpu.setState(state); - - return rom[off]; - }, - - write: function() { - }, - - getState: function() { - return { - disks: disks.map( - (disk) => disk.map( - (block) => new Uint8Array(block) - ) - ) - }; - }, - - setState: function(state) { - disks = state.disks.map( - (disk) => disk.map( - (block) => new Uint8Array(block) - ) - ); - }, - - setBinary: function (drive, name, fmt, data) { - disks[drive] = []; - if (fmt == '2mg') { - data = data.slice(64); - } - for (var idx = 0; idx < data.byteLength; idx += 512) { - disks[drive].push(new Uint8Array(data.slice(idx, idx + 512))); - } - }, - - setDisk: function(drive, json) { - decodeDisk(drive, json); - } - }; -} diff --git a/js/cards/smartport.ts b/js/cards/smartport.ts new file mode 100644 index 0000000..a90d9d5 --- /dev/null +++ b/js/cards/smartport.ts @@ -0,0 +1,464 @@ +/* Copyright 2010-2019 Will Scullin + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + */ + +import { debug, toHex } from '../util'; +import { rom as smartPortRom } from '../roms/cards/smartport'; +import { Card, Restorable, byte, word, rom } from '../types'; +import CPU6502, { CpuState, flags } from '../cpu6502'; + +type SmartDisk = Uint8Array[]; + +interface BlockDevice { + blocks: Uint8Array +} + +export interface SmartPortState { + disks: SmartDisk[] +} + +export interface SmartPortOptions { + block: boolean; +} + +class Address { + lo: byte; + hi: byte; + + constructor(private cpu: CPU6502, a: byte | word, b?: byte) { + if (b === undefined) { + this.lo = a & 0xff; + this.hi = a >> 8; + } else { + this.lo = a; + this.hi = b; + } + } + + loByte() { + return this.lo; + } + + hiByte() { + return this.hi; + } + + inc(val: byte) { + return new Address(this.cpu, ((this.hi << 8 | this.lo) + val) & 0xffff); + } + + readByte() { + return this.cpu.read(this.hi, this.lo); + } + + readWord() { + const readLo = this.readByte(); + const readHi = this.inc(1).readByte(); + + return readHi << 8 | readLo; + } + + readAddress() { + const readLo = this.readByte(); + const readHi = this.inc(1).readByte(); + + return new Address(this.cpu, readLo, readHi); + } + + writeByte(val: byte) { + this.cpu.write(this.hi, this.lo, val); + } + + writeWord(val: word) { + this.writeByte(val & 0xff); + this.inc(1).writeByte(val >> 8); + } + + writeAddress(val: Address) { + this.writeByte(val.loByte()); + this.inc(1).writeByte(val.hiByte()); + } + + toString() { + return '$' + toHex(this.hi) + toHex(this.lo); + } +} + +// ProDOS zero page locations + +const COMMAND = 0x42; +const UNIT = 0x43; +const ADDRESS_LO = 0x44; +// const ADDRESS_HI = 0x45; +const BLOCK_LO = 0x46; +// const BLOCK_HI = 0x47; + +export default class SmartPort implements Card, Restorable { + + private rom: rom; + private disks: SmartDisk[] = []; + + constructor(private cpu: CPU6502, options: SmartPortOptions) { + if (options?.block) { + const dumbPortRom = new Uint8Array(smartPortRom); + dumbPortRom[0x07] = 0x3C; + this.rom = dumbPortRom; + debug('DumbPort card'); + } else { + debug('SmartPort card'); + } + this.rom = smartPortRom; + } + + private decodeDisk(unit: number, disk: BlockDevice) { + this.disks[unit] = []; + for (let idx = 0; idx < disk.blocks.length; idx++) { + this.disks[unit][idx] = new Uint8Array(disk.blocks[idx]); + } + } + + private debug(..._args: any[]) { + // debug.apply(this, arguments); + } + + /* + * dumpBlock + */ + /* + dumpBlock(drive, block) { + const result = ''; + const b; + const jdx; + + for (const idx = 0; idx < 32; idx++) { + result += toHex(idx << 4, 4) + ': '; + for (jdx = 0; jdx < 16; jdx++) { + b = disks[drive][block][idx * 16 + jdx]; + if (jdx == 8) { + result += ' '; + } + result += toHex(b) + ' '; + } + result += ' '; + for (jdx = 0; jdx < 16; jdx++) { + b = disks[drive][block][idx * 16 + jdx] & 0x7f; + if (jdx == 8) { + result += ' '; + } + if (b >= 0x20 && b < 0x7f) { + result += String.fromCharCode(b); + } else { + result += '.'; + } + } + result += '\n'; + } + return result; + } +*/ + /* + * getDeviceInfo + */ + + getDeviceInfo(state: CpuState, drive: number) { + if (this.disks[drive]) { + const blocks = this.disks[drive].length; + state.x = blocks & 0xff; + state.y = blocks >> 8; + + state.a = 0; + state.s &= ~flags.C; + } else { + state.a = 0x28; + state.s |= flags.C; + } + } + + /* + * readBlock + */ + + readBlock(state: CpuState, drive: number, block: number, buffer: Address) { + this.debug('read drive=' + drive); + this.debug('read buffer=' + buffer); + this.debug('read block=$' + toHex(block)); + + if (!this.disks[drive] || !this.disks[drive].length) { + debug('Drive', drive, 'is empty'); + return; + } + + // debug('read', '\n' + dumpBlock(drive, block)); + + for (let idx = 0; idx < 512; idx++) { + buffer.writeByte(this.disks[drive][block][idx]); + buffer = buffer.inc(1); + } + + state.a = 0; + state.s &= 0xfe; + } + + /* + * writeBlock + */ + + writeBlock(state: CpuState, drive: number, block: number, buffer: Address) { + this.debug('write drive=' + drive); + this.debug('write buffer=' + buffer); + this.debug('write block=$' + toHex(block)); + + if (!this.disks[drive] || !this.disks[drive].length) { + debug('Drive', drive, 'is empty'); + return; + } + + // debug('write', '\n' + dumpBlock(drive, block)); + + for (let idx = 0; idx < 512; idx++) { + this.disks[drive][block][idx] = buffer.readByte(); + buffer = buffer.inc(1); + } + state.a = 0; + state.s &= 0xfe; + } + + /* + * formatDevice + */ + + formatDevice(state: CpuState, drive: number) { + for (let idx = 0; idx < this.disks[drive].length; idx++) { + this.disks[drive][idx] = new Uint8Array(); + for (let jdx = 0; jdx < 512; jdx++) { + this.disks[drive][idx][jdx] = 0; + } + } + + state.a = 0; + state.s &= 0xfe; + } + + private access(off: byte, val: byte) { + let result; + const readMode = val === undefined; + + switch (off & 0x8f) { + case 0x80: + if (readMode) { + result = 0; + for (let idx = 0; idx < this.disks.length; idx++) { + result <<= 1; + if (this.disks[idx]) { + result |= 0x01; + } + } + } + break; + } + + return result; + } + + /* + * Interface + */ + + ioSwitch(off: byte, val: byte) { + return this.access(off, val); + } + + read(_page: byte, off: byte) { + const state = this.cpu.getState(); + let cmd; + let unit; + let buffer; + let block; + const blockOff = this.rom[0xff]; + const smartOff = blockOff + 3; + + if (off === blockOff && this.cpu.getSync()) { // Regular block device entry POINT + this.debug('block device entry'); + cmd = this.cpu.read(0x00, COMMAND); + unit = this.cpu.read(0x00, UNIT); + const bufferAddr = new Address(this.cpu, ADDRESS_LO); + const blockAddr = new Address(this.cpu, BLOCK_LO); + const drive = (unit & 0x80) ? 2 : 1; + const driveSlot = (unit & 0x70) >> 4; + + buffer = bufferAddr.readAddress(); + block = blockAddr.readWord(); + + this.debug('cmd=' + cmd); + this.debug('unit=$' + toHex(unit)); + + this.debug('slot=' + driveSlot + ' drive=' + drive); + this.debug('buffer=' + buffer + ' block=$' + toHex(block)); + + switch (cmd) { + case 0: // INFO + this.getDeviceInfo(state, drive); + break; + + case 1: // READ + this.readBlock(state, drive, block, buffer); + break; + + case 2: // WRITE + this.writeBlock(state, drive, block, buffer); + break; + + case 3: // FORMAT + this.formatDevice(state, unit); + break; + } + } else if (off == smartOff && this.cpu.getSync()) { + this.debug('smartport entry'); + const stackAddr = new Address(this.cpu, state.sp + 1, 0x01); + let blocks; + + const retVal = stackAddr.readAddress(); + + this.debug('return=' + retVal); + + const cmdBlockAddr = retVal.inc(1); + cmd = cmdBlockAddr.readByte(); + const cmdListAddr = cmdBlockAddr.inc(1).readAddress(); + + this.debug('cmd=' + cmd); + this.debug('cmdListAddr=' + cmdListAddr); + + stackAddr.writeAddress(retVal.inc(3)); + + const parameterCount = cmdListAddr.readByte(); + unit = cmdListAddr.inc(1).readByte(); + buffer = cmdListAddr.inc(2).readAddress(); + let status; + + this.debug('parameterCount=' + parameterCount); + switch (cmd) { + case 0x00: // INFO + status = cmdListAddr.inc(4).readByte(); + this.debug('info unit=' + unit); + this.debug('info buffer=' + buffer); + this.debug('info status=' + status); + switch (unit) { + case 0: + switch (status) { + case 0: + buffer.writeByte(1); // one device + buffer.inc(1).writeByte(1 << 6); // no interrupts + buffer.inc(2).writeByte(0); // reserved + buffer.inc(3).writeByte(0); // reserved + buffer.inc(4).writeByte(0); // reserved + buffer.inc(5).writeByte(0); // reserved + buffer.inc(6).writeByte(0); // reserved + buffer.inc(7).writeByte(0); // reserved + state.x = 8; + state.y = 0; + state.a = 0; + state.s &= 0xfe; + break; + } + break; + default: // Unit 1 + switch (status) { + case 0: + blocks = this.disks[unit].length; + buffer.writeByte(0xf0); // W/R Block device in drive + buffer.inc(1).writeByte(blocks & 0xff); // 1600 blocks + buffer.inc(2).writeByte((blocks & 0xff00) >> 8); + buffer.inc(3).writeByte((blocks & 0xff0000) >> 16); + state.x = 4; + state.y = 0; + state.a = 0; + state.s &= 0xfe; + break; + } + break; + } + state.a = 0; + state.s &= 0xfe; + break; + + case 0x01: // READ BLOCK + block = cmdListAddr.inc(4).readWord(); + this.readBlock(state, unit, block, buffer); + break; + + case 0x02: // WRITE BLOCK + block = cmdListAddr.inc(4).readWord(); + this.writeBlock(state, unit, block, buffer); + break; + + case 0x03: // FORMAT + this.formatDevice(state, unit); + break; + + case 0x04: // CONTROL + break; + + case 0x05: // INIT + break; + + case 0x06: // OPEN + break; + + case 0x07: // CLOSE + break; + + case 0x08: // READ + break; + + case 0x09: // WRITE + break; + } + } + + this.cpu.setState(state); + + return this.rom[off]; + } + + write() { + } + + getState() { + return { + disks: this.disks.map( + (disk) => disk.map( + (block) => new Uint8Array(block) + ) + ) + }; + } + + setState(state: SmartPortState) { + this.disks = state.disks.map( + (disk) => disk.map( + (block) => new Uint8Array(block) + ) + ); + } + + setBinary(drive: number, _name: string, fmt: string, data: ArrayBuffer) { + this.disks[drive] = []; + if (fmt == '2mg') { + data = data.slice(64); + } + for (let idx = 0; idx < data.byteLength; idx += 512) { + this.disks[drive].push(new Uint8Array(data.slice(idx, idx + 512))); + } + } + + setDisk(drive: number, json: BlockDevice) { + this.decodeDisk(drive, json); + } +} diff --git a/js/formats/format_utils.ts b/js/formats/format_utils.ts index a0dc306..a138429 100644 --- a/js/formats/format_utils.ts +++ b/js/formats/format_utils.ts @@ -13,20 +13,54 @@ import { byte, DiskFormat, memory } from '../types'; import { base64_decode, base64_encode } from '../base64'; import { bytify, debug, toHex } from '../util'; -export type Disk = { - format: DiskFormat, - name: string, - volume: byte, - tracks: memory[], - readOnly: boolean, -}; +export interface Disk { + format: DiskFormat + name: string + volume: byte + tracks: memory[] + readOnly: boolean +} -export type Drive = { - format: DiskFormat, - volume: byte, - tracks: memory[], - readOnly: boolean, - dirty: boolean, +/** + * Base format for JSON defined disks + */ +export class JSONDiskBase { + type: DiskFormat + name: string + volume: byte + readOnly: boolean +} + +/** + * JSON Disk format with base64 encoded tracks + */ + +export interface Base64JSONDisk extends JSONDiskBase { + encoding: 'base64' + data: string[] +} + +/** + * JSON Disk format with byte array tracks + */ + +export interface BinaryJSONDisk extends JSONDiskBase { + encoding: 'binary' + data: memory[][] +} + +/** + * General JSON Disk format + */ + +export type JSONDisk = Base64JSONDisk | BinaryJSONDisk; + +export interface Drive { + format: DiskFormat + volume: byte + tracks: memory[] + readOnly: boolean + dirty: boolean } /** @@ -52,7 +86,7 @@ export const PO = [ 0x0, 0x8, 0x1, 0x9, 0x2, 0xa, 0x3, 0xb, 0x4, 0xc, 0x5, 0xd, 0x6, 0xe, 0x7, 0xf ]; - + /** * ProDOS Logical sector order (index is ProDOS sector, value is physical sector). */ diff --git a/js/main2.js b/js/main2.js index 6932a2b..9e6a894 100644 --- a/js/main2.js +++ b/js/main2.js @@ -76,13 +76,13 @@ var io = apple2.getIO(); var printer = new Printer('#printer-modal .paper'); -var lc = new LanguageCard(io, rom); +var lc = new LanguageCard(rom); var parallel = new Parallel(io, printer); var videoTerm = new VideoTerm(io); var slinky = new RAMFactor(io, 1024 * 1024); var disk2 = new DiskII(io, driveLights, sectors); var clock = new Thunderclock(io); -var smartport = new SmartPort(io, cpu, { block: true }); +var smartport = new SmartPort(cpu, { block: true }); initUI(apple2, disk2, smartport, printer, false); diff --git a/js/main2e.js b/js/main2e.js index 131da75..e8d16b1 100644 --- a/js/main2e.js +++ b/js/main2e.js @@ -62,7 +62,7 @@ var parallel = new Parallel(io, printer); var slinky = new RAMFactor(io, 1024 * 1024); var disk2 = new DiskII(io, driveLights); var clock = new Thunderclock(io); -var smartport = new SmartPort(io, cpu); +var smartport = new SmartPort(cpu); initUI(apple2, disk2, smartport, printer, options.e); diff --git a/js/mmu.ts b/js/mmu.ts index f26ae83..36d221c 100644 --- a/js/mmu.ts +++ b/js/mmu.ts @@ -695,69 +695,53 @@ export default class MMU implements Memory, Restorable { _accessLangCard(off: byte, val?: byte) { const readMode = val === undefined; + const result = readMode ? 0 : undefined; - switch (off & 0x8b) { - // Language Card Switches + const writeSwitch = off & 0x01; + const offSwitch = off & 0x02; + const bank1Switch = off & 0x08; - case LOC.READBSR2: // 0xC080 - this._bank1 = false; + let bankStr; + let rwStr; + + if (writeSwitch) { // 0xC081, 0xC083 + if (readMode) { + this._writebsr = this._prewrite; + } + this._prewrite = readMode; + + if (offSwitch) { // 0xC08B this._readbsr = true; - this._writebsr = false; - this._prewrite = false; - // _debug('Bank 2 Read'); - break; - case LOC.WRITEBSR2: // 0xC081 - this._bank1 = false; + rwStr = 'Read/Write'; + } else { this._readbsr = false; - if (readMode) { this._writebsr = this._prewrite; } - this._prewrite = readMode; - // _debug('Bank 2 Write'); - break; - case LOC.OFFBSR2: // 0xC082 - this._bank1 = false; + rwStr = 'Write'; + } + } else { // 0xC080, 0xC082 + this._writebsr = false; + this._prewrite = false; + + if (offSwitch) { // 0xC082 this._readbsr = false; - this._writebsr = false; - this._prewrite = false; - // _debug('Bank 2 Off'); - break; - case LOC.READWRBSR2: // 0xC083 - this._bank1 = false; + rwStr = 'Off'; + } else { // 0xC080 this._readbsr = true; - if (readMode) { this._writebsr = this._prewrite; } - this._prewrite = readMode; - // _debug('Bank 2 Read/Write'); - break; - case LOC.READBSR1: // 0xC088 - this._bank1 = true; - this._readbsr = true; - this._writebsr = false; - this._prewrite = false; - // _debug('Bank 1 Read'); - break; - case LOC.WRITEBSR1: // 0xC089 - this._bank1 = true; - this._readbsr = false; - if (readMode) { this._writebsr = this._prewrite; } - this._prewrite = readMode; - // _debug('Bank 1 Write'); - break; - case LOC.OFFBSR1: // 0xC08A - this._bank1 = true; - this._readbsr = false; - this._writebsr = false; - this._prewrite = false; - // _debug('Bank 1 Off'); - break; - case LOC.READWRBSR1: // 0xC08B - this._bank1 = true; - this._readbsr = true; - if (readMode) { this._writebsr = this._prewrite; } - this._prewrite = readMode; - //_debug('Bank 1 Read/Write'); - break; + rwStr = 'Read'; + } } + + if (bank1Switch) { + this._bank1 = true; + bankStr = 'Bank 1'; + } else { + this._bank1 = false; + bankStr = 'Bank 2'; + } + + this._debug(bankStr, rwStr); this._updateBanks(); - } + + return result; } /* * The Big Switch diff --git a/js/types.ts b/js/types.ts index fee86a5..9fac0b1 100644 --- a/js/types.ts +++ b/js/types.ts @@ -47,7 +47,7 @@ export interface MemoryPages extends Memory { /* An interface card */ export interface Card extends Memory, Restorable { /* Reset the card */ - reset(): void; + reset?(): void; /* Draw card to canvas */ blit?(): ImageData;