mirror of
https://github.com/DerekK19/PDP-8-E-Simulator.git
synced 2025-01-17 04:30:06 +00:00
513 lines
12 KiB
Objective-C
513 lines
12 KiB
Objective-C
/*
|
|
* PDP-8/E Simulator
|
|
*
|
|
* Copyright © 1994-2015 Bernhard Baehr
|
|
*
|
|
* RK05.m - Class implementing a RK05 DECpack drive
|
|
*
|
|
* This file is part of PDP-8/E Simulator.
|
|
*
|
|
* PDP-8/E Simulator is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
|
|
#import <Cocoa/Cocoa.h>
|
|
#import <sys/stat.h>
|
|
#import <mach/mach_time.h>
|
|
|
|
#import "PluginFramework/PluginAPI.h"
|
|
#import "PluginFramework/PDP8.h"
|
|
#import "PluginFramework/Utilities.h"
|
|
|
|
#import "RK05.h"
|
|
#import "RK8E.h"
|
|
|
|
|
|
@implementation RK05
|
|
|
|
|
|
/* "commands" for a RK05 drive to be performed asynchroniously, bit coded in self.cmd */
|
|
#define RK05_RECALIBRATE 0400
|
|
#define RK05_SET_CRC_STATE 0200
|
|
#define RK05_DELAY 0100
|
|
#define RK05_SEEK 0040
|
|
#define RK05_FLUSH_CYL 0020
|
|
#define RK05_READ_CYL 0010
|
|
#define RK05_INPUT_BLOCK 0004
|
|
#define RK05_OUTPUT_BLOCK 0002
|
|
#define RK05_RAISE_FLAG 0001
|
|
|
|
#define NO_COMMANDS_AVAILABLE 0
|
|
#define COMMANDS_AVAILABLE 1
|
|
|
|
|
|
- (int) driveNumber
|
|
{
|
|
return driveNumber;
|
|
}
|
|
|
|
|
|
- (void) setDriveNumber:(short)drvNum
|
|
{
|
|
driveNumber = drvNum;
|
|
}
|
|
|
|
|
|
- (void) setPDP8:(PDP8 *)p8
|
|
{
|
|
pdp8 = p8;
|
|
}
|
|
|
|
|
|
- (void) setWriteProtected:(BOOL)writeProtected
|
|
{
|
|
locked = writeProtected;
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:WRITEPROTECT_CHANGED_NOTIFICATION
|
|
object:self];
|
|
}
|
|
|
|
|
|
- (BOOL) isWriteProtected
|
|
{
|
|
return locked;
|
|
}
|
|
|
|
|
|
- (BOOL) flush
|
|
{
|
|
BOOL error = NO;
|
|
if (decpack && dirty) {
|
|
if (fseek(decpack, cyl * RK05_BUFSIZE, SEEK_SET) < 0)
|
|
error = YES;
|
|
else if (fwrite(buffer, 1, RK05_BUFSIZE, decpack) != RK05_BUFSIZE)
|
|
error = YES;
|
|
dirty = NO;
|
|
}
|
|
return error;
|
|
}
|
|
|
|
|
|
- (BOOL) canMount:(NSString *)path
|
|
{
|
|
NSAssert (decpack == NULL, @"A DECpack is already mounted");
|
|
FILE *handle = fopen([path cStringUsingEncoding:NSUTF8StringEncoding], "r+");
|
|
if (handle) {
|
|
if (flock(fileno(handle), LOCK_EX | LOCK_NB)) {
|
|
fclose (handle);
|
|
return NO;
|
|
}
|
|
flock (fileno(handle), LOCK_UN);
|
|
return YES;
|
|
}
|
|
return NO;
|
|
}
|
|
|
|
|
|
- (int) mount:(NSString *)path create:(BOOL)create
|
|
{
|
|
NSAssert (decpack == NULL, @"A DECpack is already mounted");
|
|
int err = 0;
|
|
decpack = fopen([path cStringUsingEncoding:NSUTF8StringEncoding], create ? "w+" : "r+");
|
|
if (decpack) {
|
|
if (flock(fileno(decpack), LOCK_EX | LOCK_NB)) {
|
|
fclose (decpack);
|
|
decpack = NULL;
|
|
err = -2;
|
|
}
|
|
} else
|
|
err = -1;
|
|
cyl = -1;
|
|
dirty = NO;
|
|
memset (crcstate, 0, sizeof(crcstate));
|
|
[rk8e updateStatusMountFlags];
|
|
return err;
|
|
}
|
|
|
|
|
|
- (BOOL) unmount
|
|
{
|
|
NSAssert (decpack != NULL, @"Cannot unmount without mounted DECpack");
|
|
BOOL error = [self flush];
|
|
flock (fileno(decpack), LOCK_UN);
|
|
fclose (decpack);
|
|
decpack = NULL;
|
|
[rk8e updateStatusMountFlags];
|
|
return error;
|
|
}
|
|
|
|
|
|
- (BOOL) isMounted
|
|
{
|
|
return decpack != NULL;
|
|
}
|
|
|
|
|
|
- (BOOL) isBusy
|
|
{
|
|
return cmd != 0;
|
|
}
|
|
|
|
|
|
- (BOOL) isCalibrating
|
|
{
|
|
return (cmd & RK05_RECALIBRATE) ? YES : NO;
|
|
}
|
|
|
|
|
|
- (void) stopCalibrating
|
|
{
|
|
cmd &= ~RK05_RECALIBRATE;
|
|
}
|
|
|
|
|
|
- (void) abortAllCommands
|
|
{
|
|
cmd = 0;
|
|
}
|
|
|
|
|
|
- (void) setStatusAndAbortAllCommands
|
|
{
|
|
if (cmd) {
|
|
unsigned short set = 0;
|
|
if (cmd & RK05_RECALIBRATE)
|
|
set |= STATUS_CONTROL_BUSY;
|
|
else
|
|
set |= (STATUS_DONE | STATUS_ERROR);
|
|
if (cmd & RK05_SEEK)
|
|
set |= STATUS_HEAD_IN_MOTION;
|
|
cmd = 0;
|
|
[rk8e setStatusBits:set clearStatusBits:0];
|
|
}
|
|
}
|
|
|
|
|
|
- (void) setRecalibrating
|
|
{
|
|
durationMicroSeconds = 85000; // 85 ms == 200 track movement
|
|
cmd |= RK05_RECALIBRATE | RK05_DELAY;
|
|
}
|
|
|
|
|
|
- (void) setFlushCylinder
|
|
{
|
|
if (dirty)
|
|
cmd |= RK05_FLUSH_CYL;
|
|
}
|
|
|
|
|
|
- (void) setReadCylinder:(unsigned short)cylinder
|
|
{
|
|
if (cyl != cylinder) {
|
|
newcyl = cylinder;
|
|
cmd |= RK05_READ_CYL;
|
|
}
|
|
}
|
|
|
|
|
|
- (void) setRaiseFlag
|
|
{
|
|
cmd |= RK05_RAISE_FLAG;
|
|
}
|
|
|
|
|
|
- (void) clearRaiseFlag
|
|
{
|
|
cmd &= ~RK05_RAISE_FLAG;
|
|
}
|
|
|
|
|
|
- (void) setRead:(BOOL)read write:(BOOL)write all:(BOOL)all newBlock:(unsigned short)newBlock
|
|
{
|
|
long d;
|
|
|
|
if (write && locked) {
|
|
[rk8e setStatusBits:STATUS_WRITE_LOCK_ERROR | STATUS_ERROR clearStatusBits:0];
|
|
return;
|
|
}
|
|
if (newBlock > 014537) {
|
|
[rk8e setStatusBits:STATUS_DONE | STATUS_ERROR clearStatusBits:0];
|
|
return;
|
|
}
|
|
if (! read && ! write && [self isMounted]) { // seek only
|
|
/* set on DISK ADDRESS ACKNOWLAGE; it is set again
|
|
on seek complete when command bit 4 is on */
|
|
[rk8e setStatusBits:STATUS_DONE clearStatusBits:0];
|
|
cmd |= RK05_SEEK;
|
|
}
|
|
if (cyl != (newBlock >> 5)) {
|
|
if (dirty)
|
|
cmd |= RK05_FLUSH_CYL;
|
|
newcyl = newBlock >> 5;
|
|
cmd |= RK05_READ_CYL;
|
|
d = newcyl - cyl;
|
|
if (d < 0)
|
|
d = -d;
|
|
d = 9621l + 379l * d;
|
|
/* 9.621 ms for seek start/stop, 0.379 ms per track */
|
|
} else if (read || write) {
|
|
d = ((long) (newBlock & 017)) - ((long) (blk & 017));
|
|
if (d < 0)
|
|
d += 16;
|
|
/* d == # sectors between the last accessed sector and the new sector */
|
|
if (d <= 1) { /* correctly, this must be "if (d == 1)", but
|
|
MAINDEC-08-DHRKB-E-PB test 28 (consecutive sector read all test)
|
|
seems to have a little bug: it starts with WRITE ALL sector 36,
|
|
then READ ALL sector 0 (two sectors distance) and often fails
|
|
immediately with Transfer Done not set. The test should start
|
|
with sector 37. */
|
|
if (all) { /* read or write ALL can read consecutive sectors */
|
|
d = 1;
|
|
} else { /* normal read can't read consecutive sectors */
|
|
if (d == 1)
|
|
d = 16;
|
|
}
|
|
}
|
|
d *= 2500; /* 2.5 ms per sector, 40 ms per revolution */
|
|
} else
|
|
d = 0;
|
|
durationMicroSeconds = d;
|
|
cmd |= RK05_DELAY;
|
|
blk = newBlock; /* DMAN may destroy rk8e.block */
|
|
if (read)
|
|
cmd |= RK05_INPUT_BLOCK | RK05_RAISE_FLAG;
|
|
if (write)
|
|
cmd |= RK05_OUTPUT_BLOCK | RK05_RAISE_FLAG;
|
|
}
|
|
|
|
|
|
- (void) start
|
|
{
|
|
NSDate *delayUntil = [[NSDate alloc] initWithTimeIntervalSinceNow:0.005]; // 5 ms
|
|
startAtMachAbsolute = mach_absolute_time();
|
|
if ([commandsLock lockWhenCondition:NO_COMMANDS_AVAILABLE beforeDate:delayUntil]) {
|
|
[commandsLock unlockWithCondition:COMMANDS_AVAILABLE];
|
|
}
|
|
[delayUntil release]; // don't use autorelease, this floods the PDP-8 run thread autorelease pool
|
|
}
|
|
|
|
|
|
static ushort CRC (const unsigned char *p) /* CRC with polygon x^16 + x^15 + x^2 + 1 */
|
|
{
|
|
register short i, j;
|
|
register unsigned long data;
|
|
register ushort crc;
|
|
|
|
crc = i = 0;
|
|
while (i < 384) {
|
|
data = p[i++] << 4;
|
|
data |= p[i] >> 4;
|
|
data |= p[i++] << 20;
|
|
data |= p[i++] << 12;
|
|
for (j = 0; j < 24; j++) {
|
|
if ((data ^ crc) & 0x1)
|
|
crc = (crc >> 1) ^ 0xa001;
|
|
else
|
|
crc >>= 1;
|
|
data >>= 1;
|
|
}
|
|
}
|
|
return (crc);
|
|
}
|
|
|
|
|
|
- (void) processCommand
|
|
{
|
|
register ushort *field;
|
|
register unsigned char *bp;
|
|
register ushort i;
|
|
register ushort words;
|
|
unsigned short rk8eBlock;
|
|
unsigned short rk8eCommand;
|
|
unsigned short clear, set;
|
|
|
|
/* after processing a command, return, so that the next call can process higher priority
|
|
commands that may have been added in the meantime by the RK8-E control */
|
|
if (cmd & RK05_FLUSH_CYL) {
|
|
[rk8e lockControl];
|
|
cmd &= ~RK05_FLUSH_CYL;
|
|
if (driveNumber == [rk8e getCurrentDriveNumber])
|
|
[rk8e setStatusBits:STATUS_HEAD_IN_MOTION clearStatusBits:0];
|
|
cmd |= RK05_SEEK;
|
|
if (fseek(decpack, cyl * RK05_BUFSIZE, SEEK_SET) < 0)
|
|
[rk8e setStatusBits:STATUS_SEEK_FAILED clearStatusBits:0];
|
|
else if (fwrite(buffer, 1, RK05_BUFSIZE, decpack) != RK05_BUFSIZE)
|
|
[rk8e setStatusBits:STATUS_ERROR clearStatusBits:0];
|
|
else
|
|
dirty = 0;
|
|
[rk8e unlockControl];
|
|
return;
|
|
}
|
|
if (cmd & RK05_READ_CYL) {
|
|
[rk8e lockControl];
|
|
cmd &= ~RK05_READ_CYL;
|
|
if (cyl != newcyl) {
|
|
if (driveNumber == [rk8e getCurrentDriveNumber])
|
|
[rk8e setStatusBits:STATUS_HEAD_IN_MOTION clearStatusBits:0];
|
|
cmd |= RK05_SEEK;
|
|
cyl = newcyl;
|
|
if (fseek(decpack, 0, SEEK_END) < 0)
|
|
[rk8e setStatusBits:STATUS_SEEK_FAILED clearStatusBits:0];
|
|
else if (ftell(decpack) < (cyl + 1) * RK05_BUFSIZE)
|
|
bzero (buffer, RK05_BUFSIZE);
|
|
else if (fseek(decpack, cyl * RK05_BUFSIZE, SEEK_SET) < 0)
|
|
[rk8e setStatusBits:STATUS_SEEK_FAILED clearStatusBits:0];
|
|
else if (fread(buffer, 1, RK05_BUFSIZE, decpack) != RK05_BUFSIZE)
|
|
[rk8e setStatusBits:STATUS_ERROR clearStatusBits:0];
|
|
}
|
|
[rk8e unlockControl];
|
|
return;
|
|
}
|
|
if (cmd & RK05_INPUT_BLOCK) {
|
|
[rk8e lockControl];
|
|
if (! [rk8e isCRCOK])
|
|
[rk8e setStatusBits:STATUS_CYLINDER_ADDRESS_ERROR clearStatusBits:0];
|
|
/*
|
|
* Read a sector from the RK8E disk file. The file packing scheme:
|
|
* <- 8 bits->
|
|
* +---------+ 0H => High order of pdp8 word 0
|
|
* | 0H | 0L => Low order of pdp8 word 0
|
|
* +----+----+ 1H => High order of pdp8 word 1
|
|
* | 0L | 1H | 1L => Low order of pdp8 word 1
|
|
* +----+----+
|
|
* | 1L | In other words, pdp8 words are written
|
|
* +----+----+ in bit order (high to low).
|
|
*/
|
|
rk8eBlock = [rk8e getBlockNumber];
|
|
bp = buffer + (rk8eBlock & 037) * 384;
|
|
[rk8e setCRC:CRC(bp)];
|
|
rk8eCommand = [rk8e getCommand];
|
|
words = (rk8eCommand & COMMAND_HALF_BLOCK) ? 64 : 128; /* half of words to transfer */
|
|
field = [pdp8 directMemoryAccess] + ((rk8eCommand & 070) << 9);
|
|
i = [rk8e getCurrentAddress];
|
|
if (field < [pdp8 directMemoryAccess] + [pdp8 memorySize]) {
|
|
while (words--) { /* two words per loop */
|
|
field[i & 07777] = *bp++ << 4;
|
|
field[i++ & 07777] |= *bp >> 4;
|
|
field[i & 07777] = (*bp++ & 017) << 8;
|
|
field[i++ & 07777] |= *bp++;
|
|
}
|
|
[pdp8 directMemoryWriteFinished];
|
|
}
|
|
words = (rk8eCommand & COMMAND_HALF_BLOCK) ? 128 : 256;
|
|
[rk8e setCurrentAddress:([rk8e getCurrentAddress] + words) & 07777];
|
|
cmd &= ~RK05_INPUT_BLOCK;
|
|
if (crcstate[rk8eBlock >> 3] & (1 << (rk8eBlock & 7)))
|
|
[rk8e setStatusBits:STATUS_CRC_ERROR clearStatusBits:0];
|
|
[rk8e unlockControl];
|
|
return;
|
|
}
|
|
if (cmd & RK05_OUTPUT_BLOCK) {
|
|
[rk8e lockControl];
|
|
if (! [rk8e isCRCOK])
|
|
[rk8e setStatusBits:STATUS_CYLINDER_ADDRESS_ERROR clearStatusBits:0];
|
|
rk8eBlock = [rk8e getBlockNumber];
|
|
bp = (unsigned char *) buffer + (rk8eBlock & 037) * 384;
|
|
rk8eCommand = [rk8e getCommand];
|
|
words = (rk8eCommand & COMMAND_HALF_BLOCK) ? 64 : 128; /* half of words to transfer */
|
|
field = [pdp8 directMemoryAccess] + ((rk8eCommand & 070) << 9);
|
|
i = [rk8e getCurrentAddress];
|
|
while (words--) { /* two words per loop */
|
|
*bp++ = field[i & 07777] >> 4;
|
|
*bp = field[i++ & 07777] << 4;
|
|
*bp++ |= field[i & 07777] >> 8;
|
|
*bp++ = field[i++ & 07777];
|
|
}
|
|
if (rk8eCommand & COMMAND_HALF_BLOCK) {
|
|
for (i = 0; i < 192; i++)
|
|
*bp++ = 0;
|
|
}
|
|
[rk8e setCRC:CRC(bp - 384)];
|
|
words = (rk8eCommand & COMMAND_HALF_BLOCK) ? 128 : 256;
|
|
[rk8e setCurrentAddress:([rk8e getCurrentAddress] + words) & 07777];
|
|
dirty = true;
|
|
cmd &= ~RK05_OUTPUT_BLOCK;
|
|
cmd |= RK05_SET_CRC_STATE;
|
|
[rk8e unlockControl];
|
|
return;
|
|
}
|
|
if (cmd & RK05_DELAY) {
|
|
// don't lock the control
|
|
cmd &= ~RK05_DELAY;
|
|
int speed = [pdp8 getGoSpeed];
|
|
if (speed != GO_AS_FAST_AS_POSSIBLE) {
|
|
uint64_t delayUntilAbsolute = startAtMachAbsolute +
|
|
nanoseconds2absolute(durationMicroSeconds * 1000ll);
|
|
if (speed == GO_WITH_PDP8_SPEED)
|
|
mach_wait_until (delayUntilAbsolute);
|
|
else {
|
|
while (mach_absolute_time() < delayUntilAbsolute)
|
|
;
|
|
}
|
|
}
|
|
// don't unlock the control
|
|
return;
|
|
}
|
|
if (cmd & RK05_SET_CRC_STATE) {
|
|
[rk8e lockControl];
|
|
cmd &= ~RK05_SET_CRC_STATE;
|
|
words = 1 << (blk & 7);
|
|
if ([rk8e isCRCOK])
|
|
crcstate[blk >> 3] &= ~words;
|
|
else
|
|
crcstate[blk >> 3] |= words;
|
|
[rk8e unlockControl];
|
|
return;
|
|
}
|
|
if (cmd & (RK05_RECALIBRATE | RK05_SEEK | RK05_RAISE_FLAG)) {
|
|
[rk8e lockControl];
|
|
set = clear = 0;
|
|
if (cmd & RK05_RAISE_FLAG) {
|
|
set = STATUS_DONE;
|
|
if (driveNumber == [rk8e getCurrentDriveNumber])
|
|
clear |= STATUS_HEAD_IN_MOTION;
|
|
if (! [rk8e isCRCOK])
|
|
set |= STATUS_CYLINDER_ADDRESS_ERROR;
|
|
}
|
|
if (cmd & (RK05_RECALIBRATE | RK05_SEEK)) {
|
|
if (driveNumber == [rk8e getCurrentDriveNumber]) {
|
|
if ([rk8e getCommand] & COMMAND_SET_DONE_ON_SEEK_DONE)
|
|
set = STATUS_DONE;
|
|
clear = STATUS_HEAD_IN_MOTION;
|
|
}
|
|
}
|
|
[rk8e setStatusBits:set clearStatusBits:clear];
|
|
cmd &= ~(RK05_RAISE_FLAG | RK05_RECALIBRATE | RK05_SEEK);
|
|
[rk8e unlockControl];
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
- (void) rk05Thread:(id)object
|
|
{
|
|
[[NSAutoreleasePool alloc] init];
|
|
for (;;) {
|
|
[commandsLock lockWhenCondition:COMMANDS_AVAILABLE];
|
|
while (cmd)
|
|
[self processCommand];
|
|
[commandsLock unlockWithCondition:NO_COMMANDS_AVAILABLE];
|
|
}
|
|
}
|
|
|
|
|
|
- (void) awakeFromNib
|
|
{
|
|
commandsLock = [[NSConditionLock alloc] initWithCondition:NO_COMMANDS_AVAILABLE];
|
|
[NSThread detachNewThreadSelector:@selector(rk05Thread:) toTarget:self withObject:nil];
|
|
}
|
|
|
|
|
|
@end
|