- Integration with updated sim_card API - Addition of MT (Mag Tape) device - Addition of DSK (Disk) device - Build time simulator test
494 lines
20 KiB
C
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";
|
|
}
|
|
|
|
|