/* | |
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); | |
} |