PDP-8-E-Simulator/RK8E/RK05.m

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