| /* | |
| Copyright (c) 2015-2017, John Forecast | |
| 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 | |
| JOHN FORECAST 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 John Forecast shall not | |
| be used in advertising or otherwise to promote the sale, use or other dealings | |
| in this Software without prior written authorization from John Forecast. | |
| */ | |
| /* cdc1700_msos5.c: CDC1700 MSOS 5 trace and debugging support | |
| */ | |
| #include "cdc1700_defs.h" | |
| extern uint16 M[], Preg, Areg, Qreg; | |
| extern char INTprefix[]; | |
| extern uint16 doADDinternal(uint16, uint16); | |
| /* | |
| * Information about monitor requests. | |
| */ | |
| #define RQ_SYSDIRREAD 0 /* System directory read (monitor only) */ | |
| #define RQ_READ 1 /* Normal read */ | |
| #define RQ_WRITE 2 /* Normal write */ | |
| #define RQ_STATUS 3 /* I/O request status */ | |
| #define RQ_FREAD 4 /* Formatted read */ | |
| #define RQ_EXIT 5 /* Unprotected exit */ | |
| #define RQ_FWRITE 6 /* Formatted write */ | |
| #define RQ_LOADER 7 /* Relocatable binary loader */ | |
| #define RQ_TIMER 8 /* Schedule program with delay */ | |
| #define RQ_SCHDLE 9 /* Schedule program */ | |
| #define RQ_SPACE 10 /* Allocate core */ | |
| #define RQ_CORE 11 /* Unprotected core bounds */ | |
| #define RQ_RELEAS 12 /* Release core */ | |
| #define RQ_GTFILE 13 /* Access permanent file in program library */ | |
| #define RQ_MOTION 14 /* Tape motion */ | |
| #define RQ_TIMPT1 15 /* Schedule directory program with delay */ | |
| #define RQ_INDIR 16 /* Indirect (use another parameter list) */ | |
| #define RQ_PTNCOR 17 /* Allocate partitioned core */ | |
| #define RQ_SYSCHD 18 /* Schedule directory program */ | |
| #define RQ_DIRCHD 19 /* Enable/Disable system directory scheduling */ | |
| /* | |
| * Masks for default fields in the first parameter word. | |
| */ | |
| #define D 0x4000 /* Part 1 request indicator */ | |
| #define RQ 0x3E00 /* Request code */ | |
| #define X 0x0100 /* Relative/indirect indicator */ | |
| #define RP 0x00F0 /* Request priority */ | |
| #define CP 0x000F /* Completion priority */ | |
| #define TELETYPE 0x04 /* Console TTY LU */ | |
| /* | |
| * Well-known locations within MSOS 5. | |
| */ | |
| #define LIBLU 0x00C2 /* Library LU */ | |
| #define CREXTB 0x00E9 /* Extended communications region */ | |
| #define LOG1A 28 /* Offset to LOG1A table address */ | |
| /* | |
| * Queueable requests have a completion address as the second parameter. | |
| * Note that INDIR requests may or may not be queueable depending on the | |
| * target parameter list. | |
| */ | |
| t_bool queueable[] = { | |
| TRUE, TRUE, TRUE, FALSE, TRUE, FALSE, TRUE, FALSE, TRUE, TRUE, | |
| TRUE, FALSE, FALSE, TRUE, TRUE, TRUE, FALSE, TRUE, TRUE, FALSE | |
| }; | |
| const char *indent[] = { | |
| "", " ", " ", " ", " ", " ", " ", " ", " " | |
| }; | |
| const char mode[] = { 'B', 'A' }; | |
| const char luchr[] = { ' ', 'R', 'I', '?' }; | |
| const char rel[] = { '0', '1' }; | |
| const char part1[] = { '0', '1' }; | |
| const char exitind[] = { '0', '1' }; | |
| const char units[] = { | |
| '0', '1', '2', '3', '?', '?', '?', '?', | |
| '?', '?', '?', '?', '?', '?', '?', '?' | |
| }; | |
| const char *density[] = { | |
| "", "800 BPI", "556 BPI", "200 BPI", "1600 BPI", "???", "???", "???", | |
| "???", "???", "???", "???", "???", "???", "???", "???" | |
| }; | |
| const char *action[] = { | |
| "", "BSR", "EOF", "REW", "UNL", "FSF", "BSF", "ADR", | |
| "???", "???", "???", "???", "???", "???", "???", "???" | |
| }; | |
| uint32 seqno = 0; | |
| #define END(s) &s[strlen(s)] | |
| /* | |
| * Character representation. | |
| */ | |
| const char *charRep[128] = { | |
| "<00>", "<01>", "<02?", "<03>", "<04>", "<05>", "<06>", "<07>", | |
| "<08>", "<09>", "<0A>", "<0B>", "<0C>", "<0D>", "<0E>", "<0F>", | |
| "<10>", "<11>", "<12>", "<13>", "<14>", "<15>", "<16>", "<17>", | |
| "<18>", "<19>", "<1A>", "<1B>", "<1C>", "<1D>", "<1E>", "<1F>", | |
| " ", "!", "\"", "#", "$", "%", "&", "'", | |
| "(", ")", "*", "+", ",", "-", ".", "/", | |
| "0", "1", "2", "3", "4", "5", "6", "7", | |
| "8", "9", ":", ";", "<", "=", ">", "?", | |
| "@", "A", "B", "C", "D", "E", "F", "G", | |
| "H", "I", "J", "K", "L", "M", "N", "O", | |
| "P", "Q", "R", "S", "T", "U", "V", "W", | |
| "X", "Y", "Z", "[", "\\", "]", "^", "_", | |
| "<60>", "<61>", "<62>", "<63>", "<64>", "<65>", "<66>", "<67>", | |
| "<68>", "<69>", "<6A>", "<6B>", "<6C>", "<6D>", "<6E>", "<6F>", | |
| "<70>", "<71>", "<72>", "<73>", "<74>", "<75>", "<76>", "<77>", | |
| "<78>", "<79>", "<7A>", "<7B>", "<7C>", "<7D>", "<7E>", "<7F>", | |
| }; | |
| /* | |
| * Check if a logical unit is a mass storage device. | |
| */ | |
| t_bool isMassStorage(uint16 lu) | |
| { | |
| uint16 extbv4 = M[CREXTB]; | |
| uint16 log1a = M[extbv4 + LOG1A]; | |
| if ((lu > 0) && (lu <= M[log1a])) { | |
| uint16 physDev = M[log1a + lu]; | |
| /* | |
| * Check if equipment class is 2 (Mass storage) | |
| */ | |
| if ((M[physDev + 8] & 0x3800) == 0x1000) | |
| return TRUE; | |
| } | |
| return FALSE; | |
| } | |
| /* | |
| * Get the mass storage sector address associated with a read/write | |
| * request. | |
| */ | |
| uint32 getMSA(uint16 reqCode, uint16 param) | |
| { | |
| if (reqCode == RQ_SYSDIRREAD) | |
| return M[param + 6]; | |
| if ((M[param] & D) == 0) { | |
| uint16 sa = M[param + 5]; | |
| if ((M[param] & X) == 0) { | |
| if ((sa & 0x8000) != 0) { | |
| sa &= 0x7FFF; | |
| return (M[sa + 1] << 15) | (M[sa + 2] & 0x7FFF); | |
| } | |
| } else { | |
| if ((sa & 0x8000) != 0) { | |
| sa = param + (sa & 0x7FFF); | |
| return (M[sa + 1] << 15) | (M[sa + 2] & 0x7FFF); | |
| } | |
| } | |
| } | |
| return (M[param + 6] << 15) | (M[param + 7] & 0x7FFF); | |
| } | |
| /* | |
| * Parameter conversion routines. Based on the assembly source code of MSOS 5 | |
| * available on bitsavers. | |
| */ | |
| /* | |
| * Convert Logical Unit parameter to absolute value | |
| */ | |
| static uint16 luabs(uint16 param, uint16 lu, uint16 a) | |
| { | |
| switch (a) { | |
| case ' ': | |
| break; | |
| case 'R': | |
| if ((lu & 0x200) != 0) | |
| lu |= 0xFC00; | |
| lu = doADDinternal(param, lu); | |
| break; | |
| case 'I': | |
| lu = M[lu]; | |
| if ((lu & 0x8000) != 0) | |
| lu = doADDinternal(lu, 0x7FFF); | |
| break; | |
| case 3: | |
| lu = 0xFFFF; | |
| break; | |
| } | |
| return lu; | |
| } | |
| /* | |
| * Convert Starting Address parameter to absolute value | |
| */ | |
| static uint16 spabs(uint16 param) | |
| { | |
| uint16 sa = M[param + 5]; | |
| uint16 sl = param + 5; | |
| /* | |
| * If the D bit is set, the starting address must be absolute. | |
| */ | |
| if ((M[param] & D) != 0) | |
| return sa; | |
| if ((M[param] & X) == 0) { | |
| if ((sa & 0x8000) != 0) | |
| sa = M[sa & 0x7FFF]; | |
| } else { | |
| if ((sa & 0x8000) != 0) | |
| sa = param + (M[param + (sa & 0x7FFF)] & 0x7FFF); | |
| else sa = param + sa; | |
| } | |
| return sa; | |
| } | |
| /* | |
| * Convert Number of words to absolute value | |
| */ | |
| static uint16 npabs(uint16 param) | |
| { | |
| uint16 nw = M[param + 4]; | |
| /* | |
| * If the D bit is set, the number of words must be absolute. | |
| */ | |
| if ((M[param] & D) != 0) | |
| return nw; | |
| if ((nw & 0x8000) != 0) { | |
| nw = nw + (((M[param] & X) != 0) ? (param & 0x7FFF) : 0); | |
| if ((nw & 0x8000) != 0) | |
| nw = doADDinternal(nw, 0x7FFF); | |
| nw = M[nw]; | |
| if ((nw & 0x8000) != 0) | |
| nw = doADDinternal(nw, 0x7FFF); | |
| } | |
| return nw; | |
| } | |
| /* | |
| * Convert completion address to absolute value | |
| */ | |
| static char *cpabs(uint16 param, char *buf) | |
| { | |
| uint16 ca = M[param + 1]; | |
| /* | |
| * Only absolutize the completion address if one is specified. | |
| */ | |
| if (ca != 0) { | |
| /* | |
| * If the D bit is set, the completion address must be absolute. | |
| */ | |
| if ((M[param] & D) == 0) { | |
| if ((ca & 0x8000) != 0) { | |
| /* | |
| * If negative, System directory reference | |
| */ | |
| sprintf(buf, "SYSDIR(%u)", ca & 0x7FFF); | |
| return buf; | |
| } else { | |
| if ((M[param] & X) != 0) { | |
| if ((param & 0x8000) == 0) | |
| param = doADDinternal(param, 0x8000); | |
| ca = doADDinternal(ca, param); | |
| } | |
| } | |
| if ((ca & 0x8000) != 0) | |
| ca = doADDinternal(ca, 0x7FFF); | |
| } | |
| } | |
| sprintf(buf, "$%04X", ca); | |
| return buf; | |
| } | |
| /* | |
| * Describe motion parameters | |
| */ | |
| static void motion(uint16 param, char *d) | |
| { | |
| uint16 commands = M[param + 4]; | |
| if ((commands & 0xF) != 0) | |
| sprintf(END(d), " Density = %s\r\n", density[commands & 0xF]); | |
| if ((commands & 0x8000) == 0) { | |
| if ((commands & 0xF000) != 0) { | |
| sprintf(END(d), " Actions = %s", action[(commands & 0xF000) >> 12]); | |
| if ((commands & 0xF00) != 0) { | |
| sprintf(END(d), ",%s", action[(commands & 0xF00) >> 8]); | |
| if ((commands & 0xF0) != 0) | |
| sprintf(END(d), ",%s", action[(commands & 0xF0) >> 4]); | |
| } | |
| sprintf(END(d), "\r\n"); | |
| } | |
| } else { | |
| sprintf(END(d), " Repeat = %s, %u times\r\n", | |
| action[(commands & 0x7000) >> 12], commands & 0xFFF); | |
| } | |
| } | |
| /* | |
| * Generate a test representation of a write to the console teletype. If the | |
| * text is too long to fix (> 50 chars) it will be truncated. | |
| */ | |
| #define MAXTEXT 50 | |
| char *textRep(uint16 start, uint16 len) | |
| { | |
| int i; | |
| static char text[64]; | |
| size_t text_space = sizeof (text) - 1; | |
| text[0] = '\0'; | |
| for (i = 0; (i < (2 * len)) && (text_space >= MAXTEXT); i++) { | |
| uint16 ch = M[start]; | |
| if ((i & 1) == 0) | |
| ch >>= 8; | |
| else start++; | |
| ch &= 0x7F; | |
| strncpy(&text[strlen(text)], charRep[ch], text_space); | |
| text_space -= strlen(charRep[ch]); | |
| } | |
| return text; | |
| } | |
| /* | |
| * Dump MSOS5 request information. | |
| */ | |
| void MSOS5request(uint16 param, uint16 depth) | |
| { | |
| uint16 reqCode = (M[param] & RQ) >> 9; | |
| char partOne = part1[(M[param] & D) >> 14]; | |
| char relative = rel[(M[param] & X) >> 8]; | |
| uint16 completion = M[param + 1]; | |
| const char *request; | |
| char parameters[128], details[512]; | |
| char luadr; | |
| uint16 lu, abslu, abss, abswd, i; | |
| uint32 sector; | |
| t_bool secondary = FALSE; | |
| parameters[0] = '\0'; | |
| details[0] = '\0'; | |
| if (depth == 0) { | |
| /* | |
| * Check for INDIR request with 15-bit addressing. | |
| */ | |
| if ((M[param] & 0x8000) != 0) { | |
| fprintf(DBGOUT, "%sMSOS5(%06u): [RQ: $%04X]%sINDIR $%04X,0\r\n", | |
| INTprefix, seqno++, param, indent[depth & 0x7], | |
| M[param] & 0x7FFF); | |
| MSOS5request(M[param] & 0x7FFF, depth + 1); | |
| return; | |
| } | |
| } | |
| if ((M[param] & 0x8000) != 0) { | |
| /* | |
| * Secondary scheduler call | |
| */ | |
| secondary = TRUE; | |
| reqCode = RQ_SCHDLE; | |
| } | |
| /* | |
| * Check for invalid monitor requests | |
| */ | |
| if (reqCode > RQ_DIRCHD) { | |
| fprintf(DBGOUT, "%sUnknown MSOS5 request (code %u)\r\n", | |
| INTprefix, reqCode); | |
| return; | |
| } | |
| if (queueable[reqCode]) { | |
| char temp[16]; | |
| if (secondary) | |
| sprintf(details, " Compl = $%04X\r\n", M[param + 1]); | |
| else sprintf(details, " Compl = %s\r\n", cpabs(param, temp)); | |
| } | |
| switch (reqCode) { | |
| case RQ_SYSDIRREAD: | |
| request = "*SYSDIRREAD*"; | |
| goto rw; | |
| case RQ_READ: | |
| request = "READ"; | |
| goto rw; | |
| case RQ_WRITE: | |
| request = "WRITE"; | |
| goto rw; | |
| case RQ_STATUS: | |
| request = "STATUS"; | |
| luadr = luchr[(M[param + 1] & 0xC00) >> 10]; | |
| lu = M[param + 1] & 0x3FF; | |
| sprintf(parameters, "%u, 0, %c, 0, %c", lu, luadr, partOne); | |
| sprintf(END(details), " LU = %u\r\n", luabs(param, lu, luadr)); | |
| break; | |
| case RQ_FREAD: | |
| request = "FREAD"; | |
| goto rw; | |
| case RQ_EXIT: | |
| request = "EXIT"; | |
| /* No parameters */ | |
| break; | |
| case RQ_FWRITE: | |
| request = "FWRITE"; | |
| rw: | |
| luadr = luchr[(M[param + 3] & 0xC00) >> 10]; | |
| lu = M[param + 3] & 0x3FF; | |
| sprintf(parameters, "%u, $%04X, $%04X, %u, %c, %u, %u, %c, %c, %c", | |
| lu, completion, M[param + 5], M[param + 4], | |
| mode[(M[param + 3] & 0x1000) >> 12], | |
| (M[param] & RP) >> 4, M[param] & CP, | |
| luadr, relative, partOne); | |
| if (reqCode == RQ_SYSDIRREAD) { | |
| abslu = M[LIBLU]; | |
| abss = completion; | |
| } else { | |
| abslu = luabs(param, lu, luadr); | |
| abss = spabs(param); | |
| } | |
| abswd = npabs(param); | |
| sprintf(END(details), " LU = %u\r\n", abslu); | |
| sprintf(END(details), " Start = $%04X\r\n", abss); | |
| sprintf(END(details), " Words = %u ($%04X)\r\n", abswd, abswd); | |
| if (isMassStorage(abslu)) | |
| sprintf(END(details), " MSA = $%08X\r\n", getMSA(reqCode, param)); | |
| /* | |
| * If this a write to the console teletype, generate a partial | |
| * representation of the text being written so that we can correlate | |
| * our current location with the output. | |
| */ | |
| if (abslu == TELETYPE) { | |
| if ((M[param + 3] & 0x1000) != 0) { | |
| if ((reqCode == RQ_WRITE) || (reqCode == RQ_FWRITE)) { | |
| sprintf(END(details), " Text = %s\r\n", | |
| textRep(spabs(param), npabs(param))); | |
| } | |
| } | |
| } | |
| break; | |
| case RQ_LOADER: | |
| request = "LOADER"; | |
| sprintf(parameters, "[A: %04X, Q: %04X, lu: %u, t: %u, tna: %04X]", | |
| Areg, Qreg, (Areg & 0xFFF0) >> 4, Areg & 0xF, Qreg); | |
| break; | |
| case RQ_TIMER: | |
| request = "TIMER"; | |
| sprintf(parameters, "$%04X, %u, %c, %u, %c, %c", | |
| M[param + 1], M[param] & 0xF, | |
| relative, M[param + 2], | |
| units[(M[param] & 0xF0) >> 4], partOne); | |
| break; | |
| case RQ_SCHDLE: | |
| request = secondary ? "Secondary SCHDLE" : "SCHDLE"; | |
| sprintf(parameters, "$%04X, %u, %c, %c", | |
| M[param + 1], M[param] & CP, relative, partOne); | |
| break; | |
| case RQ_SPACE: | |
| request = "SPACE"; | |
| sprintf(parameters, "%u, $%04X, %u, %u, %c, %c", | |
| M[param + 4], M[param + 1], | |
| (M[param] & RP) >> 4, M[param] & CP, relative, partOne); | |
| break; | |
| case RQ_CORE: | |
| request = "CORE"; | |
| sprintf(parameters, "[A: %04X, Q: %04X]", Areg, Qreg); | |
| break; | |
| case RQ_RELEAS: | |
| request = "RELEAS"; | |
| sprintf(parameters, "$%04X, %c, %c, %c", | |
| M[param + 1], exitind[M[param] & 0x01], relative, partOne); | |
| break; | |
| case RQ_GTFILE: | |
| request = "GTFILE"; | |
| sprintf(parameters, "$%04X, $%04X, $%04X, $%04X, $%04X, %c, %u, %u, %c", | |
| M[param + 1], M[param + 7], M[param + 5], | |
| M[param + 4], M[param + 6], relative, | |
| (M[param] & RP) >> 4, M[param] & CP, partOne); | |
| /* | |
| * The reference manual does not correctly document the GTFILE request. | |
| * According to the MSOS 5.0 source code, there is a 10th parameter | |
| * which is used in calculating the address of the name block. | |
| */ | |
| i = M[param + 7]; | |
| if ((i & 0x8000) == 0) { | |
| i = doADDinternal(M[param + 10], i); | |
| if ((M[param] & D) == 0) { | |
| i = doADDinternal(i, 0x8000); | |
| i &= 0x7FFF; | |
| } | |
| } else i &= 0x7FFF; | |
| sector = (M[param + 8] << 16) | M[param + 9]; | |
| if (sector != 0) | |
| sprintf(END(details), " Sector = %u\r\n", sector); | |
| else sprintf(END(details), " Name = %c%c%c%c%c%c\r\n", | |
| (M[i] >> 8) & 0xFF, M[i] & 0xFF, | |
| (M[i + 1] >> 8) & 0xFF, M[i + 1] & 0xFF, | |
| (M[i + 2] >> 8) & 0xFF, M[i + 2] & 0xFF); | |
| break; | |
| case RQ_MOTION: | |
| request = "MOTION"; | |
| luadr = luchr[(M[param + 3] & 0xC00) >> 10]; | |
| lu = M[param + 3] & 0x3FF; | |
| sprintf(parameters, "%u, $%04X, %u, %u, %u, %u, %u, %u, %c, %c, %c, %c", | |
| lu, M[param + 1], | |
| (M[param + 4] & 0xF000) >> 12, | |
| (M[param + 4] & 0xF00) >> 8, | |
| (M[param + 4] & 0xF0) >> 4, M[param + 4] & 0xF, | |
| (M[param] & RP) >> 4, M[param] & CP, | |
| luadr, relative, partOne, | |
| mode[(M[param + 3] & 0x1000) >> 12]); | |
| sprintf(END(details), " LU = %u\r\n", luabs(param, lu, luadr)); | |
| motion(param, details); | |
| break; | |
| case RQ_TIMPT1: | |
| request = "TIMPT1"; | |
| sprintf(parameters, "$%04X, %u, 0, %u, %c", | |
| M[param + 1], M[param] & 0xF, | |
| M[param + 2], units[(M[param] & 0xF0) >> 4]); | |
| break; | |
| case RQ_INDIR: | |
| fprintf(DBGOUT, "%sMSOS5(%06u): [RQ: $%04X]%sINDIR $%04X,1\r\n", | |
| INTprefix, seqno++, param, indent[depth & 0x7], M[param + 1]); | |
| MSOS5request(M[param + 1], depth + 1); | |
| return; | |
| case RQ_PTNCOR: | |
| request = "PTNCOR"; | |
| sprintf(parameters, "%u, $%04X, %u, %u, %u, %c, %c", | |
| M[param + 4], M[param + 1], M[param + 5], | |
| (M[param] & RP) >> 4, M[param] & CP, | |
| relative, partOne); | |
| break; | |
| case RQ_SYSCHD: | |
| request = "SYSCHD"; | |
| sprintf(parameters, "$%04X, %u", | |
| M[param + 1], M[param & 0xF]); | |
| break; | |
| case RQ_DIRCHD: | |
| switch (M[param] & 0xFF) { | |
| case 0: | |
| request = "ENSCHD"; | |
| sprintf(parameters, "$%04X", M[param + 1]); | |
| break; | |
| case 0xFF: | |
| request = "DISCHD"; | |
| sprintf(parameters, "$%04X", M[param + 1]); | |
| break; | |
| default: | |
| request = "SYSCHD"; | |
| strcpy(parameters, "Invalid directory scheduling code"); | |
| break; | |
| } | |
| break; | |
| default: | |
| request = "*Unknown*"; | |
| sprintf(parameters, "Request code: %d", (M[param] & 0x3E00) >> 9); | |
| break; | |
| } | |
| fprintf(DBGOUT, "%sMSOS5(%06u): [RQ: $%04X]%s%s %s\r\n", | |
| INTprefix, seqno++, param, indent[depth & 0x7], request, parameters); | |
| if (details[0] != '\0') | |
| fprintf(DBGOUT, "%s\r\n", details); | |
| } |