apple2js/js/cards/cffa.js
2020-01-12 13:06:40 -08:00

412 lines
12 KiB
JavaScript

/* Copyright 2010-2019 Will Scullin <scullin@scullinsteel.com>
*
* 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/cffa';
import _2MG from '../formats/2mg';
import { ProDOSVolume } from '../formats/prodos';
import BlockVolume from '../formats/block';
import { dump } from '../formats/prodos/utils';
export default function CFFA() {
var COMMANDS = {
ATACRead: 0x20,
ATACWrite: 0x30,
ATAIdentify: 0xEC
};
// CFFA Card Settings
var SETTINGS = {
Max32MBPartitionsDev0: 0x800,
Max32MBPartitionsDev1: 0x801,
DefaultBootDevice: 0x802,
DefaultBootPartition: 0x803,
Reserved: 0x804, // 4 bytes
WriteProtectBits: 0x808,
MenuSnagMask: 0x809,
MenuSnagKey: 0x80A,
BootTimeDelayTenths: 0x80B,
BusResetSeconds: 0x80C,
CheckDeviceTenths: 0x80D,
ConfigOptionBits: 0x80E,
BlockOffsetDev0: 0x80F, // 3 bytes
BlockOffsetDev1: 0x812, // 3 bytes
Unused: 0x815
};
// CFFA ATA Register Locations
var LOC = {
ATADataHigh: 0x80,
SetCSMask: 0x81,
ClearCSMask: 0x82,
WriteEEPROM: 0x83,
NoWriteEEPROM: 0x84,
ATADevCtrl: 0x86,
ATAAltStatus: 0x86,
ATADataLow: 0x88,
AError: 0x89,
ASectorCnt: 0x8a,
ASector: 0x8b,
ATACylinder: 0x8c,
ATACylinderH: 0x8d,
ATAHead: 0x8e,
ATACommand: 0x8f,
ATAStatus: 0x8f
};
// ATA Status Bits
var STATUS = {
BSY: 0x80, // Busy
DRDY: 0x40, // Drive ready. 1 when ready
DWF: 0x20, // Drive write fault. 1 when fault
DSC: 0x10, // Disk seek complete. 1 when not seeking
DRQ: 0x08, // Data request. 1 when ready to write
CORR: 0x04, // Correct data. 1 on correctable error
IDX: 0x02, // 1 once per revolution
ERR: 0x01 // Error. 1 on error
};
// ATA Identity Block Locations
var IDENTITY = {
SectorCountLow: 58,
SectorCountHigh: 57
};
// CFFA internal Flags
var _disableSignalling = false;
var _writeEEPROM = true;
var _lba = true;
// LBA/CHS registers
var _sectorCnt = 1;
var _sector = 0;
var _cylinder = 0;
var _cylinderH = 0;
var _head = 0;
var _drive = 0;
// CFFA Data High register
var _dataHigh = 0;
// Current Sector
var _curSector = [];
var _curWord = 0;
// ATA Status registers
var _interruptsEnabled = false;
var _altStatus = 0;
var _error = 0;
var _identity = [[], []];
// Disk data
var _partitions = [
// Drive 1
[],
// Drive 2
[]
];
var _sectors = [
// Drive 1
[],
// Drive 2
[]
];
function _init() {
debug('CFFA');
for (var idx = 0; idx < 0x100; idx++) {
_identity[0][idx] = 0;
_identity[1][idx] = 0;
}
rom[SETTINGS.Max32MBPartitionsDev0] = 0x1;
rom[SETTINGS.Max32MBPartitionsDev1] = 0x0;
rom[SETTINGS.BootTimeDelayTenths] = 0x5; // 0.5 seconds
rom[SETTINGS.CheckDeviceTenths] = 0x5; // 0.5 seconds
}
// Verbose debug method
function _debug() {
// debug.apply(this, arguments);
}
function _reset() {
_debug('reset');
_sectorCnt = 1;
_sector = 0;
_cylinder = 0;
_cylinderH = 0;
_head = 0;
_drive = 0;
_dataHigh = 0;
}
// Convert status register into readable string
function _statusString(status) {
var statusArray = [];
for (var flag in STATUS) {
if(status & STATUS[flag]) {
statusArray.push(flag);
}
}
return statusArray.join('|');
}
// Dump sector as hex and ascii
function _dumpSector(sector) {
if (sector >= _sectors[_drive].length) {
_debug('dump sector out of range', sector);
return;
}
for (var idx = 0; idx < 16; idx++) {
var row = [];
var charRow = [];
for (var jdx = 0; jdx < 16; jdx++) {
var val = _sectors[_drive][sector][idx * 16 + jdx];
row.push(toHex(val, 4));
var low = val & 0x7f;
var hi = val >> 8 & 0x7f;
charRow.push(low > 0x1f ? String.fromCharCode(low) : '.');
charRow.push(hi > 0x1f ? String.fromCharCode(hi) : '.');
}
_debug(row.join(' '), ' ', charRow.join(''));
}
}
// Card I/O access
function _access(off, val) {
var readMode = val === undefined;
var retVal = readMode ? 0 : undefined;
var sector;
if (readMode) {
switch (off & 0x8f) {
case LOC.ATADataHigh: // 0x00
retVal = _dataHigh;
break;
case LOC.SetCSMask: // 0x01
_disableSignalling = true;
break;
case LOC.ClearCSMask: // 0x02
_disableSignalling = false;
break;
case LOC.WriteEEPROM: // 0x03
_writeEEPROM = true;
break;
case LOC.NoWriteEEPROM: // 0x04
_writeEEPROM = false;
break;
case LOC.ATAAltStatus: // 0x06
retVal = _altStatus;
break;
case LOC.ATADataLow: // 0x08
_dataHigh = _curSector[_curWord] >> 8;
retVal = _curSector[_curWord] & 0xff;
if (!_disableSignalling) {
_curWord++;
}
break;
case LOC.AError: // 0x09
retVal = _error;
break;
case LOC.ASectorCnt: // 0x0A
retVal = _sectorCnt;
break;
case LOC.ASector: // 0x0B
retVal = _sector;
break;
case LOC.ATACylinder: // 0x0C
retVal = _cylinder;
break;
case LOC.ATACylinderH: // 0x0D
retVal = _cylinderH;
break;
case LOC.ATAHead: // 0x0E
retVal = _head | (_lba ? 0x40 : 0) | (_drive ? 0x10 : 0) | 0xA0;
break;
case LOC.ATAStatus: // 0x0F
retVal = _sectors[_drive].length > 0 ? STATUS.DRDY | STATUS.DSC : 0;
_debug('returning status', _statusString(retVal));
break;
default:
debug('read unknown soft switch', toHex(off));
}
if (off & 0x7) { // Anything but data high/low
_debug('read soft switch', toHex(off), toHex(retVal));
}
} else {
if (off & 0x7) { // Anything but data high/low
_debug('write soft switch', toHex(off), toHex(val));
}
switch (off & 0x8f) {
case LOC.ATADataHigh: // 0x00
_dataHigh = val;
break;
case LOC.SetCSMask: // 0x01
_disableSignalling = true;
break;
case LOC.ClearCSMask: // 0x02
_disableSignalling = false;
break;
case LOC.WriteEEPROM: // 0x03
_writeEEPROM = true;
break;
case LOC.NoWriteEEPROM: // 0x04
_writeEEPROM = false;
break;
case LOC.ATADevCtrl: // 0x06
_debug('devCtrl:', toHex(val));
_interruptsEnabled = (val & 0x04) ? true : false;
_debug('Interrupts', _interruptsEnabled ? 'enabled' : 'disabled');
if (val & 0x02) {
_reset();
}
break;
case LOC.ATADataLow: // 0x08
_curSector[_curWord] = _dataHigh << 8 | val;
_curWord++;
break;
case LOC.ASectorCnt: // 0x0a
_debug('setting sector count', val);
_sectorCnt = val;
break;
case LOC.ASector: // 0x0b
_debug('setting sector', toHex(val));
_sector = val;
break;
case LOC.ATACylinder: // 0x0c
_debug('setting cylinder', toHex(val));
_cylinder = val;
break;
case LOC.ATACylinderH: // 0x0d
_debug('setting cylinder high', toHex(val));
_cylinderH = val;
break;
case LOC.ATAHead:
_head = val & 0xf;
_lba = val & 0x40 ? true : false;
_drive = val & 0x10 ? 1 : 0;
_debug('setting head', toHex(val & 0xf), 'drive', _drive);
if (!_lba) {
console.error('CHS mode not supported');
}
break;
case LOC.ATACommand: // 0x0f
_debug('command:', toHex(val));
sector = _head << 24 | _cylinderH << 16 | _cylinder << 8 | _sector;
_dumpSector(sector);
switch (val) {
case COMMANDS.ATAIdentify:
_debug('ATA identify');
_curSector = _identity[_drive];
_curWord = 0;
break;
case COMMANDS.ATACRead:
_debug('ATA read sector', toHex(_cylinderH), toHex(_cylinder), toHex(_sector), sector);
_curSector = _sectors[_drive][sector];
_curWord = 0;
break;
case COMMANDS.ATACWrite:
_debug('ATA write sector', toHex(_cylinderH), toHex(_cylinder), toHex(_sector), sector);
_curSector = _sectors[_drive][sector];
_curWord = 0;
break;
default:
debug('unknown command', toHex(val));
}
break;
default:
debug('write unknown soft switch', toHex(off), toHex(val));
}
}
return retVal;
}
_init();
return {
ioSwitch: function (off, val) {
return _access(off, val);
},
read: function(page, off) {
return rom[(page - 0xc0) << 8 | off];
},
write: function(page, off, val) {
if (_writeEEPROM) {
_debug('writing', toHex(page << 8 | off), toHex(val));
rom[(page - 0xc0) << 8 | off] - val;
}
},
// Assign a raw disk image to a drive. Must be 2mg or raw PO image.
setBinary: function(drive, name, ext, rawData) {
drive = drive - 1;
var disk;
var options = {
rawData,
name
};
if (ext === '2mg') {
disk = new _2MG(options);
} else {
disk = new BlockVolume(options);
}
// Convert 512 byte blocks into 256 word sectors
_sectors[drive] = disk.blocks.map(function(block) {
return new Uint16Array(block.buffer);
});
_identity[drive][IDENTITY.SectorCountHigh] = _sectors[0].length & 0xffff;
_identity[drive][IDENTITY.SectorCountLow] = _sectors[0].length >> 16;
var prodos = new ProDOSVolume(disk);
dump(prodos);
_partitions[drive] = prodos;
if (drive) {
rom[SETTINGS.Max32MBPartitionsDev1] = 0x1;
} else {
rom[SETTINGS.Max32MBPartitionsDev0] = 0x1;
}
}
};
}