/* i7090_chron.c: IBM 7090 Chrono clock on MT drive. | |
Copyright (c) 2005-2016, Richard Cornwell | |
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 | |
RICHARD CORNWELL 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 "i7000_defs.h" | |
#include <time.h> | |
#ifdef NUM_DEVS_CHRON | |
#define BUFFSIZE (12) | |
#define UNIT_MT(x) UNIT_DISABLE | UNIT_ROABLE | \ | |
UNIT_S_CHAN(x) | |
/* in u3 is device address */ | |
/* in u4 is current buffer position */ | |
/* in u5 */ | |
#define MT_RDS 1 | |
#define MT_RDSB 2 | |
#define MT_SKIP 11 /* Do skip to end of record */ | |
#define MT_CMDMSK 000017 /* Command being run */ | |
#define MT_RDY 000020 /* Device is ready for command */ | |
#define MT_IDLE 000040 /* Tape still in motion */ | |
#define MT_EOR 000200 /* Hit end of record */ | |
#define MT_ERR 000400 /* Device recieved error */ | |
#define MT_BOT 001000 /* Unit at begining of tape */ | |
#define MT_EOT 002000 /* Unit at end of tape */ | |
uint32 chron_cmd(UNIT *, uint16, uint16); | |
t_stat chron_srv(UNIT *); | |
t_stat chron_reset(DEVICE *); | |
t_stat set_addr(UNIT * uptr, int32 val, CONST char *cptr, void *desc); | |
t_stat get_addr(FILE * st, UNIT *uptr, int32 v, CONST void *desc); | |
t_stat chron_help(FILE *st, DEVICE *dptr, UNIT *uptr, | |
int32 flags, const char *ctxt); | |
const char *chron_description (DEVICE *dptr); | |
/* One buffer per channel */ | |
uint8 chron_buffer[BUFFSIZE]; | |
UNIT chron_unit[] = { | |
/* Controller 1 */ | |
{UDATA(&chron_srv, UNIT_MT(1) | UNIT_DIS, 0), 10}, /* 0 */ | |
}; | |
MTAB chron_mod[] = { | |
{MTAB_XTD | MTAB_VUN | MTAB_VALR, 0, "UNIT", "UNIT", &set_addr, &get_addr, | |
NULL, "Chronoclock unit number"}, | |
{MTAB_XTD | MTAB_VUN | MTAB_VALR, 0, "CHAN", "CHAN", &set_chan, &get_chan, | |
NULL, "Chronoclock channel"}, | |
{0} | |
}; | |
DEVICE chron_dev = { | |
"CHRON", chron_unit, NULL, chron_mod, | |
NUM_DEVS_CHRON, 8, 15, 1, 8, 8, | |
NULL, NULL, &chron_reset, NULL, NULL, NULL, | |
&chron_dib, DEV_DISABLE, 0, NULL, | |
NULL, NULL, &chron_help, NULL, NULL, &chron_description | |
}; | |
uint32 chron_cmd(UNIT * uptr, uint16 cmd, uint16 dev) | |
{ | |
int chan = UNIT_G_CHAN(uptr->flags); | |
int time = 30; | |
int unit = (dev & 017); | |
/* Make sure valid drive number */ | |
if (unit != uptr->u3) | |
return SCPE_NODEV; | |
if (uptr->flags & UNIT_DIS) | |
return SCPE_NODEV; | |
/* Check if drive is ready to recieve a command */ | |
if ((uptr->u5 & MT_RDY) == 0) { | |
/* Return indication if not ready and doing TRS */ | |
if (cmd == IO_TRS) { | |
return SCPE_IOERR; | |
} else | |
return SCPE_BUSY; | |
} | |
uptr->u5 &= ~(MT_CMDMSK | MT_RDY); | |
switch (cmd) { | |
case IO_RDS: | |
if (dev & 020) | |
uptr->u5 |= MT_RDSB; | |
else | |
uptr->u5 |= MT_RDS; | |
time = 100; | |
chan_set_sel(chan, 0); | |
chan_clear_status(chan); | |
uptr->u6 = 0; | |
break; | |
case IO_WRS: | |
/* Can't write to it so return error */ | |
return SCPE_IOERR; | |
case IO_BSR: /* Nop, just set us back at begining */ | |
case IO_BSF: /* Nop, just set flag and leave */ | |
chan_set(chan, CHS_BOT); | |
/* All nops, just return success */ | |
case IO_WEF: | |
case IO_REW: | |
case IO_RUN: | |
case IO_SDL: | |
case IO_SDH: | |
case IO_TRS: | |
return SCPE_OK; | |
} | |
sim_cancel(uptr); | |
sim_activate(uptr, us_to_ticks(time)); | |
return SCPE_OK; | |
} | |
/* Chronolog clock */ | |
/* Convert number (0-99) to BCD */ | |
static void | |
bcd_2d(int n, uint8 * b2) | |
{ | |
uint8 d1, d2; | |
d1 = n / 10; | |
d2 = n % 10; | |
*b2++ = d1; | |
*b2 = d2; | |
} | |
void | |
chron_read_buff(UNIT * uptr, int cmd) | |
{ | |
time_t curtim; | |
struct tm *tptr; | |
int ms; | |
uptr->u6 = 0; /* Set to no data */ | |
curtim = time(NULL); /* get time */ | |
tptr = localtime(&curtim); /* decompose */ | |
if (tptr == NULL) | |
return; /* error? */ | |
ms = sim_os_msec() % 1000; | |
ms /= 100; | |
/* Convert and fill buffer */ | |
bcd_2d(tptr->tm_mon + 1, &chron_buffer[0]); | |
bcd_2d(tptr->tm_mday, &chron_buffer[2]); | |
bcd_2d(tptr->tm_hour, &chron_buffer[4]); | |
bcd_2d(tptr->tm_min, &chron_buffer[6]); | |
bcd_2d(tptr->tm_sec, &chron_buffer[8]); | |
bcd_2d(ms, &chron_buffer[10]); | |
return; | |
} | |
t_stat chron_srv(UNIT * uptr) | |
{ | |
int chan = UNIT_G_CHAN(uptr->flags); | |
int cmd = uptr->u5 & MT_CMDMSK; | |
/* Channel has disconnected, abort current read. */ | |
if ((uptr->u5 & MT_RDY) == 0 && chan_stat(chan, DEV_DISCO)) { | |
uptr->u5 &= ~MT_CMDMSK; | |
if (cmd == MT_RDS || cmd == MT_RDSB) { | |
uptr->u6 = 0; | |
} | |
uptr->u5 |= MT_RDY; | |
chan_clear(chan, DEV_WEOR|DEV_SEL); | |
return SCPE_OK; | |
} | |
switch (uptr->u5 & MT_CMDMSK) { | |
case 0: /* No command, stop tape */ | |
uptr->u5 |= MT_RDY; /* Ready since command is done */ | |
break; | |
case MT_SKIP: /* Record skip done, enable tape drive */ | |
sim_activate(uptr, us_to_ticks(500)); | |
break; | |
case MT_RDS: | |
case MT_RDSB: | |
if (uptr->u6 == 0) | |
chron_read_buff(uptr, cmd); | |
switch (chan_write_char(chan, &chron_buffer[uptr->u6], | |
(uptr->u6 == (BUFFSIZE-1)) ? DEV_REOR : 0)) { | |
case DATA_OK: | |
uptr->u6++; | |
sim_activate(uptr, us_to_ticks(100)); | |
break; | |
case END_RECORD: | |
case TIME_ERROR: | |
uptr->u5 &= ~MT_CMDMSK; | |
uptr->u5 |= MT_SKIP; | |
sim_activate(uptr, us_to_ticks(100)); | |
uptr->u6 = 0; /* Force read next record */ | |
break; | |
} | |
} | |
return SCPE_OK; | |
} | |
t_stat | |
chron_reset(DEVICE * dptr) | |
{ | |
chron_unit[0].u5 = MT_RDY; | |
return SCPE_OK; | |
} | |
/* Sets the address of the chrono clock */ | |
t_stat | |
set_addr(UNIT * uptr, int32 val, CONST char *cptr, void *desc) | |
{ | |
int i; | |
if (cptr == NULL) | |
return SCPE_ARG; | |
if (uptr == NULL) | |
return SCPE_IERR; | |
if (uptr->flags & UNIT_ATT) | |
return SCPE_ALATT; | |
i = 0; | |
while (*cptr != '\0') { | |
if (*cptr < '0' || *cptr > '9') | |
return SCPE_ARG; | |
i = (i * 10) + (*cptr++) - '0'; | |
} | |
if (i < 0 || i > 10) | |
return SCPE_ARG; | |
uptr->u3 = i; | |
return SCPE_OK; | |
} | |
t_stat | |
get_addr(FILE * st, UNIT * uptr, int32 v, CONST void *desc) | |
{ | |
if (uptr == NULL) | |
return SCPE_IERR; | |
fprintf(st, "Unit=%d", uptr->u3); | |
return SCPE_OK; | |
} | |
t_stat | |
chron_help (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr) | |
{ | |
fprintf (st, "Chronoclock\n\n"); | |
fprintf (st, "The Chronoclock replaces one of your tape drives, and is\n"); | |
fprintf (st, "for CTSS operation\n\n"); | |
fprintf (st, " sim> SET %s ENABLE to enable chronoclock\n", dptr->name); | |
fprintf (st, " sim> SET %s UNIT=# sets unit to override [0-9]\n\n", dptr->name); | |
help_set_chan_type(st, dptr, "Chrono clock"); | |
fprintf (st, "You must disable the corrosponding tape drive in order for\n"); | |
fprintf (st, "the chronoclook to be seen. The chronoclock replaces one of\n"); | |
fprintf (st, "your tape drives, and by reading the tape drive, it will\n"); | |
fprintf (st, "return a short record with the current date and time, no year\n"); | |
fprintf (st, "is returned\n"); | |
fprint_set_help (st, dptr) ; | |
fprint_show_help (st, dptr) ; | |
return SCPE_OK; | |
} | |
const char * | |
chron_description (DEVICE *dptr) | |
{ | |
return "Chronoclock"; | |
} | |
#endif |