/* * BESM-6 magnetic tape device (formatted) * * Copyright (c) 2009, Serge Vakulenko * Copyright (c) 2009-2020, Leonid Broukhis * * 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 * SERGE VAKULENKO OR LEONID BROUKHIS 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. * Except as contained in this notice, the name of Leonid Broukhis or * Serge Vakulenko shall not be used in advertising or otherwise to promote * the sale, use or other dealings in this Software without prior written * authorization from Leonid Broukhis and Serge Vakulenko. */ #include "besm6_defs.h" #include #include "sim_tape.h" /* * I/O command bits */ #define MG_BLOCK 0740000000 /* RAM block number - 27-24 рр */ #define MG_READ_SYSDATA 004000000 /* control words only */ #define MG_READ 000400000 /* reading to RAM flag */ #define MG_PAGE 000370000 /* номер страницы памяти */ #define MG_UNIT 000001600 /* номер устройства */ /* * Tape movement bits */ #define MG_CLEARINTR 040000000 #define MG_BACK 000000002 /* 0 - forward, 1 - backward */ #define MG_MOVE 000000001 /* start moving the tape */ #define MG_OFFLINE (1<<8) /* 0 - online, 1 - offline */ #define MG_READONLY (1<<16) /* 0 - r/w, 1 - r/o */ #define MG_MOVING 1 /* 0 - stopped, 1 - moving */ /* * Параметры обмена с внешним устройством. */ typedef struct { int op; /* Условное слово обмена */ int dev; /* Номер устройства, 0..7 */ int memory; /* Начальный адрес памяти */ int format; /* Флаг разметки */ int last_moving; /* Last unit on which movement started */ int status; /* Регистр состояния */ t_value mask_done, mask_free; /* Маска готовности для ГРП */ int mask_fail; /* Маска ошибки обмена */ t_value *sysdata; /* Буфер системных данных */ } KMT; static KMT controller [4]; /* 4 channels, 8 tape devices on each */ int mg_fail; /* Маска ошибок по направлениям */ t_stat mg_event (UNIT *u); #define MG_SIZE 0 #define MG_TOTBLK 02010 #define MG_IO_DELAY (200*MSEC) #define MG_MOVE_DELAY (100*MSEC) #define MG_GAP_DELAY (10*MSEC) // Formatting is allowed only on channel 6 (controller 3) #define FMT_CTLR 3 /* * MG data structures * * mg_dev DISK device descriptor * mg_unit DISK unit descriptor * mg_reg DISK register list */ UNIT mg_unit [32] = { { UDATA (mg_event, UNIT_ATTABLE+UNIT_ROABLE, MG_SIZE) }, { UDATA (mg_event, UNIT_ATTABLE+UNIT_ROABLE, MG_SIZE) }, { UDATA (mg_event, UNIT_ATTABLE+UNIT_ROABLE, MG_SIZE) }, { UDATA (mg_event, UNIT_ATTABLE+UNIT_ROABLE, MG_SIZE) }, { UDATA (mg_event, UNIT_ATTABLE+UNIT_ROABLE, MG_SIZE) }, { UDATA (mg_event, UNIT_ATTABLE+UNIT_ROABLE, MG_SIZE) }, { UDATA (mg_event, UNIT_ATTABLE+UNIT_ROABLE, MG_SIZE) }, { UDATA (mg_event, UNIT_ATTABLE+UNIT_ROABLE, MG_SIZE) }, { UDATA (mg_event, UNIT_ATTABLE+UNIT_ROABLE, MG_SIZE) }, { UDATA (mg_event, UNIT_ATTABLE+UNIT_ROABLE, MG_SIZE) }, { UDATA (mg_event, UNIT_ATTABLE+UNIT_ROABLE, MG_SIZE) }, { UDATA (mg_event, UNIT_ATTABLE+UNIT_ROABLE, MG_SIZE) }, { UDATA (mg_event, UNIT_ATTABLE+UNIT_ROABLE, MG_SIZE) }, { UDATA (mg_event, UNIT_ATTABLE+UNIT_ROABLE, MG_SIZE) }, { UDATA (mg_event, UNIT_ATTABLE+UNIT_ROABLE, MG_SIZE) }, { UDATA (mg_event, UNIT_ATTABLE+UNIT_ROABLE, MG_SIZE) }, { UDATA (mg_event, UNIT_ATTABLE+UNIT_ROABLE, MG_SIZE) }, { UDATA (mg_event, UNIT_ATTABLE+UNIT_ROABLE, MG_SIZE) }, { UDATA (mg_event, UNIT_ATTABLE+UNIT_ROABLE, MG_SIZE) }, { UDATA (mg_event, UNIT_ATTABLE+UNIT_ROABLE, MG_SIZE) }, { UDATA (mg_event, UNIT_ATTABLE+UNIT_ROABLE, MG_SIZE) }, { UDATA (mg_event, UNIT_ATTABLE+UNIT_ROABLE, MG_SIZE) }, { UDATA (mg_event, UNIT_ATTABLE+UNIT_ROABLE, MG_SIZE) }, { UDATA (mg_event, UNIT_ATTABLE+UNIT_ROABLE, MG_SIZE) }, { UDATA (mg_event, UNIT_ATTABLE+UNIT_ROABLE, MG_SIZE) }, { UDATA (mg_event, UNIT_ATTABLE+UNIT_ROABLE, MG_SIZE) }, { UDATA (mg_event, UNIT_ATTABLE+UNIT_ROABLE, MG_SIZE) }, { UDATA (mg_event, UNIT_ATTABLE+UNIT_ROABLE, MG_SIZE) }, { UDATA (mg_event, UNIT_ATTABLE+UNIT_ROABLE, MG_SIZE) }, { UDATA (mg_event, UNIT_ATTABLE+UNIT_ROABLE, MG_SIZE) }, { UDATA (mg_event, UNIT_ATTABLE+UNIT_ROABLE, MG_SIZE) }, { UDATA (mg_event, UNIT_ATTABLE+UNIT_ROABLE, MG_SIZE) }, }; #define in_io u3 #define cmd u4 REG mg_reg[] = { { ORDATA (КУС_0, controller[0].op, 24) }, { ORDATA (УСТР_0, controller[0].dev, 3) }, { ORDATA (МОЗУ_0, controller[0].memory, 20) }, { ORDATA (РС_0, controller[0].status, 24) }, { 0 }, { ORDATA (КУС_1, controller[1].op, 24) }, { ORDATA (УСТР_1, controller[1].dev, 3) }, { ORDATA (МОЗУ_1, controller[1].memory, 20) }, { ORDATA (РС_1, controller[1].status, 24) }, { 0 }, { ORDATA (КУС_2, controller[2].op, 24) }, { ORDATA (УСТР_2, controller[2].dev, 3) }, { ORDATA (МОЗУ_2, controller[2].memory, 20) }, { ORDATA (РС_2, controller[2].status, 24) }, { 0 }, { ORDATA (КУС_3, controller[3].op, 24) }, { ORDATA (УСТР_3, controller[3].dev, 3) }, { ORDATA (МОЗУ_3, controller[3].memory, 20) }, { ORDATA (РС_3, controller[3].status, 24) }, { ORDATA (ОШ, mg_fail, 6) }, { 0 } }; MTAB mg_mod[] = { { 0 } }; t_stat mg_reset (DEVICE *dptr); t_stat mg_attach (UNIT *uptr, CONST char *cptr); t_stat mg_detach (UNIT *uptr); DEVICE mg_dev[4] = { { "MG3", mg_unit, mg_reg, mg_mod, 8, 8, 21, 1, 8, 50, NULL, NULL, &mg_reset, NULL, &mg_attach, &mg_detach, NULL, DEV_DISABLE | DEV_DEBUG | DEV_TAPE }, { "MG4", mg_unit + 8, mg_reg + 5, mg_mod, 8, 8, 21, 1, 8, 50, NULL, NULL, &mg_reset, NULL, &mg_attach, &mg_detach, NULL, DEV_DISABLE | DEV_DEBUG | DEV_TAPE }, { "MG5", mg_unit + 16, mg_reg + 10, mg_mod, 8, 8, 21, 1, 8, 50, NULL, NULL, &mg_reset, NULL, &mg_attach, &mg_detach, NULL, DEV_DISABLE | DEV_DEBUG | DEV_TAPE }, { "MG6", mg_unit + 24, mg_reg + 15, mg_mod, 8, 8, 21, 1, 8, 50, NULL, NULL, &mg_reset, NULL, &mg_attach, &mg_detach, NULL, DEV_DISABLE | DEV_DEBUG | DEV_TAPE }, }; /* * Определение контроллера по устройству. */ static KMT *unit_to_ctlr (UNIT *u) { return &controller[(u - mg_unit) >> 3]; } /* * Reset routine */ t_stat mg_reset (DEVICE *dptr) { int i; int ctlr = dptr - mg_dev; KMT *c = &controller[ctlr]; memset (c, 0, sizeof (*c)); /* * The areas starting from words 030 and 040 are used for * disks; the remaining locations are shared by two channels each. */ c->sysdata = &memory [ctlr <= 1 ? 050 : 060]; /* * The "end of tape movement" interrupts are not used by the disks * and remain as per the initial spec. */ c->mask_done = GRP_CHAN3_DONE >> ctlr; /* * The "end of I/O" interrupts go to channel 5 for all * channels except the 6th, which is the only channel used for * formatting tapes, requiring better responsiveness. */ c->mask_free = ctlr == FMT_CTLR ? GRP_CHAN6_FREE : GRP_CHAN5_FREE; /* * Error masks follow the I/O interrupt scheme. */ c->mask_fail = ctlr == FMT_CTLR ? 02 : 04; c->status = BITS(8) << 8; /* r/w, offline, not moving */ c->last_moving = -1; /* used only by the FMT_CTLR */ c->format = 0; for (i=0; i<8; ++i) { if (mg_unit[ctlr*8+i].flags & UNIT_ATT) { c->status &= ~(MG_OFFLINE << i); if (mg_unit[ctlr*8+i].flags & UNIT_RO) { c->status |= MG_READONLY << i; } } mg_unit[ctlr*8+i].dptr = dptr; mg_unit[ctlr*8+i].in_io = 0; sim_cancel (&mg_unit[ctlr*8+i]); } return SCPE_OK; } t_stat mg_attach (UNIT *u, CONST char *cptr) { t_stat s; int32 saved_switches = sim_switches; int num = (u - mg_unit) & 7; int ctrl = (u - mg_unit) / 8; sim_switches |= SWMASK ('E'); while (1) { s = sim_tape_attach (u, cptr); if ((s == SCPE_OK) && (sim_switches & SWMASK ('N'))) { t_value fullzone[8+1024]; t_value * control = fullzone; /* block (zone) number, key, userid, checksum */ t_value * zone = fullzone + 8; t_value funit = u - mg_unit + 030; int tapeno, blkno, word; char *filenamepart = NULL; char *pos; /* Using the rightmost sequence of digits within the filename * provided in the command line as a volume number, * e.g. "/var/tmp/besm6/2052.bin" -> 2052 */ filenamepart = sim_filepath_parts (u->filename, "n"); pos = filenamepart + strlen(filenamepart); while (pos > filenamepart && !isdigit(*--pos)); while (pos > filenamepart && isdigit(*pos)) --pos; if (!isdigit(*pos)) ++pos; tapeno = strtoul (pos, NULL, 10); free (filenamepart); if (tapeno == 0 || tapeno >= 2048) { if (tapeno == 0) s = sim_messagef (SCPE_ARG, "%s: filename must contain volume number 1..2047\n", sim_uname(u)); else s = sim_messagef (SCPE_ARG, "%s: tape volume %d from filename %s invalid (must be 1..2047)\n", sim_uname (u), tapeno, cptr); filenamepart = strdup (u->filename); sim_tape_detach (u); remove (filenamepart); free (filenamepart); return s; /* not formatting */ } sim_messagef (SCPE_OK, "%s: formatting tape volume %d\n", sim_uname (u), tapeno); control[0] = SET_PARITY(funit << 42 | (memory[0221] & 0377774000000LL), PARITY_NUMBER); control[1] = SET_PARITY(0x987654321000LL, PARITY_NUMBER); /* task ID */ control[2] = SET_PARITY((t_value)tapeno << 30 | tapeno, PARITY_NUMBER); control[4] = SET_PARITY(12345, PARITY_NUMBER); /* time */ control[5] = SET_PARITY(0, PARITY_NUMBER); /* last word */ control[7] = SET_PARITY(0, PARITY_NUMBER); /* checksum */ for (word = 0; word < 02000; ++word) { zone[word] = SET_PARITY(0, PARITY_NUMBER); } for (blkno = 0; blkno < MG_TOTBLK; ++blkno) { int zno = blkno / 2; control[3] = SET_PARITY(070707LL << 24 | zno << 13 | blkno, PARITY_NUMBER); control[6] = control[3]; sim_tape_wrrecf(u, (uint8*)fullzone, sizeof(fullzone)); // sim_tape_wrgap(u, 20); } sim_tape_wrtmk(u); sim_tape_wrtmk(u); sim_tape_rewind(u); break; } if (s == SCPE_OK || (saved_switches & SWMASK ('E')) || (sim_switches & SWMASK('N'))) break; sim_switches |= SWMASK ('N'); } if (sim_switches & SWMASK ('R')) controller[ctrl].status |= MG_READONLY << num; else controller[ctrl].status &= ~(MG_READONLY << num); /* ready */ controller[ctrl].status &= ~(MG_OFFLINE << num); GRP |= controller[ctrl].mask_free; return SCPE_OK; } t_stat mg_detach (UNIT *u) { /* TODO: сброс бита ГРП готовности направления при отключении последнего устройства. */ int num = (u - mg_unit) & 7; int ctrl = (u - mg_unit) / 8; /* Set RO, not ready */ controller[ctrl].status |= (1 << (16 + num)); controller[ctrl].status |= (1 << (8 + num)); return sim_tape_detach (u); } /* * Отладочная печать массива данных обмена. */ static void log_data (t_value *data, int nwords) { int i; t_value val; for (i=0; i> 36) & 07777, (int) (val >> 24) & 07777, (int) (val >> 12) & 07777, (int) val & 07777); if ((i & 3) == 3) fprintf (sim_log, "\n"); } if ((i & 3) != 0) fprintf (sim_log, "\n"); } /* * Writing to a tape. */ void mg_write (UNIT *u) { KMT *c = unit_to_ctlr (u); int unit = u - mg_unit; int ret; t_value fullzone[8+1024]; int page = (u->cmd & MG_PAGE) >> 2 | (u->cmd & MG_BLOCK) >> 8; if (u->dptr->dctrl) sim_printf ("::: writing %s mem %05o\n", sim_uname(u), page); memcpy(fullzone, c->sysdata, 8*sizeof(t_value)); memcpy(fullzone+8, &memory[page], 1024*sizeof(t_value)); ret = sim_tape_wrrecf (u, (uint8*) fullzone, sizeof(fullzone)); if (ret != MTSE_OK) { mg_fail |= c->mask_fail; } } /* * Controlling formatting mode: * 0 - disable, 2 - create gap, 3 - create synchrotrack */ void mg_format (uint32 op) { KMT *c = &controller[FMT_CTLR]; int prev = c->format; c->format = op & 3; switch (op & 3) { case 0: if (prev != 0 && c->last_moving != -1) { sim_printf("Formatting off on MG6%d\n", c->last_moving); } break; case 1: sim_printf("Formatting mode 1 does not exist\n"); break; case 2: // When mode 2 (erasure) is enabled, if the tape is not yet moving, // nothing happens; if the tape is already moving, the movement ceases // to be self-sustaining; the runoff is 50 ms if (c->last_moving != -1) { int num = c->last_moving; UNIT * u = mg_unit + 8*FMT_CTLR + num; sim_printf("Formatting mode 2\n"); if (c->status & (MG_MOVING << num)) { sim_cancel(u); sim_activate(u, MG_GAP_DELAY); sim_printf("Block runoff on MG6%d\n", c->last_moving); } } break; case 3: sim_printf("Formatting mode 3\n"); // A tape must already be moving if (c->last_moving == -1) { sim_printf("Enabling synchrotrack on a stationary tape?\n"); } else { int num = c->last_moving; UNIT * u = mg_unit + 8*FMT_CTLR + num; if (c->status & (MG_MOVING << num)) { t_value fullzone[8+1024]; sim_cancel(u); u->in_io = 0; sim_printf("(in_io = 0) Extending block on %s\n", sim_uname(u)); // Writing the synchrotrack for a zone is like writing a zone of arbitrary values sim_tape_wrrecf(u, (uint8*) fullzone, sizeof(fullzone)); // Writing the synchrotrack is self-sustaining, no end event requested. sim_printf("Formatting block on %s\n", sim_uname(u)); } } } } /* * Reading from a tape. */ void mg_read (UNIT *u) { KMT *c = unit_to_ctlr (u); t_mtrlnt len; t_value fullzone[8+1024]; int ret; int unit = (u - mg_unit) & 7; int page = (u->cmd & MG_PAGE) >> 2 | (u->cmd & MG_BLOCK) >> 8; if (u->dptr->dctrl) sim_printf ((u->cmd & MG_READ_SYSDATA) ? "::: reading %s control words\n" : "::: reading %s mem %05o\n", sim_uname(u), page); ret = sim_tape_rdrecf (u, (uint8*) fullzone, &len, sizeof(t_value)*(8+1024)); if (ret != MTSE_OK || len != sizeof(t_value)*(8+1024)) { /* Bad tape format */ if (u->dptr->dctrl) sim_printf("%s: Bad read: ret %d len %d\n", sim_uname(u), ret, len); mg_fail |= c->mask_fail; return; } memcpy(c->sysdata, fullzone, 8*sizeof(t_value)); if (! (u->cmd & MG_READ_SYSDATA)) { memcpy(&memory [page], fullzone+8, 8*1024); } } /* * Specifying the operation (read/write) and the memory location. * The actual I/O is initiated by a move command. * The I/O setting is taken by two controllers. * Given 2 affects 0 and 1. * Given 3 affects 2 and 3. */ void mg_io (int ctlr, uint32 op) { int i; int dev = (op >> 7) & 7; for (i = (ctlr & 1) * 2; i <= (ctlr & 1) * 2 + 1; ++i) { KMT *c = &controller [i]; c->op = op; c->dev = dev; c->memory = (op & MG_PAGE) >> 2 | (op & MG_BLOCK) >> 8; } if (mg_dev[ctlr].dctrl) sim_printf ("::: MG%o/%o: %s %s %08o\n", (ctlr&1)*16 + 030 + dev, (ctlr&1)*16 + 040 + dev, (op & MG_READ) ? "read" : "write", (op & MG_READ_SYSDATA) ? "sysdata" : "", op); /* * Error flags and interrupts, however, use the given controller number. */ mg_fail &= ~controller[ctlr].mask_fail; /* Clearing the main interrupt register */ GRP &= ~controller[ctlr].mask_free; } /* * Moving the tape. */ void mg_ctl (int unit, uint32 op) { UNIT *u = &mg_unit [unit]; KMT *c = unit_to_ctlr (u); int num = unit & 7; int move, back; if (op == MG_CLEARINTR) { // Only the controller number matters, unit is not used. GRP &= ~c->mask_done; return; } if (op & MG_CLEARINTR) { sim_printf("Clearing interrupts AND attempting to do something else (%08o)?\n", op); longjmp (cpu_halt, SCPE_IOERR); } if ((u->dptr->flags & DEV_DIS) || ! (u->flags & UNIT_ATT)) { /* Device not attached. */ if (op != 0 && u->dptr->dctrl) sim_printf("::: %s: unattached, but control %08o issued\n", sim_uname(u), op); mg_fail |= c->mask_fail; return; } mg_fail &= ~c->mask_fail; c->last_moving = num; if (c->format) { c->status |= MG_MOVING << num; if (c->format == 3) { // Must not be happening: starting from the stationary position // while writing the synchrotrack is bad. sim_printf("Accelerating while writing the synchrotrack is a bad idea.\n"); // Moving with synchrotrack is self-sustaining, no activation needed. } else if (c->format == 2) { // Erasing, will sustain for about 50 ms sim_printf("Erasing %s\n", sim_uname(u)); sim_activate (u, MG_GAP_DELAY); } else if (c->format == 1) { if (u->dptr->dctrl) sim_printf("WHAT IS FORMAT 1?\n"); } return; } move = op & MG_MOVE; back = op & MG_BACK; if (num == c->dev && move && !back) { /* Reading or writing */ if (!(c->op & MG_READ) && u->flags & UNIT_RO) { /* Read only. */ mg_fail |= c->mask_fail; return; } u->cmd = c->op; u->in_io = 1; if (u->dptr->dctrl) sim_printf("::: %s: in_io = 1\n", sim_uname(u)); c->status |= MG_MOVING << num; sim_activate (u, MG_IO_DELAY); } else if (move) { t_mtrlnt len; if (back) { if (sim_tape_bot (u)) { if (u->dptr->dctrl) sim_printf("%s: at BOT, nowhere to step back\n", sim_uname(u)); sim_activate (u, MG_GAP_DELAY); } else { if (u->dptr->dctrl) sim_printf("%s: Step back\n", sim_uname(u)); sim_tape_sprecr (u, &len); sim_activate (u, MG_MOVE_DELAY); } } else { if (u->dptr->dctrl) sim_printf("%s: Step forward\n", sim_uname(u)); sim_tape_sprecf (u, &len); sim_activate (u, MG_MOVE_DELAY); } c->status |= MG_MOVING << num; } else { if (u->dptr->dctrl) sim_printf("Invalid command combination for %s: %08o\n", sim_uname(u), op); } } /* * Запрос состояния контроллера. */ int mg_state (int ctlr) { KMT *c = &controller [ctlr]; static uint32 prev[4]; if (mg_dev[ctlr].dctrl && c->status != prev[ctlr]) { char status[24]; int i; // Some tapes are online sim_printf("::: MG%02o-%02o: READONLY-ONLINE--MOVING-\n", ctlr*8+31, ctlr*8+24); for (i = 0; i < 8; ++i) { status[23-i] = c->status & (MG_MOVING << i) ? '0'+i : ' '; status[15-i] = c->status & (MG_OFFLINE << i) ? ' ': '0'+i; status[7-i] = c->status & (MG_READONLY << i) ? '0'+i : ' '; } sim_printf("::: MG%02o-%02o: %.24s\n", ctlr*8+31, ctlr*8+24, status); prev[ctlr] = c->status; } return c->status; } /* * End of I/O, sending an interrupt. */ t_stat mg_event (UNIT *u) { KMT *c = unit_to_ctlr (u); int unit = u - mg_unit; int num = unit & 7; if (u->dptr->dctrl) sim_printf("::: %s: event\n", sim_uname(u)); if (u->in_io) { if (u->cmd & MG_READ) { mg_read(u); } else { mg_write(u); } GRP |= c->mask_free; u->in_io = 0; sim_activate (u, MG_GAP_DELAY); if (u->dptr->dctrl) sim_printf("::: %s: (in_io = 0) end of I/O event\n", sim_uname(u)); } else { c->status &= ~(MG_MOVING << num); c->status &= ~(MG_OFFLINE << num); GRP |= c->mask_done; if (u->dptr->dctrl) sim_printf("::: %s: stopping event\n", sim_uname(u)); } return SCPE_OK; } /* * Опрос ошибок обмена командой 033 4035. */ int mg_errors () { #if 0 if (mg_dev[0].dctrl) sim_printf ("::: КМД: опрос шкалы ошибок = %04o\n", mg_fail); #endif return mg_fail; }