/* * BESM-6 magnetic disk device * * Copyright (c) 2009, Serge Vakulenko * Copyright (c) 2009, 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 /* * Управляющее слово обмена с магнитным диском. */ #define DISK_BLOCK 0740000000 /* номер блока памяти - 27-24 рр */ #define DISK_READ_SYSDATA 004000000 /* считывание только служебных слов */ #define DISK_PAGE_MODE 001000000 /* обмен целой страницей */ #define DISK_READ 000400000 /* чтение с диска в память */ #define DISK_PAGE 000370000 /* номер страницы памяти */ #define DISK_HALFPAGE 000004000 /* выбор половины листа */ #define DISK_UNIT 000001600 /* номер устройства */ #define DISK_HALFZONE 000000001 /* выбор половины зоны */ /* * "Хороший" статус чтения/записи. * Вычислено по текстам ОС Дубна. * Диспак доволен. */ #define STATUS_GOOD 014000400 /* * Total size of a disk in blocks, including hidden blocks */ #define DISK_TOTBLK 01767 /* * Параметры обмена с внешним устройством. */ typedef struct { int op; /* Условное слово обмена */ int dev; /* Номер устройства, 0..7 */ int zone; /* Номер зоны на диске */ int track; /* Выбор половины зоны на диске */ int memory; /* Начальный адрес памяти */ int format; /* Флаг разметки */ int status; /* Регистр состояния */ t_value mask_grp; /* Маска готовности для ГРП */ int mask_fail; /* Маска ошибки обмена */ t_value *sysdata; /* Буфер системных данных */ } KMD; static KMD controller [2]; /* Две стойки КМД */ int disk_fail; /* Маска ошибок по направлениям */ t_stat disk_event (UNIT *u); /* * DISK data structures * * disk_dev DISK device descriptor * disk_unit DISK unit descriptor * disk_reg DISK register list */ UNIT disk_unit [16] = { { UDATA (disk_event, UNIT_FIX+UNIT_ATTABLE+UNIT_ROABLE, DISK_SIZE) }, { UDATA (disk_event, UNIT_FIX+UNIT_ATTABLE+UNIT_ROABLE, DISK_SIZE) }, { UDATA (disk_event, UNIT_FIX+UNIT_ATTABLE+UNIT_ROABLE, DISK_SIZE) }, { UDATA (disk_event, UNIT_FIX+UNIT_ATTABLE+UNIT_ROABLE, DISK_SIZE) }, { UDATA (disk_event, UNIT_FIX+UNIT_ATTABLE+UNIT_ROABLE, DISK_SIZE) }, { UDATA (disk_event, UNIT_FIX+UNIT_ATTABLE+UNIT_ROABLE, DISK_SIZE) }, { UDATA (disk_event, UNIT_FIX+UNIT_ATTABLE+UNIT_ROABLE, DISK_SIZE) }, { UDATA (disk_event, UNIT_FIX+UNIT_ATTABLE+UNIT_ROABLE, DISK_SIZE) }, { UDATA (disk_event, UNIT_FIX+UNIT_ATTABLE+UNIT_ROABLE, DISK_SIZE) }, { UDATA (disk_event, UNIT_FIX+UNIT_ATTABLE+UNIT_ROABLE, DISK_SIZE) }, { UDATA (disk_event, UNIT_FIX+UNIT_ATTABLE+UNIT_ROABLE, DISK_SIZE) }, { UDATA (disk_event, UNIT_FIX+UNIT_ATTABLE+UNIT_ROABLE, DISK_SIZE) }, { UDATA (disk_event, UNIT_FIX+UNIT_ATTABLE+UNIT_ROABLE, DISK_SIZE) }, { UDATA (disk_event, UNIT_FIX+UNIT_ATTABLE+UNIT_ROABLE, DISK_SIZE) }, { UDATA (disk_event, UNIT_FIX+UNIT_ATTABLE+UNIT_ROABLE, DISK_SIZE) }, { UDATA (disk_event, UNIT_FIX+UNIT_ATTABLE+UNIT_ROABLE, DISK_SIZE) }, }; REG disk_reg[] = { { ORDATA ( "КУС_0", controller[0].op, 24) }, { ORDATA ( "УСТР_0", controller[0].dev, 3) }, { ORDATA ( "ЗОНА_0", controller[0].zone, 10) }, { ORDATA ( "ДОРОЖКА_0", controller[0].track, 2) }, { ORDATA ( "МОЗУ_0", controller[0].memory, 20) }, { ORDATA ( "РС_0", controller[0].status, 24) }, { ORDATA ( "КУС_1", controller[1].op, 24) }, { ORDATA ( "УСТР_1", controller[1].dev, 3) }, { ORDATA ( "ЗОНА_1", controller[1].zone, 10) }, { ORDATA ( "ДОРОЖКА_1", controller[1].track, 2) }, { ORDATA ( "МОЗУ_1", controller[1].memory, 20) }, { ORDATA ( "РС_1", controller[1].status, 24) }, { ORDATA ( "ОШ", disk_fail, 6) }, { 0 } }; MTAB disk_mod[] = { { 0 } }; t_stat disk_reset (DEVICE *dptr); t_stat disk_attach (UNIT *uptr, CONST char *cptr); t_stat disk_detach (UNIT *uptr); DEVICE disk_dev = { "DISK", disk_unit, disk_reg, disk_mod, 16, 8, 21, 1, 8, 50, NULL, NULL, &disk_reset, NULL, &disk_attach, &disk_detach, NULL, DEV_DISABLE | DEV_DEBUG }; /* * Определение контроллера по устройству. */ static KMD *unit_to_ctlr (UNIT *u) { if (u < &disk_unit[8]) return &controller[0]; else return &controller[1]; } /* * Reset routine */ t_stat disk_reset (DEVICE *dptr) { int i; memset (&controller, 0, sizeof (controller)); controller[0].sysdata = &memory [030]; controller[1].sysdata = &memory [040]; controller[0].mask_grp = GRP_CHAN3_FREE; controller[1].mask_grp = GRP_CHAN4_FREE; controller[0].mask_fail = 020; controller[1].mask_fail = 010; for (i=0; i<16; ++i) sim_cancel (&disk_unit[i]); return SCPE_OK; } t_stat disk_attach (UNIT *u, CONST char *cptr) { t_stat s; int32 saved_switches = sim_switches; sim_switches |= SWMASK ('E'); while (1) { s = attach_unit (u, cptr); if ((s == SCPE_OK) && (sim_switches & SWMASK ('N'))) { t_value control[4]; /* block (zone) number, key, userid, checksum */ int diskno, blkno, word; const char *pos; /* Using the rightmost sequence of digits within the filename * as a volume number, e.g. "/var/tmp/besm6/2052.bin" -> 2052 */ pos = cptr + strlen(cptr); while (pos > cptr && !isdigit(*--pos)); while (pos > cptr && isdigit(*pos)) --pos; if (!isdigit(*pos)) ++pos; diskno = atoi(pos); if (diskno < 2048 || diskno > 4095) { if (diskno == 0) sim_printf ("%s: filename must contain volume number 2048..4095\n", sim_uname(u)); else sim_printf ("%s: disk volume %d from filename %s invalid (must be 2048..4095)\n", sim_uname (u), diskno, cptr); /* unlink (cptr); ??? */ return SCPE_ARG; } if (!sim_quiet && !(sim_switches & SWMASK ('Q'))) sim_printf ("%s: formatting disk volume %d\n", sim_uname (u), diskno); control[1] = SET_PARITY(0, PARITY_NUMBER); control[2] = SET_PARITY(0, PARITY_NUMBER); control[3] = SET_PARITY(0, PARITY_NUMBER); control[1] |= 01370707LL << 24; /* Magic mark */ control[1] |= diskno << 12; for (blkno = 0; blkno < DISK_TOTBLK; ++blkno) { control[0] = SET_PARITY((t_value)(2*blkno) << 36, PARITY_NUMBER); sim_fwrite(control, sizeof(t_value), 4, u->fileref); control[0] = SET_PARITY((t_value)(2*blkno+1) << 36, PARITY_NUMBER); sim_fwrite(control, sizeof(t_value), 4, u->fileref); for (word = 0; word < 02000; ++word) { sim_fwrite(control+2, sizeof(t_value), 1, u->fileref); } } return SCPE_OK; } if (s == SCPE_OK || (saved_switches & SWMASK ('E')) || (sim_switches & SWMASK('N'))) return s; sim_switches |= SWMASK ('N'); } return SCPE_OK; } t_stat disk_detach (UNIT *u) { /* TODO: сброс бита ГРП готовности направления при отключении последнего диска. */ return detach_unit (u); } t_value spread (t_value val) { int i, j; t_value res = 0; for (i = 0; i < 5; i++) for (j = 0; j < 9; j++) if (val & (1LL<<(i+j*5))) res |= 1LL << (i*9+j); return res & BITS48; } /* * Отладочная печать массива данных обмена. */ 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"); } /* * Сложение с переносом вправо. */ static unsigned sum_with_right_carry (unsigned a, unsigned b) { unsigned c; while (b) { c = a & b; a ^= b; b = c >> 1; } return a; } /* * Запись на диск. */ void disk_write (UNIT *u) { KMD *c = unit_to_ctlr (u); if (disk_dev.dctrl) besm6_debug ("::: запись МД %o зона %04o память %05o-%05o", c->dev, c->zone, c->memory, c->memory + 1023); if (fseek (u->fileref, ZONE_SIZE * c->zone * 8, SEEK_SET) == 0) { sim_fwrite (c->sysdata, 8, 8, u->fileref); sim_fwrite (&memory [c->memory], 8, 1024, u->fileref); } if (ferror (u->fileref)) longjmp (cpu_halt, SCPE_IOERR); } void disk_write_track (UNIT *u) { KMD *c = unit_to_ctlr (u); if (disk_dev.dctrl) besm6_debug ("::: запись МД %o полузона %04o.%d память %05o-%05o", c->dev, c->zone, c->track, c->memory, c->memory + 511); if (fseek (u->fileref, (ZONE_SIZE*c->zone + 4*c->track) * 8, SEEK_SET) == 0) { sim_fwrite (c->sysdata + 4*c->track, 8, 4, u->fileref); if (fseek (u->fileref, (8 + ZONE_SIZE*c->zone + 512*c->track) * 8, SEEK_SET) == 0) { sim_fwrite (&memory [c->memory], 8, 512, u->fileref); } } if (ferror (u->fileref)) longjmp (cpu_halt, SCPE_IOERR); } /* * Форматирование дорожки. */ void disk_format (UNIT *u) { KMD *c = unit_to_ctlr (u); t_value fmtbuf[5], *ptr; int i; /* По сути, эмулятору ничего делать не надо. */ if (! disk_dev.dctrl) return; /* Находим начало записываемого заголовка. */ ptr = &memory [c->memory]; while ((*ptr & BITS48) == 0) ptr++; /* Декодируем из гребенки в нормальный вид. */ for (i = 0; i < 5; i++) fmtbuf[i] = spread (ptr[i]); /* При первой попытке разметки адресный маркер начинается в старшем 5-разрядном слоге, * пропускаем первый слог. */ for (i=0; i<4; i++) fmtbuf[i] = ((fmtbuf[i] & BITS48) << 5) | ((fmtbuf[i+1] >> 40) & BITS(5)); /* Печатаем идентификатор, адрес и контрольную сумму адреса. */ besm6_debug ("::: формат МД %o полузона %04o.%d память %05o и-а-кса %010o %010o", c->dev, c->zone, c->track, c->memory, (int) (fmtbuf[0] >> 8 & BITS(30)), (int) (fmtbuf[2] >> 14 & BITS(30))); /* log_data (fmtbuf, 4); */ } /* * Чтение с диска. */ void disk_read (UNIT *u) { KMD *c = unit_to_ctlr (u); if (disk_dev.dctrl) besm6_debug ((c->op & DISK_READ_SYSDATA) ? "::: чтение МД %o зона %04o служебные слова" : "::: чтение МД %o зона %04o память %05o-%05o", c->dev, c->zone, c->memory, c->memory + 1023); if (fseek (u->fileref, ZONE_SIZE * c->zone * 8, SEEK_SET) != 0 || sim_fread (c->sysdata, 8, 8, u->fileref) != 8) { /* Чтение неинициализированного диска */ disk_fail |= c->mask_fail; return; } if (! (c->op & DISK_READ_SYSDATA) && sim_fread (&memory [c->memory], 8, 1024, u->fileref) != 1024) { /* Чтение неинициализированного диска */ disk_fail |= c->mask_fail; return; } if (ferror (u->fileref)) longjmp (cpu_halt, SCPE_IOERR); } t_value collect (t_value val) { int i, j; t_value res = 0; for (i = 0; i < 5; i++) for (j = 0; j < 9; j++) if (val & (1LL<<(i*9+j))) res |= 1LL << (i+j*5); return res & BITS48; } void disk_read_track (UNIT *u) { KMD *c = unit_to_ctlr (u); if (disk_dev.dctrl) besm6_debug ((c->op & DISK_READ_SYSDATA) ? "::: чтение МД %o полузона %04o.%d служебные слова" : "::: чтение МД %o полузона %04o.%d память %05o-%05o", c->dev, c->zone, c->track, c->memory, c->memory + 511); if (fseek (u->fileref, (ZONE_SIZE*c->zone + 4*c->track) * 8, SEEK_SET) != 0 || sim_fread (c->sysdata + 4*c->track, 8, 4, u->fileref) != 4) { /* Чтение неинициализированного диска */ disk_fail |= c->mask_fail; return; } if (! (c->op & DISK_READ_SYSDATA)) { if (fseek (u->fileref, (8 + ZONE_SIZE*c->zone + 512*c->track) * 8, SEEK_SET) != 0 || sim_fread (&memory [c->memory], 8, 512, u->fileref) != 512) { /* Чтение неинициализированного диска */ disk_fail |= c->mask_fail; return; } } if (ferror (u->fileref)) longjmp (cpu_halt, SCPE_IOERR); } /* * Чтение заголовка дорожки. */ void disk_read_header (UNIT *u) { KMD *c = unit_to_ctlr (u); t_value *sysdata = c->sysdata + 4*c->track; int iaksa, i, cyl, head; /* Адрес: номер цилиндра и головки. */ head = (c->zone << 1) + c->track; cyl = head / 10; head %= 10; iaksa = (cyl << 20) | (head << 16); /* Идентификатор дорожки замены. */ if (c->zone >= 01750) iaksa |= BBIT(30); /* Контрольная сумма адреса с переносом вправо. */ iaksa |= BITS(12) & ~sum_with_right_carry (iaksa >> 12, iaksa >> 24); /* Амиакса, 42 нуля, амиакса, много единиц. */ sysdata[0] = 07404000000000000LL | (t_value) iaksa << 8; sysdata[1] = 03740LL; sysdata[2] = 00400000000037777LL | (t_value) iaksa << 14; sysdata[3] = BITS48; if (disk_dev.dctrl) log_data (sysdata, 4); /* Кодируем гребенку. */ for (i=0; i<4; i++) sysdata[i] = SET_PARITY (collect (sysdata[i]), PARITY_NUMBER); } /* * Задание адреса памяти и длины массива для последующего обращения к диску. * Номера дисковода и дорожки будут выданы позже, командой 033 0023(0024). */ void disk_io (int ctlr, uint32 cmd) { KMD *c = &controller [ctlr]; c->op = cmd; c->format = 0; if (c->op & DISK_PAGE_MODE) { /* Обмен страницей */ c->memory = (cmd & DISK_PAGE) >> 2 | (cmd & DISK_BLOCK) >> 8; } else { /* Обмен половиной страницы (дорожкой) */ c->memory = (cmd & (DISK_PAGE | DISK_HALFPAGE)) >> 2 | (cmd & DISK_BLOCK) >> 8; } #if 0 if (disk_dev.dctrl) besm6_debug ("::: КМД %c: задание на %s %08o", ctlr + '3', (c->op & DISK_READ) ? "чтение" : "запись", cmd); #endif disk_fail &= ~c->mask_fail; /* Гасим главный регистр прерываний. */ GRP &= ~c->mask_grp; } /* * Управление диском: команда 00 033 0023(0024). */ void disk_ctl (int ctlr, uint32 cmd) { KMD *c = &controller [ctlr]; UNIT *u = &disk_unit [c->dev]; if (cmd & BBIT(12)) { /* Выдача в КМД адреса дорожки. * Здесь же выполняем обмен с диском. * Номер дисковода к этому моменту уже известен. */ if ((disk_dev.flags & DEV_DIS) || ! (u->flags & UNIT_ATT)) { /* Device not attached. */ disk_fail |= c->mask_fail; return; } c->zone = (cmd >> 1) & BITS(10); c->track = cmd & 1; #if 0 if (disk_dev.dctrl) besm6_debug ("::: КМД %c: выдача адреса дорожки %04o.%d", ctlr + '3', c->zone, c->track); #endif disk_fail &= ~c->mask_fail; if (c->op & DISK_READ) { if (c->op & DISK_PAGE_MODE) disk_read (u); else disk_read_track (u); } else { if (u->flags & UNIT_RO) { /* Read only. */ /*longjmp (cpu_halt, SCPE_RO);*/ disk_fail |= c->mask_fail; return; } if (c->format) disk_format (u); else if (c->op & DISK_PAGE_MODE) disk_write (u); else disk_write_track (u); } /* Ждём события от устройства. */ sim_activate (u, 20*USEC); /* Ускорим для отладки. */ } else if (cmd & BBIT(11)) { /* Выбора номера устройства и занесение в регистр маски КМД. * Бит 8 - устройство 0, бит 7 - устройство 1, ... бит 1 - устройство 7. * Также установлен бит 9 - что он означает? */ if (cmd & BBIT(8)) c->dev = 7; else if (cmd & BBIT(7)) c->dev = 6; else if (cmd & BBIT(6)) c->dev = 5; else if (cmd & BBIT(5)) c->dev = 4; else if (cmd & BBIT(4)) c->dev = 3; else if (cmd & BBIT(3)) c->dev = 2; else if (cmd & BBIT(2)) c->dev = 1; else if (cmd & BBIT(1)) c->dev = 0; else { /* Неверная маска выбора устройства. */ c->dev = -1; return; } c->dev += ctlr << 3; u = &disk_unit[c->dev]; #if 0 if (disk_dev.dctrl) besm6_debug ("::: КМД %c: выбор устройства %d", ctlr + '3', c->dev); #endif if ((disk_dev.flags & DEV_DIS) || ! (u->flags & UNIT_ATT)) { /* Device not attached. */ disk_fail |= c->mask_fail; GRP &= ~c->mask_grp; } GRP |= c->mask_grp; } else if (cmd & BBIT(9)) { /* Проверка прерывания от КМД? */ #if 0 if (disk_dev.dctrl) besm6_debug ("::: КМД %c: проверка готовности", ctlr + '3'); #endif GRP |= c->mask_grp; } else { /* Команда, выдаваемая в КМД. */ switch (cmd & 077) { case 000: /* диспак выдаёт эту команду один раз в начале загрузки */ #if 0 if (disk_dev.dctrl) besm6_debug ("::: КМД %c: недокументированная команда 00", ctlr + '3'); #endif break; case 001: /* сброс на 0 цилиндр */ #if 0 if (disk_dev.dctrl) besm6_debug ("::: КМД %c: сброс на 0 цилиндр", ctlr + '3'); #endif break; case 002: /* подвод */ if (disk_dev.dctrl) besm6_debug ("::: КМД %c: подвод", ctlr + '3'); break; case 003: /* чтение (НСМД-МОЗУ) */ case 043: /* резервной дорожки */ #if 0 if (disk_dev.dctrl) besm6_debug ("::: КМД %c: чтение", ctlr + '3'); #endif break; case 004: /* запись (МОЗУ-НСМД) */ case 044: /* резервной дорожки */ #if 0 if (disk_dev.dctrl) besm6_debug ("::: КМД %c: запись", ctlr + '3'); #endif break; case 005: /* разметка */ c->format = 1; break; case 006: /* сравнение кодов (МОЗУ-НСМД) */ #if 0 if (disk_dev.dctrl) besm6_debug ("::: КМД %c: сравнение кодов", ctlr + '3'); #endif break; case 007: /* чтение заголовка */ case 047: /* резервной дорожки */ if (disk_dev.dctrl) besm6_debug ("::: КМД %c: чтение %s заголовка", ctlr + '3', cmd & 040 ? "резервного" : ""); disk_fail &= ~c->mask_fail; disk_read_header (u); /* Ждём события от устройства. */ sim_activate (u, 20*USEC); /* Ускорим для отладки. */ break; case 010: /* гашение PC */ #if 0 if (disk_dev.dctrl) besm6_debug ("::: КМД %c: гашение регистра состояния", ctlr + '3'); #endif c->status = 0; break; case 011: /* опрос 1÷12 разрядов PC */ #if 0 if (disk_dev.dctrl) besm6_debug ("::: КМД %c: опрос младших разрядов состояния", ctlr + '3'); #endif if (disk_unit[c->dev].flags & UNIT_ATT) c->status = STATUS_GOOD & BITS(12); else c->status = 0; break; case 031: /* опрос 13÷24 разрядов РС */ #if 0 if (disk_dev.dctrl) besm6_debug ("::: КМД %c: опрос старших разрядов состояния", ctlr + '3'); #endif if (disk_unit[c->dev].flags & UNIT_ATT) c->status = (STATUS_GOOD >> 12) & BITS(12); else c->status = 0; break; case 050: /* освобождение НМД */ #if 0 if (disk_dev.dctrl) besm6_debug ("::: КМД %c: освобождение накопителя", ctlr + '3'); #endif break; default: besm6_debug ("::: КМД %c: неизвестная команда %02o", ctlr + '3', cmd & 077); GRP |= c->mask_grp; /* чтобы не зависало */ break; } } } /* * Запрос состояния контроллера. */ int disk_state (int ctlr) { KMD *c = &controller [ctlr]; #if 0 if (disk_dev.dctrl) besm6_debug ("::: КМД %c: опрос состояния = %04o", ctlr + '3', c->status); #endif return c->status; } /* * Событие: закончен обмен с МД. * Устанавливаем флаг прерывания. */ t_stat disk_event (UNIT *u) { KMD *c = unit_to_ctlr (u); GRP |= c->mask_grp; return SCPE_OK; } /* * Опрос ошибок обмена командой 033 4035. */ int disk_errors () { #if 0 if (disk_dev.dctrl) besm6_debug ("::: КМД: опрос шкалы ошибок = %04o", disk_fail); #endif return disk_fail; }