simh-testsetgenerator/I650/i650_dsk.c
Roberto Sancho Villa 08027162ca I650: Update IBM 650 simulator to Release 4
- Integration with updated sim_card API
- Addition of MT (Mag Tape) device
- Addition of DSK (Disk) device
- Build time simulator test
2020-05-15 05:57:01 -07:00

494 lines
20 KiB
C

/* i650_dsk.c: IBM 650 RAMAC Disk Dotrage
Copyright (c) 2018, Roberto Sancho
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
ROBERTO SANCHO BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "i650_defs.h"
#define UNIT_DSK UNIT_ATTABLE | UNIT_DISABLE | UNIT_FIX
#define DISK_SIZE (12*60*100) // a physical disk plate size: 12 bytes per word x 60 words per track x 100 tracks per disk
// there are 100 like this in each unit
#define UPDATE_RAMAC 10 // update ramac arm movement each 10 msec of simulted time
// time pregress as drum wordcount progresses
/* Definitions */
uint32 dsk_cmd(int opcode, int32 addr, uint16 fast);
t_stat dsk_srv(UNIT *);
void dsk_ini(UNIT *, t_bool f);
t_stat dsk_reset(DEVICE *);
t_stat dsk_attach(UNIT *, CONST char *);
t_stat dsk_detach(UNIT *);
t_stat dsk_help (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr);
const char *dsk_description (DEVICE *dptr);
UNIT dsk_unit[4] = {
{UDATA(&dsk_srv, UNIT_DSK, 0), 0}, /* 0 */
{UDATA(&dsk_srv, UNIT_DSK, 0), 0}, /* 1 */
{UDATA(&dsk_srv, UNIT_DSK, 0), 0}, /* 2 */
{UDATA(&dsk_srv, UNIT_DSK, 0), 0}, /* 3 */
};
DEVICE dsk_dev = {
"DSK", dsk_unit, NULL, NULL,
4, 8, 15, 1, 8, 8,
NULL, NULL, &dsk_reset, NULL, &dsk_attach, &dsk_detach,
&dsk_dib, DEV_DISABLE | DEV_DEBUG, 0, dev_debug,
NULL, NULL, &dsk_help, NULL, NULL, &dsk_description
};
// array for disc units (4) arm's positions (3 arms per unit)
struct armrec {
int current_disk, current_track; // current disk plate/track where the arm is positioned
int dest_disk, dest_track; // destination position where the arm should go
int cmd; // opcode being executed (OP_SDS, OP_RDS, OP_WDS)
t_int64 InitTime; // timestamp using global wordTime counter when operation starts
struct armmov {
int disk, track; // disk plate/track where the arm is positioned in this point of movement sequence
int msec; // time in msec arm stay in this position
} seq[1+100+50+100+1]; // sequeece of arm movement. If =0 -> end of sequence
} Arm[4][3];
int dsk_read_numeric_word(char * buf, t_int64 * d, int * ZeroNeg)
{
int i, neg;
char c;
neg = 0;
*d = 0;
if (ZeroNeg != NULL) *ZeroNeg = 0;
for (i=0;i<10;i++) {
c = *buf++;
if ((c < '0') || (c > '9')) c='0';
*d = *d * 10 + (c - '0');
}
if (*buf++ == '-') neg=1;
if (neg) *d = -*d;
if (ZeroNeg != NULL) *ZeroNeg = ((neg) && (*d == 0)) ? 1:0;
return 0;
}
void dsk_write_numeric_word(char * buf, t_int64 d, int ZeroNeg)
{
int i, neg;
char c;
neg = 0;
if (d < 0) {neg=1; d=-d;}
if (ZeroNeg) neg=1;
for (i=0;i<10;i++) {
c = Shift_Digits(&d,1) + '0';
*buf++ = c;
}
*buf++ = neg ? '-':'+';
}
// perform the operation (Read, Write) on RAMAC unit file
// init file if len=0 (flat format)
//
t_stat dsk_operation(int cmd, int unit, int arm, int disk, int track)
{
FILE *f;
int flen, i, ic, ZeroNeg;
char buf[DISK_SIZE+1];
t_int64 d;
char s[6];
// buf holds a full disk
if ((unit < 0) || (unit > 3)) return 0;
if ((arm < 0) || (arm > 2) ) return 0;
if ((disk < 0) || (disk > 99)) return 0;
if ((track < 0) || (track > 99)) return 0;
f = dsk_unit[unit].fileref; // get disk file from unit;
flen = sim_fsize(f);
if (flen == 0) {
// new file, fill it with blanks
memset(buf, 32, sizeof(buf)); // fill with space
for (i=1;i<1000;i++) buf[i*12*6-1]=13; // ad some cr lo allow text editor to vire ramac file
buf[sizeof(buf)-1]=0; // add string terminator
for(i=0;i<100;i++) sim_fwrite(buf, 1, DISK_SIZE, f);
}
sim_fseek(f, DISK_SIZE * disk, SEEK_SET);
sim_fread(buf, 1, DISK_SIZE, f); // read the entire disc (100 tracks)
ic = 12 * 60 * track; // ic is char at beginning of track
sim_debug(DEBUG_DETAIL, &cpu_dev, "... RAMAC file at fseek %d, ic %d\n", DISK_SIZE * disk, ic);
if (cmd==OP_RDS) {
for(i=0;i<60;i++) {
dsk_read_numeric_word(&buf[ic], &d, &ZeroNeg);
ic += 12; // 12 bytes per word
// store into IAS
IAS[i] = d;
IAS_NegativeZeroFlag[i] = ZeroNeg;
sim_debug(DEBUG_DETAIL, &cpu_dev, "... RAMAC to IAS %04d: %06d%04d%c '%s'\n",
i+9000, printfw(d,ZeroNeg),
word_to_ascii(s, 1, 5, d));
}
// set IAS_TimingRing. Nothing said in RAMAC manual, but needed to make supersoap CDD pseudo op work properly
IAS_TimingRing=0;
} else if (cmd==OP_WDS) {
for(i=0;i<60;i++) {
// read IAS
d = IAS[i];
ZeroNeg = IAS_NegativeZeroFlag[i];
sim_debug(DEBUG_DETAIL, &cpu_dev, "... IAS %04d to RAMAC: %06d%04d%c '%s'\n",
i+9000, printfw(d,ZeroNeg),
word_to_ascii(s, 1, 5, d));
// write numeric to disk buf
dsk_write_numeric_word(&buf[ic], d, ZeroNeg);
ic += 12;
}
// set IAS_TimingRing. Nothing said in RAMAC manual, but needed to make supersoap CDD pseudo op work properly
IAS_TimingRing=0;
// write back disk to ramac unit file
sim_fseek(f, DISK_SIZE * disk, SEEK_SET);
sim_fwrite(buf, 1, DISK_SIZE, f); // write the entire disc (100 tracks)
}
// don't know if Seek Opcode (SDS) also sets TimingRing to zero
return SCPE_OK;
}
// return 1 if disk unit n (0..3) and arm (0..2) is ready to receive a command
int dsk_ready(int unit, int arm)
{
if ((unit < 0) || (unit > 3)) return 0;
if ((arm < 0) || (arm > 2) ) return 0;
if (Arm[unit][arm].cmd == 0) return 1; // arm has no cmd to execute -> it is ready to receive new command
return 0;
}
void dsk_set_mov_seq(int unit,int arm)
{
// set arm movement sequence to its destination
//
// arm timing
// seek: 50 msec setup time
// on same disk:
// 2 msec per track in same disk (0-99)
// 25 msec sensing track gap (that identifies the start of track pos) a mean between 0-50 msec or
// to extract arm outside disk for arm to go to another disk
// going to another physical disk:
// 200 msec start arm vertical motion
// 9 msec per physical disk (0 to 49)
// 200 msec stop arm vertical motion
//
// read: 110 msec
// write: 135 msec
//
int cmd, nseq, i, d1, d2, dy, tr;
cmd = Arm[unit][arm].cmd;
nseq = 0;
// seek or read/write but current arm pos not the addr selected for
// read/write -> must do a seek cycle
if ((cmd == OP_SDS) ||
(Arm[unit][arm].current_disk != Arm[unit][arm].dest_disk) ||
(Arm[unit][arm].current_track != Arm[unit][arm].dest_track)) {
// start seek sequence at current arm pos
Arm[unit][arm].seq[nseq].disk = Arm[unit][arm].current_disk;
Arm[unit][arm].seq[nseq].track = tr = Arm[unit][arm].current_track;
Arm[unit][arm].seq[nseq++].msec = 50; // msec needed for seek setup time
// is arm already accessing physical destination disk?
if ((d1=(Arm[unit][arm].current_disk % 50)) != (d2=(Arm[unit][arm].dest_disk % 50))) {
// not yet, should move arm up or down
// is arm outside physical disk stack?
if (Arm[unit][arm].current_track >= 0) {
// not yet, should move arm outside physical disk (up to -1)
// move out arm track to track until outside of physical disk
for (i=Arm[unit][arm].current_track;i>=0;i--) {
Arm[unit][arm].seq[nseq].disk = Arm[unit][arm].current_disk;
Arm[unit][arm].seq[nseq].track = i;
Arm[unit][arm].seq[nseq++].msec = 2; // msec needed for horizontal arm movement of 1 track
}
}
// now arm is outside disk stack, can move up and down
Arm[unit][arm].seq[nseq].disk = Arm[unit][arm].current_disk;
Arm[unit][arm].seq[nseq].track = -1;
Arm[unit][arm].seq[nseq++].msec = 200; // msec needed to setup vertical arm movement
// move out up/down on disk stack up to destination disk
dy = (d1 < d2) ? +1:-1;
i = Arm[unit][arm].current_disk;
for (;;) {
if (i % 50 == d2) break;
Arm[unit][arm].seq[nseq].disk = i;
Arm[unit][arm].seq[nseq].track = -1;
Arm[unit][arm].seq[nseq++].msec = 9; // msec needed for vertical arm movement of 1 physical disk
i=i+dy;
}
// stop motion and select destination disk (not physical disk)
Arm[unit][arm].seq[nseq].disk = Arm[unit][arm].dest_disk;
Arm[unit][arm].seq[nseq].track = tr = -1;
Arm[unit][arm].seq[nseq++].msec = 200; // msec needed to stop vertical arm movement
}
// now arm accessing physical destination disk
// is arm at destination track?
if (tr != (d2=Arm[unit][arm].dest_track)) {
// not yet, should move arm horizontally
dy = (tr < d2) ? +1:-1;
for (;;) {
if (tr == d2) break;
Arm[unit][arm].seq[nseq].disk = Arm[unit][arm].dest_disk;
Arm[unit][arm].seq[nseq].track = tr;
Arm[unit][arm].seq[nseq++].msec = 2; // msec needed for horizontal arm movement of 1 track
tr=tr+dy;
}
}
// now arm is positioned on destination track, disk
// sense the track gap to finish seek operation
Arm[unit][arm].seq[nseq].disk = Arm[unit][arm].dest_disk;
Arm[unit][arm].seq[nseq].track = Arm[unit][arm].dest_track;
Arm[unit][arm].seq[nseq++].msec = 25; // msec needed for sensing track gap
}
// read operation
if (cmd == OP_RDS) {
Arm[unit][arm].seq[nseq].disk = Arm[unit][arm].dest_disk;
Arm[unit][arm].seq[nseq].track = Arm[unit][arm].dest_track;
Arm[unit][arm].seq[nseq++].msec = 110; // msec needed for reading entire track
} else if (cmd == OP_WDS) {
Arm[unit][arm].seq[nseq].disk = Arm[unit][arm].dest_disk;
Arm[unit][arm].seq[nseq].track = Arm[unit][arm].dest_track;
Arm[unit][arm].seq[nseq++].msec = 135; // msec needed for writing entire track
}
// set end of sequence
Arm[unit][arm].seq[nseq].disk = Arm[unit][arm].dest_disk;
Arm[unit][arm].seq[nseq].track = Arm[unit][arm].dest_track;
Arm[unit][arm].seq[nseq++].msec = 0; // end of sequence mark
}
/* Start off a RAMAC command */
uint32 dsk_cmd(int cmd, int32 addr, uint16 fast)
{
DEVICE *dptr;
UNIT *uptr;
int unit, disk, track, arm;
int time;
int bFastMode;
unit =(addr / 100000) % 10;
disk =(addr / 1000) % 100,
track=(addr / 10) % 100,
arm =(addr % 10);
time = 0;
/* Make sure addr unit number */
if ((unit > 3) || (unit < 0)) return STOP_ADDR;
if ((arm > 2) || (arm < 0)) return STOP_ADDR;
uptr = &dsk_unit[unit];
dptr = find_dev_from_unit(uptr);
// init IBM 652 Control Unit internal registers
bFastMode = fast;
/* If disk unit disabled return error */
if (uptr->flags & UNIT_DIS) {
sim_debug(DEBUG_EXP, dptr, "RAMAC command attempted on disabled unit %d\n", unit);
// not stated in manual: what happends if command to non existant disk?
// option 1 -> cpu halt (used this)
// option 2 -> indictor flag set
return STOP_IO;
}
/* If disk unit has no file attached return error */
if ((uptr->flags & UNIT_ATT) == 0) {
sim_debug(DEBUG_EXP, dptr, "RAMAC command attempted on unit %d that has no file attached\n", unit);
return STOP_IO;
}
// init arm operation
Arm[unit][arm].cmd = cmd; // the command to execute: can be OP_SDS, OP_RDS, OP_WDS
Arm[unit][arm].dest_disk = disk; // the destination address
Arm[unit][arm].dest_track = track;
sim_debug(DEBUG_CMD, dptr, "RAMAC unit %d, arm %d: %s on disk %d, track %d started\n",
unit, arm,
(cmd == OP_SDS) ? "SEEK" : (cmd == OP_RDS) ? "READ" : "WRITE",
Arm[unit][arm].dest_disk, Arm[unit][arm].dest_track);
if (bFastMode) {
time = 0; // no movement sequence. Just go to destination pos inmediatelly and exec command
Arm[unit][arm].InitTime = -1;
} else {
time = msec_to_wordtime(UPDATE_RAMAC); // sampling disk arm movement sequence each 10 msec
Arm[unit][arm].InitTime = GlobalWordTimeCount; // when the movement sequence starts (in word time counts)
// calculate the movement seqnece
dsk_set_mov_seq(unit,arm);
}
// schedule command execution
sim_cancel(uptr);
sim_activate(uptr, time);
return SCPE_OK_INPROGRESS;
}
/* Handle processing of disk requests. */
t_stat dsk_srv(UNIT * uptr)
{
DEVICE *dptr = find_dev_from_unit(uptr);
int unit = (uptr - dptr->units);
int time, msec, arm, cmd, nseq;
t_int64 InitTime;
int bSequenceInProgress=0;
int bFastMode;
t_stat r;
// init IBM 652 Control Unit internal registers
bFastMode = 0;
// update arm movement for this unit
for (arm=0;arm<3;arm++) {
cmd = Arm[unit][arm].cmd;
if (cmd == 0) continue; // RAMAC arm for this disk unit is stoped (=ready).
// continue to Process next arm of this unit
// arm in movement (=busy)
// calc time in msec elapsed from start of comand execution
InitTime=Arm[unit][arm].InitTime;
if (InitTime<0) {
bFastMode=1;
} else {
time=msec_elapsed(Arm[unit][arm].InitTime);
// examine sequence of arm movements to determine what is the current position
// or arm at this point of time
nseq=0;
for(;;) {
msec=Arm[unit][arm].seq[nseq].msec;
if (msec==0) break; // exit beacuse end of sequence
time=time-msec;
if (time<0) break; // exit beacuse we are at this point of sequence
nseq++;
}
if (time <0) {
// sequence not finisehd: set current arm pos
Arm[unit][arm].current_disk=Arm[unit][arm].seq[nseq].disk;
Arm[unit][arm].current_track=Arm[unit][arm].seq[nseq].track;
bSequenceInProgress=1; // there is an arm in movement
// arm not arrived to its destination yet. contiinue proceed with next arm
sim_debug(DEBUG_CMD, dptr, "RAMAC unit %d, arm %d: now at disk %d, track %d\n",
unit, arm,
Arm[unit][arm].current_disk, Arm[unit][arm].current_track);
continue;
}
}
// arm arrived to its destination position
Arm[unit][arm].current_disk=Arm[unit][arm].dest_disk;
Arm[unit][arm].current_track=Arm[unit][arm].dest_track;
// execute command
sim_debug(DEBUG_DETAIL, &cpu_dev, "... RAMAC unit %d, arm %d: %s on disk %d, track %d start execution \n",
unit, arm,
(cmd == OP_SDS) ? "SEEK" : (cmd == OP_RDS) ? "READ" : "WRITE",
Arm[unit][arm].dest_disk, Arm[unit][arm].dest_track);
r = dsk_operation(cmd, unit, arm, Arm[unit][arm].dest_disk, Arm[unit][arm].dest_track);
if (r != SCPE_OK) return STOP_IO;
// cmd execution finished, can free IAS interlock
sim_debug(DEBUG_DETAIL, &cpu_dev, "... RAMAC unit %d, arm %d: %s on disk %d, track %d finished\n",
unit, arm,
(cmd == OP_SDS) ? "SEEK" : (cmd == OP_RDS) ? "READ" : "WRITE",
Arm[unit][arm].dest_disk, Arm[unit][arm].dest_track);
if (((cmd==OP_RDS) || (cmd==OP_WDS)) && (InterLockCount[IL_IAS])) {
// remove IAS Interlock
InterLockCount[IL_IAS] = 0;
sim_debug(DEBUG_CMD, dptr, "RAMAC unit %d, arm %d: free IAS interlock\n", unit, arm);
}
// set arm as ready, so it can accept new commands
Arm[unit][arm].cmd = 0;
sim_debug(DEBUG_CMD, dptr, "RAMAC unit %d, arm %d READY\n", unit, arm);
}
// if there is any arm in movement, re-schedulle event
sim_cancel(uptr);
if (bSequenceInProgress) {
if (bFastMode) {
time = 0; // no movement sequence. Just go to destination pos inmediatelly and exec command
} else {
time = msec_to_wordtime(UPDATE_RAMAC); // sampling disk arm movement sequence each 10 msec
}
sim_activate(uptr, time);
}
return SCPE_OK;
}
void dsk_ini(UNIT * uptr, t_bool f)
{
DEVICE *dptr = find_dev_from_unit(uptr);
int unit = (uptr - dptr->units);
memset(&Arm[unit], 0, sizeof(Arm[unit])); // zeroes arm info for this unit
}
t_stat dsk_reset(DEVICE * dptr)
{
int i;
for (i = 0; i < 4; i++) {
dsk_ini(&dsk_unit[i], 0);
}
return SCPE_OK;
}
t_stat dsk_attach(UNIT * uptr, CONST char *file)
{
DEVICE *dptr = find_dev_from_unit(uptr);
int unit = (uptr - dptr->units);
t_stat r;
int flen;
if ((r = attach_unit(uptr, file)) != SCPE_OK) return r;
flen=sim_fsize(uptr->fileref);
if ((flen > 0) && (flen != DISK_SIZE * 100)) {
sim_messagef (SCPE_IERR, "Invalid RAMAC Unit file size\n");
detach_unit (uptr);
}
dsk_ini(uptr, 0);
return SCPE_OK;
}
t_stat dsk_detach(UNIT * uptr)
{
sim_cancel(uptr); // cancel any pending command
dsk_ini(uptr, 0);
return detach_unit (uptr); /* detach unit */
}
t_stat
dsk_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr)
{
fprintf (st, "%s\n\n", dsk_description(dptr));
fprintf (st, "RAMAC Magnetic storage disk.\n\n");
fprint_set_help(st, dptr);
fprint_show_help(st, dptr);
return SCPE_OK;
}
const char *
dsk_description(DEVICE *dptr)
{
return "IBM 355 RAMAC Disk Storage Unit";
}