Sunday, September 5, 2021

VAX/VMS program to put current time in your DCL prompt

  A while back (a good while back, in the 80s) I was experimenting with "process permanent" things in VAX/VMS - ways to make code and data that survives the rundown of an image, and can do things while your process is just sitting at the DCL prompt or running other normal programs. You know, like what the DOS era PC types called "terminate and stay resident".

  In service to this idea, I wrote a simple program that loads into your process's P1 space, and at one minute or one second intervals, formats the current time and stores in in your DCL prompt. I had grown weary of having people at work that would say  "I  can  do it at home on my PeeCee, why can't we do it on the VAX?"

  The program allocates some space in your process's P1 space, and copies the code to do the work to it. That code calls $settimr to schedule an AST that modifies your prompt with the current time. It also reschedules itself to run again after a minute (or second) have elapsed.

  P1 space doesn't get run down when an image exits, so all of this endures through the life of your process. Not really all that much to it. 

  Since the code is copied to a different address than it was linked at, is is necessary for it to be PIC - position independent code. That's not too tough to do on a VAX - you access locations that are within the code being moved using addressing modes that are relative to the PC, and you access locations that are outside of the code being moved by absolute addresses.


  That is. inside the code to be relocated, use

              mov    zeep,r0
 
       (other code and data)

zeep:     long    666

  The mov     zeep,r0 uses relative mode, and after relocation, zeep is still the same distance away from the instruction as it was when it was assembled.

  If zeep is not part of the code being relocated, but is a location outside of it, use absolute mode to access it.

    mov    @#zeep,r0

  That's it in a nutshell. Register mode is always good

    mov    r2,r5 

  Is PIC,.But, if you are moving or using addresses in the registers, it will take a little thought to keep it  PIC.

  SImple, nicht wahr? Well, it gets easier with practice.


    So here it is - minute_clock will update every minute. Second_clock will update every second.





  To use...

$ mac minute_clock
$ mac second_clock

$ link minute_clock
$ link second_clock

$ run minute_clock
13:29>

or

$ run second_clock
$ 13:29:23>



  Per login session, don't run more than once, or run both versions - these programs don't unload until you log out, so it would be wasteful to get more than one copy running in your process.

Tuesday, July 13, 2021

RSX11D V4 recovered

  Before there was RSX11M+, there was RSX11M. It wasn't all that different, really, just...less capable. Most of the innards were all basically the same, mostly...just less of them. But before RSX11M, there was RSX11D (let's forget IAS just for the moment). RSX11D on the surface was a lot like RSX11M - same commands, same file structures, most of the same system calls. But under the hatches, there were some major differences (the biggest being the device IO architecture).

  Not many folks around these days have ever worked with RSX11D, and apparently not a lot of it survives. I have been looking for a runnable copy of it for decades now. I had started to think it was gone for good.  A while back, though, James Fehlinger got ahold of some RSX11D V6.2 tapes (the last version released). They were not in good shape,  and  he did a whole lot of analysis and recovery to get them into useable condition (a whole heckuva lot of work - some of it really inspired. He's a guy that doesn't take fail for an answer). His work on this is available at Bitsavers, at

http://bitsavers.org/bits/DEC/pdp11/simh_images

  So, it's great to have runnable RSX11D V6.2 available. I  have a few hobby projects planned for it already. If you ever had any interest in RSX11D, I recommend you check it out. But that's not what I'm writin' about today. A few months back. I spotted several Ebay auctions for the contents of a former DEC engineer's basement computer collection (sadly, he was deceased - at least his relatives were making his collection available, instead of heaving it all into a dumpster).  One of the items listed was an RK05 disk, whose labels indicated it might have a bootable RSX11D V4 system on it.

  I bought it, and asked around online, looking for someone who had a working RK05. Fritz Mueller replied that he had one, connected to an 11/45, and could try to read it for me. The Great Magnet smiled on his efforts (all energy flows according to the whims of the Great Magnet), and the decades old disk read flawlessly.  He copied it to an image file (at the cost of some effort getting it transferred off the 11/45 - thanks again Fritz)  and emailed it to me.

  So, flush with optimism due to the good progress so far, I mounted a copy of the disk image in SIMH and tried to boot it. No soap...it hung in a loop when I attempted to boot it. Halting SIMH and single stepping it  showed that it was looping on a few instructions, doing...something...with some RK11 registers. Fritz tried to boot the physical pack on his 11/45, but, no joy there either. He actually disassembled a goodly slug of the boot code on the pack, which was a bit of a self modifying mess, but, we didn't get the problem figured out enough to make the boot succeed.

  This was disappointing. I was starting to think this was going to be a failed computer archaeology project. I had another look at the loop that it executed, and saw that it was looping, waiting for something to happen. If that thing ever happened, it would get to location 164 and take off. I figured, waddahell, let's pretend what it was waiting for occurred, and set the PC  to 164 and go from there. When I did that, the rest of the boot worked, and I was presented with a working  RSX11D V4 system!


  UPDATE - This boot problem has since been  analyzed and patched - please see blog entry titled

 "RSX11D V4 boot problem update"

  for the explanation of the problem and a new version with the patch.


  RSX11D V4 is missing a lot of things we take for granted in the later members of the RSX family, so it's mostly of historical interest (the worst lack I've found, is that it doesn't have any form of RMD or DEMO - the memory usage display that helps RSX system programmers while away the idle hours). But, at least it hasn't been lost like so many other pieces of DEC software, and it's interesting to explore it and see where things came from.

  Here's a simh disk image and a pdp11.ini file for it.


rsx11dv4.dsk


pdp11.ini


  Here's a look at the loop problem, and how to get past it.



C:\simh40\PDP11\rsx11dpack>pdp11

PDP-11 simulator V4.0-0 Current git commit id: ab3e07a4
Disabling XQ
sim>boot
(CTRL-e)
Simulation stopped, PC: 000152 (BPL 140)
sim> s

Step expired, PC: 000140 (MOV #2,@#177406)
sim> s

Step expired, PC: 000146 (TST @#177404)
sim> s

Step expired, PC: 000152 (BPL 140)
sim> s

Step expired, PC: 000140 (MOV #2,@#177406)
sim> s

Step expired, PC: 000146 (TST @#177404)

sim> e -m 140:200
140: MOV #2,@#177406
146: TST @#177404
152: BPL 140
154: BIT #2000,@#177402
162: BEQ 0
164: MOV #77406,@#177600
172: MOV #1027,@#177640
200: MOV #144340,-(SP)

sim> g 164


RSX-004A

MCR>MOU DK:
MOUNT-**VOLUME INFORMATION**
DEVICE =DK0
CLASS =FILE 11
LABEL =RSXSYS
UIC =[1,1]
ACCESS =[RWED,RWED,RWED,RWED]
CHARAC =[]

  Above is the version banner and the automount of the system disk that occurs at boot time.

  Here's a few command examples.

MCR>PIP /LI


DIRECTORY DK0:[200,200]
 1-MAY-75 14:11

HCR.OLB;1           59.     C  30-APR-75 15:48
Z.OBJ;1             1.         01-MAY-75 13:56



MCR>ACT /ALL
DK.... STS W0 250 000 000 020 003 000 065000 000000 000000
 REGS 174000 000262 002436 000010 000000 000001 002200 150000 000246
TT.... STS W0 248 000 000 100 004 000 352200 000000 000000
 REGS 174000 007772 002070 000020 000000 120100 001770 000001 000230
LP.... STS W0 248 000 000 020 002 000 112200 000000 000000
 REGS 174000 000324 002104 000001 000000 000001 000000 002120 000100
DT.... STS W0 248 000 000 020 002 000 120700 000010 000000
 REGS 174000 001114 000366 000000 000000 000000 001000 147600 000250
HY.... STS W0 246 000 000 020 000 000 124500 000040 000000
 REGS 174000 000522 006724 000002 000000 120500 000000 000041 000500
...ACT STS RN 221 000 000 020 000 000 102500 000001 000000
 REGS 174000 001146 000016 000006 000000 000202 000000 000000 000076
MO.... STS W0 100 000 000 020 000 000 115300 000000 140000
 REGS 174000 001432 124340 000001 000000 000001 000000 000000 000120

MCR>TIM
 05/01/75 14:12:05

MCR>DEV
  TT0   **
  CI0   TT0
  CO0   TT0
  CL0   TT0
  MO0   **
  HY0   **
  PP0
  PR0
  MT0
  DT1   **
  DT0   **
  CR0
  LP0   **
  DP0
  DF0
  DK1   **
  DK0   ** MOUNTED
  SY0   DK0

MCR>TAS
ALLOCR 11 GEN 050 003400 DK 0-00000004550
BPR... V001A GEN 050 007700 DK 0-00000006363
CR.... V004A GEN 248 003500 DK 0-00000004653
DK.... V003F SYDISK 248 003000 DK 0-00000004671
DT.... V005A GEN 248 003600 DK 0-00000004723
HCRCHK RSX03A GEN 050 071400 DK 0-00000007647
HY.... V002A GEN 246 007600 DK 0-00000007156
LKD RSX03A GEN 050 076500 DK 0-00000007744
LP.... V003E GEN 248 003100 DK 0-00000004732
MO.... V003F GEN 100 003400 DK 0-00000005014
PP.... V003F GEN 248 003100 DK 0-00000004706
PRT... 03 GEN 250 003700 DK 0-00000003775
PR.... V003F GEN 248 003000 DK 0-00000004715
SG2... V001A GEN 230 025300 DK 0-00000007024
TTYN00 GEN 051 001400 DK 0-00000005702
TT.... V003F TTY 248 011600 DK 0-00000004770
.FL000 V003A FILE 248 012500 DK 0-00000004464 FIXED
.FL007 V003A GEN 250 004700 DK 0-00000004502
.FL100 V002A MCR 247 004100 DK 0-00000004512
.FL200 10 MCRDSP 250 003200 DK 0-00000004522
.FL300 V002A GEN 240 011500 DK 0-00000004557
.TKTN. V002A GEN 249 002300 DK 0-00000004241
...ABO V002A MCR 221 001700 DK 0-00000004004
...ACT V002A MCR 221 002200 DK 0-00000004011
...ALT V002A MCR 221 002000 DK 0-00000004017
...BAT 01 GEN 020 022700 DK 0-00000006266
...CAN V002A MCR 221 001400 DK 0-00000004024
...COM V001A MCR 221 002100 DK 0-00000004041
...CON V004A GEN 050 004400 DK 0-00000004370
...DCU V003A GEN 050 013500 DK 0-00000004531
...DEV V001A MCR 221 001400 DK 0-00000004047
...DIS V002A MCR 221 001600 DK 0-00000004054
...DMO V002A GEN 050 003100 DK 0-00000004446
...DMP D04 GEN 050 025200 DK 0-00000002246
...EDI V004C GEN 050 056000 DK 0-00000005147
...ENA V002A MCR 221 001500 DK 0-00000004061
...FIX V002A MCR 221 002400 DK 0-00000004074
...FLX V003F GEN 050 052700 DK 0-00000002376
...FTN V0804 GEN 050 060200 DK 0-00000001773
...IHI V001A GEN 050 072500 DK 0-00000007512
...INI V002A GEN 050 015500 DK 0-00000004411
...INS V004A GEN 050 030000 DK 0-00000004261
...LBR 001 GEN 050 057000 DK 0-00000002531
...LOA V002A MCR 221 002700 DK 0-00000004102
...LUN V001A MCR 221 003600 DK 0-00000004110
...MAC 05.01 GEN 050 126400 DK 0-00000003104
...MCP V002A MCR 221 001100 DK 0-00000004117
...MCR V001A MCRDSP 221 002000 DK 0-00000004124
...MOU V004A GEN 050 010600 DK 0-00000004432
...OPE V002A MCR 221 004000 DK 0-00000004131
...PAR V001A MCR 221 001300 DK 0-00000004140
...PIP D13 GEN 050 042700 DK 0-00000002200
...REA V003A MCR 221 003300 DK 0-00000004146
...RED V003A MCR 221 002000 DK 0-00000004155
...REM V004A GEN 050 025200 DK 0-00000004337
...RUN V004A MCR 221 003400 DK 0-00000004400
...SAV V002A MCR 221 003600 DK 0-00000004176
...SET V004A MCR 221 001500 DK 0-00000004213
...TAS V003A MCR 221 002300 DK 0-00000004226
...TIM V003A MCR 221 002000 DK 0-00000004234
...TKB 08 GEN 050 150200 DK 0-00000003553
...UFD V002A GEN 050 003700 DK 0-00000004455
...UNF V002A MCR 221 001500 DK 0-00000004247
...UNL V003F MCR 221 002000 DK 0-00000004254
...VFY D05 GEN 050 034500 DK 0-00000002277
MCR>




Sunday, February 7, 2021

DECUS APL-11 fixes for ID space and RSX11M-Plus 4.6

  Recently, Mark Matlock and I were talking about APL-11. This was a version of Iverson's language, APL, for the PDP11. It was written for DEC by OMSI, and sold as a layered product. Later on, it got donated to the DECUS library.

  Mark mentioned that it would be a lot more useful when running on RSX if it had more room in its workspace (an APL workspace is storage for variables and programs). He reckoned that using I&D space with M+ would be a good way to just about double the space available.

  I&D space is a feature found on some of the newer members of the PDP11 family. It allows the use of a second set of APRs (Address Page Registers). It allows a full set of APRs to be used to map the code in a program, and a full second set to map the data. If you have the right mix of code and data, you can wind up with double the address space.

 APL-11 was pretty old, and predated I&D space for RSX. Apparently people in the past had been able to produce I&D space versions, with mixed results, from the DECUS kit, but attempts to do so on current versions of RSX11M+ produced a task that would start and then  immediately exit without doing anything. Even worse, attempts to create even the stock, non-I&D version from the DECUS sources also produced a task that did the same thing. The old .TSK file from the kit, however, runs just fine on M+ 4.6

  I figured, waddahell, I'll have a crack at it. I mean, the sources are included, how hard could it be? A month later, I knew the answer to that question - hard - very hard. Ironically enough, this wasn't my first challenging encounter with APL. In 1971, I took a freshman computer science course. It taught the concepts of programming, using APL and PL/1. I hated both of them and swore never to work with computers again. It was 8 years later, when the typesetting company I worked at got automated using an 11/70 running IAS, that I got exposed to computing again.

  So, anyway, first I get the source kit and load 'er in. The kit had the ability to produce several different variants - single and double precision, with and without floating point processor, and like that. I picked the double precision variant, with FPP support, and got to work. First thing I wanted to do was try and get this thing to build a normal version, without I&D, that would actually  run. Sure enough, following the instructions, assembling and linking it produced a task that would start, and then exit immediately, without printing any error messages. Not a lot to go on there...

>
>run apl7
>

  Normally, I'd link in the PDP11 symbolic debugger and see where it was going wrong - but APL-11 uses an ODL from Hell - the damned thing was over 100 lines long. A few abortive attempts to get the symbolic debugger to work with it, and I fell back to stones knives and bearskins - linking in ODT (Odious Debugging Technique), and sprinkling in IOTs. IOT instructions cause a register dump and exit, or a simple breakpoint when in ODT. Note - in order to get IOT dump and exit to work, it's necessary to change the 1 in the SST vector entry in module ERROR.MAC, to a 0 - otherwise it gets trapped.

SSTVT::                                          ;SST VECTOR TABLE
         .WORD   SYSER        ;ODD OR NON-EXISTENT MEMORY
         .WORD   0                  ;MEMORY PROTECT ERROR
         .WORD   SYSER        ;T-BIT OR BPT
         .WORD   0                   ;IOT INSTRUCTION
         .WORD   SYSER         ;RESERVED INSTRUCTION
        .WORD   TERROR      ;BAD EMT
        .WORD   TERROR      ;TRAP INSTRUCTION


  The program started, logically enough, in a routine called INIT. INIT called a subroutine called $LOAD, with a RAD50 string argument of APLDF1. If $LOAD returns with the carry bit set, INIT calls EXIT$S and...exits. $LOAD was always returning with the carry bit set. Returning with carry bit set is an RSX convention for reporting failure. So, I was wondering, why does $LOAD return a failure status?

  $LOAD turns out to be a system routine that loads an overlay segment, of the name passed as the argument. It was the work of an instant to extract LOAD.OBJ from SYSLIB, and disassemble it with ORCAM (ORCAM is a utility, written by DJ Dunstan and Chris Doran that will disassemble an object file back into MACRO11 source. It's available on the APL11G.DSK virtual disk, UIC [373,221]. ORCAM is MACRO spelled backwards - get it?).

>lbr load.obj = [1,1]syslib.olb/ex:load
>orcam load=load

  So, a perusal of routine $LOAD showed it to be pretty straightforward - it loops through a table of segments (pointed to by N.OVPT, offset N.STBL), looking for a segment with a name that matches the RAD50 string in the argument. But - stepping through the loop and examining the name field of each segment  showed that all of the segments in the table had all 0's in the name field....what's up with that? When it doesn't match any of the strings with the argument, $LOAD hits the end of the table and returns...carry set.

  So...segments got no names...so you can't load them. I recalled that the ZAP utility can list segments in a task, using the /LI switch. I tried it on the APL task I had just made.

>ZAP APL7.TSK/LI
>zap apl7.tsk/li
ZAP Version 04.00  Copyright (c) Mentec, Inc., U.S.A. 1999
Segment table
000002: 000000-056663         Root
000061: 056664-057327           Overlay
000062: 057330-062523           Overlay
000066: 057330-061103            Overlay

  and so forth, for all the segments. The name is supposed to be in there between the address extents and the "root" or "overlay". It occurred to me to have a look at the old .TSK that comes with the DECUS  kit. Here's how it displayed.

>zap apl7ok.tsk/li
ZAP Version 04.00  Copyright (c) Mentec, Inc., U.S.A. 1999
Segment table
000002: 000000-060103 ACOPY   Root
000063: 060104-060577 DECNAM    Overlay
000064: 060600-063707 .CSI1           Overlay
000070: 060600-061373 PARSE        Overlay
000071: 061374-062567 PARSFN      Overlay
000073: 061374-063153 PARSDI       Overlay
000075: 060600-061407 DLFNB        Overlay
and like that...the names of the segments are clearly there...

  So, now I'm seeing why the old one works - it has the names,  and newly constructed ones don't - but...what changed and why? 

  I got to thinking that something changed between the old version of RSX11M the working task that was included in the kit was built on, and the 4.6 version of RSX11M+ I was using. I loaded up a copy of RSX11M 3.2 in SIMH and copied the APL kit to it. Creating a new version of the APL task there produced a task that worked, and that ZAP showed named segments for.  

A lengthy reading session with several different versions of the Task Builder manual shed a little light on this situation. Most of the APL program relies on autoload of segments - but in three places in the program, segments are manually loaded by calling $LOAD instead of autoloading. The TKB manual says that segment names are not used by autoloading, and are not included in the segment descriptors of tasks that use autoloading.

  The manuals all the way back to RSX11m V1 state that the segment names will be omitted in tasks that use autoloading, and included only in tasks that use manual loading - but, up to at least RSX11m V3.2, the segment names are still present in autoloaded tasks. That allows APL to call the manual $LOAD routine successfully - if it was built on  versions of RSX that still had the names in the segment descriptors.

  But, how does the Task Builder know if a task is an autoload task or not, so it can decide to include or omit the segment names?? I created some little test programs that used autoload and manual loading, alone and together, to see what caused a task to know it's an autoload task and not include the segment names. to see. Here's the results..

                                      3.2    4.6
ODL has * & $LOAD                
y     n
ODL has $LOAD, no *             y      y
ODL has * only                        n n
ODL has no $LOAD, no *     n      n


  So, it looks like, if the ODL file incudes a reference to module LOAD, in M3.2, it will have segment names included in the segment table, even if it has an * (autoload indicator) in the ODL file. However, M+ 4.6, if it has an * in the ODL, the segment table will have no names, module LOAD present or no. That explains how the old task in the kit, and the version I produced on RSX11M 3.2 could work, but the ones I produced on M+ 4.6 couldn't.

  OK ,so what to do about it? Well, I fiddled around with trying to get the required modules to autoload, so we could drop the calls to $LOAD, but, no soap. Couldn't get it to go.  Maybe I need to know more about how autoload works - but my goal was to produce a bigger version of APL-11, not to spend a lot of time becoming  a perfect master of the arcane TKB arts. 

  As an alternative, I figured, that if $LOAD could be modified to find the right segments, it could LOAD them and things would proceed normally.

  A basic Segment Descriptor looks like this.


   +----------+------------------------------+            | Status   | Relative Disk Address        |
           +----------+------ -----------------------+
           |             Load Address                |
           +-----------------------------------------+
           |           Length in Bytes               |
           +-----------------------------------------+
           |       Link Up (away from root)          |
           +-----------------------------------------+
           |       Link Down (toward root)           |
           +-----------------------------------------+
           |       Link Next (to neighbor)           |
           
+-----------------------------------------+
           |             Segment Name                |
           +---               in                  ---+
           |               Radix-50                  |
           +-----------------------------------------+
           |   Window Descriptor Address             
|
           +-----------------------------------------+

  There's a few additional optional fields at the end of the descriptor, that support memory resident overlays, and for support of I&D, but they aren't of any interest in this caper.

  So, I need something unique in the Segment Descriptor to identify it by when $LOAD looks for it, instead of the Segment Name, which is all zeroes. Load Address is right out - every overlay segment at the same level in a tree will have the same load address. The links are not unique either. But, the Relative Disk Address (the relative address of what block the segment occurs in the task file) is unique in each descriptor. You can get it from the ZAP /LI, or read it from the MAP file.

  I modified the $LOAD routine to expect a word argument rather than two words of R50, and changed a few lines to it to make it compare that word argument against the Relative Disk Address field while looking for a match. 
  Here's the original (the comments are mine - ORCAM is good, but not THAT good).
26$:    TST     (R2)            ;are we at the end of the segment table yet?
        SEC                     ;set status assuming we are
        BEQ     236$            ;if we were done, jump to exit
        CMP     14(R2),2(R0)    ;do the first three R50 chars match in arg?
        BNE     54$             ;if not, skip to next entry
        CMP     16(R2),4(R0)    ;the first three chars matched - do next 3?
        BEQ     74$             ;if these match, it's our segment, br to loading
                                ;or fall through to loop to next entry
54$:
  Here's the changes...not really much to it...
 
26$:    TST     (R2)            ;are we at the end of the segment table yet?
        SEC                     ;assume not
        BEQ     236$            ;if not, get out and fail
        mov     (r2),r3         ;move status and disk addr to r3
        bic     #170000,r3      ;mask off the 4 status bits
        cmp     r3,2(r0)        ;is this the segment we're looking for?
        beq     74$             ;if so, go load it
                                ;else fall through, to loop to next
54$:
  LOAD also contained a couple of jumps to an unknown global address, D0000000. I just replaced those with JMP    0 - they were for fatal errors, so a bad address stack dump and exit would do just as well.
  OK, I assemble it,
>mac load=load
and add a factor statement for it to the root of the ODL
BASE0:  .FCTR   gload-O1-*(O11,O13,O14,O15,O16,O17,O18,O19)
and add the factor 
gload:  .fctr   load.obj

  While editing the ODL file, I also delete all references to PATCH or PATCH1, and also edit APL7.CMD and remove line
EXTSCT  =PATCH1:400
  We got no room for patch space - we're trying to make the WS bigger...
 Then I task build it. Trouble - I get  errors from the task build - multiply defined symbols for $AUTO and $ALPBL. Those are for autoloading. The map file shows those symbols present in LOAD module - but that's the routine for manual loading - what's up with that? A look in LOAD.MAC tells the tale. There are a couple of "dummy" definitions for those, so that manual loading tasks don't try and access autoloading code. See below...

$AUTO::
        RTS     PC
$ALBPL::
        MOV     #<IE.OVR&377>,@#$DSW


  But, we need autoloading for most of the program, so highjacking these is right out. I delete from the first occurrence of ".DSABL    LSB" to just before the .END in LOAD.MAC (this also gets rid of a few hundred bytes in an ASECT section that we don't need).
   Success - it task builds with no errors. I run it, and it exits. But, that's what I expect at this point - I haven't put the numbers in for the Relative Block Addresses yet. I edit INIT.MAC, XERROR.MAC, and CLRCOR.MAC (the three macro files that have $LOAD calls in them), and substitute the block numbers for the RAD50 segment names of APLDF1,APLDF2,ROOT1, and ERSEG0 through ERSEG5. I subtract two from the disk number from the map file, since the map file numbers are absolute offsets from the beginning of the task file. Here's one example from INIT
  This is the call to $LOAD - tells us the argument is at ROTSEG
        MOV     #ROTSEG,R0      ;load root data segment
        CALL    $LOAD           ;do it
  Here's ROTSEG, with its original RAD50 argument of APLDF1
ROTSEG: .BYTE   3,0             ;load synchronously
        .RAD50  /APLDF1/        ;low data

  Now find segment APLDF1 in the map file and get the block address.
  Subtract two from it to make it "relative" and put it in as a word
  where APLDF1 R50 string was.
*** Segment: APLDF1
R/W mem  limits: 074724 074735 000012 00010.
Disk blk limits: 000237 000237 000001 00001.

ROTSEG: .BYTE   3,0             ;load synchronously
        .word    235            ;low data
        .word    0              ;filler word
;       .RAD50  /APLDF1/        ;low data

  The filler word is put in there, since the RAD50 name is two words long. Keeping the arglist  the same length is not a bad idea, and is absolutely required in the XERROR segments, since they are in a table indexed by length. I leave the RAD 50 argument in, commented out, since it documents what segment we are mucking with. 

  Finish doing that to the other arguments, reassemble and re-TKB, et viola! We have a runnable task!
>run apl7
TERMINAL..tt
WELCOME TO APL-11 X2.1
CLEAR WS a_1 2 3 4 5 6 7 8 9 10
      a
1 2 3 4 5 6 7 8 9 10
  Here, the  command assigns (APL uses "_" for the assignment operator, instead of "=" - Iverson was one of those fussy math types who hate using "=" for assignment. Why "_" instead? Because ASCII character for 95  used to be the left arrow symbol, before they changed it to the underscore. You see the same thing in RSX-15's MCR commands) a vector to variable "a".  Typing "a" causes the value of "a" to be printed out.
Looks like it's working...that's progress.
  OK, so now, after all that work, I'm where I should have started - APL-11, running more or less as it came, with no address space enhancements. This setup can run with a /INC memory increment of 35000 (36000 and above cause "not enough APRs" error message), and produce a workspace of 29720 bytes (.bxwa cause APL to display the workspace size).
>run apl7/inc=35000
TERMINAL..tt

WELCOME TO APL-11 X2.1
CLEAR WS
      .bxwa
29720
  OK, finally,  let's try TKB'ing with /ID added to the task build command. I do so, and  run the task. Natch, it doesn't do anything...adding /ID to the task build command caused all the segments to shift around in the task, so, I get the new locations from the map file, edit them into INIT, CLRCOR and XERROR, re-assemble. re-LBR  and re-TKB. Then, I get a running task. This time, I can use a /INC value of 64000 without getting the "not enough APRs" message. Success! Not so fast...when I run it with /INC that big, it loads and starts, displays the banner- but won't execute any APL commands - it just exits silently when you enter one. Now, after all this work, that's a disappointment. 

  Trying lots of different values for /INC, a pattern emerges, though. /INC values up to 37000 starts a task that runs correctly and executes commands. Going much higher, though, causes the command failure problem. With /INC=37000, the .bxwa command shows a workspace size of 31752. That's getting close to a suspiciously "binary" number. It's getting close to 32767, hex 7FFF - the largest possible positive value for 16 bit 2's complement numbers. Mark Matlock pointed out a note in a DECUS SIG tape submission, by Bob Awde, about a similar problem that occurred in a previous version, where ASR and ASL got used to convert the amount of free space from the number of bytes to number of words, and vice versa. When the size of the WS got large, it could cause a shift left to turn the size negative, or an unwanted sign extension could similarly gum up the works on a shift right. I thought all of those had been  previously fixed, but, I had a look in CLRCOR.MAC and found one case left. I changed

    ASR     R0              ;MAKE A WORD SIZE FOR THE BLOCK
to

    CLC
    ROR     R0              ;MAKE A WORD SIZE FOR THE BLOCK

  The clear carry keeps a bit set in carry from shifting into the sign bit and making the value negative. OK, now assemble, re-LBR and re-TKB (fortunately a two line change didn't cause the segments to shift locations in the task file, so editing the values into the three MAC files from the map file wasn't needed this time) and run it again. This time, it's all roses. Large /INC values don't cause commands to fail. and a much larger /INC is possible. Max is now 64000
 >run apl7/inc=64000
TERMINAL..tt

WELCOME TO APL-11 X2.1
CLEAR WS
      .bxwa
53256

  Alright Alright Alright.... Not bad - we have a workspace now that is over twice as big as when we started. Next, let's take advantage of another address space conserving tactic - the FCS Supervisor mode library. Most modern 11's have Supervisor mode , as well as I&D. Supervisor mode gets us a whole new set of APRs, and it's easy to add. All we have to do is add the line
SUBLIB=FCSFSL:SV
to the TKB command file, and remove all of the references to SYSLIB and NOANSLIB in the ODL file, that called out all the FCS routines by name. Then, re-TKB. Naturally, that caused the segments in the task file to move around...so, edit in the segment disk addresses again, reassemble, re-LBR and re-TKB again...and once again, a running task. However, it didn't gain us much in the way of workspace size. /INC=64000 is still the max /INC you can use (make sense - that's 8 whole APRs worth of increase - ya only get 8 per space).

>run apl7/inc=64000
TERMINAL..tt

WELCOME TO APL-11 X2.1
CLEAR WS
      .bxwa
53272

  We wind up with an increase of around 20 bytes or so. But, it's still worthwhile to use FCSFSL. FCSFSL saves main memory.  FCSFSL also puts all the FCS code into supervisor mode - that means you can now "flatten" the code and remove all of the overlaying there, which will reduce a lot of overlay disk IO. But, that's a whole 'nother story....
  Here's the files for the project...
aaareadme.txt    A step by step guide to changes required to produce APL7.TSK
blog.txt               A copy of this article in friendly ascii text mode
apl11.dsk           An RSX11 virtual RL02 containing the APL11 DECUS kit, no fixes done
apl11g.dsk          An RSX11 virtual RL02 with all fixes and WS increases done. 
apl7.tsk              If you just want to run the task, here's a copy  of the final result.   



Saturday, December 5, 2020

RSX utility for reading VMS backup tapes

   Back in the day, as the sun was setting on the RSX world, VAX/VMS grew strong in the land. As many people and sites migrated from PDP-11s to VAXen, there was a steady demand for a VMS utility that could read BRU format tapes (BRU is the RSX Backup and Restore Utility, used to backup up files and disks). Several pretty good solutions were coded up by the user community, and they all worked pretty well.

  Back in September, I had occasion to restore some old VMS files from VMS Backup TK50 and TK70 cassettes.  As it happens, at the moment, I don't have any VAXen that have TK tape drives installed. I do, however, have a couple of RSX systems that have TK drives - so I needed the opposite of the assorted VMS BRUREAD utilities. I did a little looking around, and couldn't find any RSX tools that could read a VMS backup tape.   I guess that doesn't really come as a surprise - the IT world almost always operates by Italian racing rules (what's behind you is not important).

  So, I needed a utility to restore files from a VMS Backup save set to an RSX system. I did a little looking around, and didn't find anything that would do the job. There was a Linux utility that can read VMS Backup tapes (vmsbackup), but it couldn't really deal with getting the file attributes right on the RSX system. I could have modified it to do the job, with a lot of work removing the Linuxisms and inserting RSXisms, but, I'd rather write my own solution in Macro-11 on a clean sheet of paper than spend a lot of time modifying a C program. There was a DECUS RT11 based project to read VMS Backup tapes on RT11, but, it didn't actually ever get completed - and RT11 is a whole lot different from RSX, so it wouldn't have been that helpful. 

  There's not a whole lot of info available about the format and data structures of a VMS Backup save set. I probably have the sources on microfiche - but, I haven't had access to a microfiche reader since I retired from MegaBig Engineering Corp. Most of the information I needed was gleaned by viewing the output of the still undocumented VMS BACKUP/LIST/ANALYZE command on some example save sets on a VMS system. I found some good info about the breakdown and offsets of the data in a Backup set on a VMS Freeware collection (https://www.digiater.nl/openvms/freeware/v30/logging-virtdisk/), as part of an interesting project by Glenn Everhart that provided automatic logging of writes to a device. More info was found in the file system MACRO definitions on both RSX and VMS systems.

  In a nutshell, Backup save sets consist of big blocks, each of which consists of a block header and typically contains one or more  "records". The possible type of records are

Null - null records are used at the end of a block to fill it to length

Summary - Each backup save set volume has a summary record, containing info about the block, including the command that created the save set.

Volume - contains summary information for each volume in the save set.

File - each file saved in  the backup set has a file record, which contains info about the file, including its name and attributes.

VBN - VBN records contain the virtual blocks of a file. These records come after a file record.

Physical - physical record - prolly from backup/physical - I didn't research this, since I am not interested in BACKUP/PHYSICAL save sets. You occasionally find one in the middle of a save set - not sure what that's all about.

LBN - not sure - probably used with physical backups - didn't see any in my research

FID - These appear to contain a list of all the file IDs of the files that are included in an image backup. 


  So, to make a long story short, in a nutshell, to summarize, prècis style, as bullet points, generally speaking, to synopsize, from 10,000 feet, glossing over a heckuva lot of details,  I whomped up a program that reads tape blocks via QIO, finds records in the blocks, and acts on each record. For each File record, I open a file of the indicated name and of the specified size and extent, with the indicated attributes. For each following VBN record after that, I write it sequentially to the file. When I get a new File record, I close the existing file and start a new one. Until we're done. Sounds pretty straightforward, nicht wahr? Yeah, it took a couple months to get it working just so. All of the IO was done via QIOs, including ACP QIOs, to avoid the space that including FCS would have cost us. Address space was an issue (as Cutler used to say, "Size matters") since Backup save set often used very large tape blocks. 

 This code doesn't change mode or muck about with the disk or OS internals using non-supported methods, but, like all my work, it's use at your own risk....

  So, here's the code

BTR.MAC

To build...

mac btr=btr

tkb btr/id=btr

  It has to be a /ID task, because I and D space is required to allow sufficient address space for the large tape input buffer. Sorry, RSX11M and IAS users (they don't have I&D space available).

  To use...

Mount the tape as foreign

>mou mu0: /for

then...

>run btr

  You have to "run btr" instead of installing it and doing BTR commands, since BTR doesn't have GCML and CSI command support - they were omitted to conserve address space.

  It will prompt as BTR>. Enter the name of the tape drive. If that's all you enter, you get a listing of the backup saveset on the tape.

>BTR>mu0:

[GLEASON.ACCOUNT]SETACCOUNT.MAR;31

[GLEASON.ACCOUNTING]Z.MAR;2

[GLEASON.ACP]ATTMOD.MAR;3

[GLEASON.ACP]CHECKUIC.MAR;1

[GLEASON.ACP]CHGCRE.MAR;3

[GLEASON.ACP]CHGDATE.MAR;5

[GLEASON.ACP]DATE.MAR;38

[GLEASON.ACP]GETFILNAM.MAR;1

 And like that.


  If you want to restore all the files, specify an output directory

>BTR>mu0:/out=[somedir]

  If you want to restore or list just some files, use the FIL switch, with wildcards

>BTR>mu0:/fil=*att*.mar  (to list selected files)

>BTR>mu0:/out=[somedir]/fil=*uic*.mar   (to restore selected files)

  If you want to see some of the inner workings while listing or restoring, you can add the /DBG switch, with one of the following values

a.dbg  =   1                    ;print a whole bunch of debug info

a.wld  =   2                    ;print wild card processing

a.iosb =   4                    ;print tape dsw and iosb

a.labl =  10                    ;print label related stuff

a.file =   20                    ;print file activities

a.summ =  40                ;print save set and volume summary info

a.rec  =    100                ;print record processing

a.blk  =    200                 ;print block header info

These are octal values. You can get more than one at a time by adding them together - ie, wild card processing and record processing  together, use 102. Note - some dbg options have bugs in them - /dbg:1 can fail in mid tape, for instance, and /dbg:200 occasionally fails as well. They worked well enough to debug the thing, so fixing them isn't a high priority.

  Since VMS supports 39.39 format filenames (39 chars for filename, 39 chars for extensions) and also has hierarchical directories, a lot of filename wrangling has to get done when restoring files. Filenames are truncated down to the 9.3 format used by RSX. No attempt was made to write them to more than one directory on restore - the output directory you specify is it. Also, VMS supports $,_ and - in filenames. RSX...doesn't. These characters are replaced with X, Y and Z. BTR will happily restore the .DIR files in the save set...but RSX will not use them as directories - they'll just be one more file in the output directory. All this means that you'll have to pay attention to what files you're getting, and what directories they were originally in.

   BTR also restores the .DIR and .SYS files from the backup, so it would be a pretty bad idea to specify [0,0] as the directory to restore to....

 Since a lot of VMS savesets spanned more than one tape, I have included primitive multi-tape support in BTR. When you get to the end of a tape of a multivolume save set, BTR will prompt for the next tape. Hang the next tape and press return to proceed. There's no checking to make sure that you hung the right tape, so...be careful...I also didn't bother adding any support for multi-savesets on a single tape. I mean, who does that? I never did , back when I managed VMS systems.

If you spot any problems using BTR - let me know...I'm working on Version 2, which will have a larger tape buffer, so I'll be fixing any bugs I hear about.


Wednesday, September 16, 2020

RD32 - Nothing Lasts Forever

 Lately, I've been using an old Micro PDP-11/73 for some development work. It's compact, fits under the desk, and is plenty fast enough for the project. But, it only had a single RD32 disk drive in it, which left me a little cramped for disk space. There's not enough room to install DECnet, for example, so that means I have to sneakernet things on and off of it using RX50s.

  So, I figured I'd add another RD32, which would double the disk space available. I installed a 6 button control panel (not strictly required, but it makes adding a second drive to a BA23 system easier). Time to install the disk... I spent quite a little bit of time on this - the cables I had aren't keyed, so I had to span the vector space of possible orientations looking for how to plug them in. No soap - the thing still wouldn't come on line. I tried some diags, but, since I didn't know how these particular diags acted when everything was working, I couldn't really conclude anything from their results. 

  It finally started making some odd noises, so I tried another disk, which worked fine. I pulled the one that wouldn't work apart, to see if I could tell what the problem was. Here's a picture of what I found....


  It's a picture of the disk arm for the top platter, with a broken wire trailing off from where the read/write head used to be attached. So, no surprise it wasn't working.

  Here's a picture of me reflected in the top platter - nothing to do with the problem, it's just how I do a selfie...



Wednesday, September 9, 2020

TU58 emulator for RSX11


  So every once in a while I try to retrieve some space here in the War Room (my computer room - we call it that because, during a spirited discussion about how messy it was, I jokingly told my wife "we can't fight in here - this is the War Room") by assembling some of the parts into systems. I have an old BA11-VA  QBUS box I got in trade a long time ago.I figured it could consume a few QBUS cards and maybe even do something useful (well, OK, maybe not useful, but at least amusing). 

I had a look at the specs for it. The power supply isn't all that powerful in that box, so the choice of cards is a little limited. It could just barely power an 11/23, an MXV11A and an RQDX3 in there. And that would have been a relatively useful system. - I could control a floppy and an old RD disk with it, and the MXV would provide memory and two serial ports.


  But, I didn't have a PDP11-23 (I got plenty of 11/23+s, but no plain 11/23s). An 11/23+, MXV11A and  RQDX3  together  draw too much power for that plan. And, I was in no mood to make the cables required to use the RQDX3 without the breakout board it gets used with in BA23 system boxes. And ST506 hard drives are loud and getting rarer every day. So scratch that idea - even though it would have allowed me to use RSX on it. 

  I decided to use an 11/2 processor (KD11-HA), and an MXV11A. The MXV11A has memory and boot roms on it, and it has two serial ports. One gets used for the console. The other one can be used to connect to a TU58 drive unit, which provides (slow and annoying) mass storage to the system. This won't run RSX (well, it would run unmapped RSX, but that's not my idea of a good time), but it could run RT11 and stand alone programs.

  A few articles back, I joked about how richly hated  TK50 drives were. Another DEC tape device that was not well liked was DECtape II - the  TU58. TU58s were little tape cartridge drives, that were used like a disk. Like their previous generation's namesake, DECtape, they were block addressable like disks and you could rewrite a block in the middle of the tape. TU58s connect via RS-232, and use good ole async serial ASCII to communicate with the system. TU58s were tiny (only held 512 blocks, 256K) and slow - very slow. They usually connected at 9600 baud, and seek times were...long.

  Since they were  controlled like disks, that meant that they were managed by a disk ACP on RSX . Disk ACPs are single threaded for all the disks that they control. If you mounted a TU58 using the same ACP as, say, the system disk, you would stall all disk activity on the system disk  whenever the TU58 needed a few minutes to seek to the end of the tape looking for a block...this is bound to make the phones ring...

  TU58s showed up as load media on assorted DEC systems. They were why VAX-11/730s and HSC50s booted slowly. A TU58 was used as part of the boot process on my old 11/725, and it took quite awhile. I managed to cut the amount of time booting required in half  by careful arrangement of the needed files on the tape so that rewinds were avoided.

  Real TU58 drives, however, are scarce these days, and almost all of the rubber capstans in them have deteriorated into gooey messes. Not a problem, though - lots of folks have written TU58 emulators, that connect to the serial ports and serve blocks back and forth just like a real TU58 unit does. Here's a couple of  good projects that illustrate use of them




   On the implementation side, Will Kranz has done a lot of work on TU58 emulation in general - lots of good info on his page at


especially about the new version of the RSP protocol, MSRP.

  Don North has also done a lot of work in the area, both on emulators and on tools to manipulate TU58 images. See some of his work at


  So, there's lot's of TU58 emulators available. But, (as far as I could discover), they all run on Windows, Linux, and a few run on stand alone microcontroller gizmos. I'm sure I've mentioned it before, but let me repeat again here, that I hate Windows and Unix/Linux with the incandescent, coruscating, ravening white hot soul searing  intensity of a billion exploding supernova antimatter hypergalaxies. Now that I think about it, I don't like Alphas, Itaniums or VMS-X86-64, either, but all that's another story... anyway, I will be thrice damned before I use Windows or Linux to support a DEC hardware project. I decided to write my own emulator, using my favorite platform - RSX11M+.  I'm a lot more likely to have RSX running here when I need to emulate a TU58 than I am Windows or Linux...

  To develop and test this program, I used a Micro-11/73 I already had. By luck, this old system came with a DLV-11 already installed - this was fortunate, since that is what RSX uses to communicate with TU58s. I connected a cable from it to my HP 4951C logic analyser, and from it, back to a DHV11 TT port back on the same 11/73 system. This way, RSX would be looking to see a TU58 on the DLV, and  my emulator program would strive to look like one on the DHV port. 

  The HP 4951C is a piece of gear I recently got on Ebay for $100, and it's invaluable for this sort of thing. It lets you see what's really on the wire, instead of having to add debug print statements and guessing which side wrote what, when. It cost $3500 back when it was new - but, there's not much demand for them anymore, since ASCII communications on RS232 style connections are pretty rare these days. If you have to do any work like this, I highly recommend acquiring one. It's from the golden age of HP test equipment.

















  So, basically, a program to emulate a TU58 boils down to doing a bunch of reads and writes.  Writes are easy - Just assemble a protocol message and send it out in one piece, via a QIOW.  Reads are another story. You can't really predict what or how much you're gonna get ahead of time, so you can't just do a read and wait for some number of characters.  The easiest way to code something like this would be to do one character reads and react to them as they come in, as the protocol requires. I didn't even try that strategy - doing that many QIOWs at 9600 baud or higher would be a surefire recipe for failure.

  Years and years back, Jamie Hanrahan (RIP) posted somewhere a description of his strategy for dealing with IO for a protocol on a serial line. I spent a couple of days looking for it -  couldn't find it. It may have been part of the comments for the DECUS UUCP project he worked on - I'm still looking for it. Anyway, as well as I can remember, it did a one character, non-blocking QIO read. When that read completed, it activated an AST that determined how many characters are in the typeahead buffer, then did a QIOW that actually read them. Those two steps repeat (getting the count, and reading that many characters) until the typeahead count returns 0). When the typeahead buffer is empty, the one character QIO is reissued, thus arming it for the next burst of incoming data. I inserted the incoming characters into a circular queue, and the code running at non-AST level de-queues them out one at a time as needed.

  The above strategy drastically reduces the number of QIOs vs the single character method - in this program, it does around 3 or 4 QIOs/data packet, vs. the 134 a full data packet would require, reading a character at a time. And, it doesn't get behind and choke itself to death, which is the whole point of doing it this way.

  Anyway, getting packets and then acting on them was middlin' straightforward. There were a couple of stumbles along the way, caused by the fact that every piece of DEC gear that accesses TU58s has its own set of quirks.

  For instance, RSP protocol has a boot protocol defined - but nothing I tested with used that - they all just did a standard read to block 0 when they wanted to boot. There is an enhanced protocol called MRSP that some TU58s support, with additional handshaking. TU58 hosts are allowed to query the TU58 if they support this protocol. XXDP and RT11 never queried about it. RSX queried about it - every other command  packet. It seems like DDDRV (the RSX TU58 driver) doesn't record the fact that we replied "none for me" to the MSRP query. Also,RSX occasionally "stutters" - emits two bytes of a packet, then starts over and sends the whole packet. RT11 likes to occasionally write FF into the unit and switch fields of a command packet for no reason.

  Anyway, it got done. I've tested this using RSX-11m+, RT11 and XXDP... Each one was a little different in behavior - for example, RSX never does a partial block read or write, while RT11 and XXDP do them both during boot and during normal operations. Here's a few screen shots of it in action...

  Running the program with raw command packet trace debug







  Here's RT11 booted from ETU58









  Here's XXDP booted from ETU58










So, here it is...



  Here's how to build it...

>mac etu58=etu58
>tkb
etu58/pr:0=etu58
/
suplib=fcsfsl:sv (or libr=fcsres:ro)
//
 
  To use, hook up the intended client device to a free terminal line and...

>run etu58
ETU>tt5:=xxxx.dsk,yyyy.dsk

 Where tt5: in the example is the  terminal line the client is connected to

xxxx.dsk and yyyy.dsk are example tu58 disk images.To use only one image,

ttx:=xxxx
  For only unit 0

ttx:=,xxxx
  For only unit 1

  There's one switch, /DBG, that will print out some debug info. It accepts a bitmask that determines what info to print.


BIt 0 - 1  prints out decoded incoming command packets
BIt 1 - 2  prints out raw incoming command packets
Bit 2 - 4  prints out comms related info
Bit 4 - 8  prints out queuing related info.

If you want more than one at a time, add up the values and enter as a decimal value
So, all four would be
  You get the picture...I like using /DBG:2

ETU>tt5:=disk0,disk1/dbg:15

 I like to run this with /DBG:2  - the raw command packets don't take up a lot of screen room, and you can easily see what unit, blocks and lengths are being requested. It was amusing to run this /DBG:2 while using disk1 as a shadow unit to disk 0 on RSX, and watch the load sharing occur (said load sharing was totally ineffectual, performance wise, since the requests are single threaded and not overlapped - still fun to watch). It's also fun to set the TU58 devices cached on RSX, and notice the reduction in IOs that get issued, as requests for blocks get served out of the cache.

  Any problems, let me know...I'm planning on improving this and testing it on more platforms (VAX is next), so I'd appreciate any feedback.