Thursday, June 8, 2023

Unlocking the index file - an example of how to remap APRs

  The other day, I was thinking about working with RSX internals. I've been doing it for a while now, and it occurred to me that I've gotten the bulk of my internals work done with just a /PR task and a call to $SWSTK. That provides access to the exec, pool and the IO page, which are the places  where most internal meddling needs to get done. It also serializes access to things, so nothing can change out from under you while you're doing...whatever it is you're in there to do.

  But that's a small fraction of what's on an RSX11M+ system these days. You've got secondary pool, directive commons, drivers, cache, and other tasks. All things that you may have occasion to look at or change.

  An example of this came up while I was working on another project. I wanted to access the Index File of the system disk, to analyze and fix things gone wrong. It turns out that by default, Files-11 disks on RSX are mounted with the Index File write locked - you can open it for read, but not for write. As well, you can't use some VFY switches (eg, /HD) on a disk with the Index FIle write locked. There are supported ways to mount the system disk with the Index File unlocked.  Instead, on that project, I just did logical QIOs to the disk and bypassed the file system completely. But I was left wondering, if there was a way to unlock the Index File without any special mounting going on.

  A little research showed me that the Index File is accessed by the system as a normally opened file, using the file system. The write locked status is recorded as a 1 in the F.NLKS byte in its FCB - File Control Block - presumably  NLKS stands for Number of Locks. No problem, I figured - find the FCB, change that byte to 0, and I'm on my way. FCBs are bound to be in pool, right? That's where all the interesting data structures tend to be in RSX.

  Well, no, not at all. Turns out that, in recent versions of RSX11M+, the Files-11 ACP (Ancillary Control Program) task, F11ACP, constructs some of the FCBs in its own task address space - and since INDEXF.SYS for the system disk is one of the first files opened at boot time, its FCB is one of the ones that is located there. So,/PR and $SWSTK ain't gonna do the business for accessing it. If I want to manipulate that byte, I'll have to access the data in the F11ACP task in memory.

  I'm sure that you've seen diagrams of what an RSX task looks like. To the average programmer, it looks like up to  64KB of contiguous bytes in memory (for the sake of simplicity, I'm leaving supervisor mode and I&D space out of the discussion) - a 16 bit linear address space. But these tasks coexist in memory with other tasks,  on machines with 18 or 22 bit memory address spaces - so the task address space has to be mapped somehow to the wider address space of the machine. I'm discussing a 22 bit physical space  here - if you're on an 18 bit machine, the process is pretty much the same, just with 4 less bits.

  This mapping is done via memory management hardware on the system. It uses 8 APRs (Address Page Registers) to map each (up to) 8 KB chunk of the task's address space, to a same sized  (up to) 8 KB sized chunk of real memory. The physical memory that is used is determined  by multiplying the contents of the APR by 64 - effectively, moving it over by 6 bits, thus producing  a  22 bit address - Then adding in the low 13 bits of the address from your program.

  Actually each APR is two registers - the Page Address Register, where the relocation value is stored, and the Page Descriptor Register, which contains the properties of that page. But, we don't have to mess with the PDR in most cases, so I'm skipping discussing it.

  Let's look at an example of how a task's 64KB could be mapped to physical memory. It's all text - if you think I have the patience  to draw all that ASCII art, you're crazier than I am. Picture it in your imagination. This is a crazy example, since a real task would have several of these  8 KB chunks arranged together in contiguous physical memory, to simplify loading, but, this is just an example of using the APRs to remap "virtual" 16 bit memory address to 22 bit physical memory.

  FIrst of all, the top three bits in a task address determines what APR will be used to map it. For example, task address 60000's top three bits are 011 - that means it will me mapped by APR 3. Und so weiter.

   Task     Top 3 bits   APR       Physical
  Address     (APR)   Contents     Address
000000-017777   0      000000    00000000-00017777
020000-037777   1      001000    00100000-00117777
040000-057777   2      000020    00002000-00021777
060000-077777   3      100000    10000000-10017777
100000-117777   4      004000    00400000-00417777
120000-137777   5      010000    01000000-01017777
140000-157777   6      000001    00000100-00020077
160000-177777   7      160000    16000000-16017777    

  Pretty wild, nicht wahr? RSX actually abstracts these two spaces, task virtual and physical, and the use of APRs, with two uber-constructs - Windows, for 16 bit task virtual space, and Regions, for physical space. But we're discussing the lowest level, raw stuff of reality here, 

  So, in our mapping example above,  let's consider how we construct a physical address from an example task address,  using task address 120366 (all addresses octal).  In binary,  it's   1 010 000 011 110 110. The  top 3 bits are 101, so it is mapped to the 22 bit physical space according to the contents of APR 5. APR 5 contains 010000. Multiplying it by 64 (decimal) gives us 01000000. Adding the bottom 13 bits of the task address, 366,  gives us the physical address of this word - 01000366.  Got it? What could be easier?

  Note that starting addresses  of blocks in  physical space are on 32 word boundaries, due to the multiplication by 64 (or, if you prefer to think of it another way, the six bit left shift) that will occur when the physcal address is constructed..

  . SInce we'll be fiddling with the APRs, we're gonna need to be running as a privileged task, mapped to the exec, pool and the device page. Since we're considering kernel mode now, we'll be dealing with two sets of APRs - Kernel and User. Here's the mapping used by the Exec, using the Kernel mode APRs.


 Kernel         Top 3  Kernel    APR       Physical
 Address        bits    APR    contents     Memory
 000000-017777   0     KISAR0  000000    00000000-00017777
 020000-037777   1     KISAR1  000200    00020000-00037777
 040000-057777   2     KISAR2  000400    00040000-00057777
 060000-077777   3     KISAR3  000600    00060000-00107777
 100000-117777   4     KISAR4  001000    00120000-00137777 
 120000-137777   5     KISAR5    -               -  
 140000-157777   6     KISAR6    -               -
 160000-177777   7     KISAR7  177600    17760000-17777777

  It uses the Kernel Mode Instruction Space APRs to do its mapping. It maps, one for one, the first 20 K of the executive to the first 20K of physical memory. The IO page gets mapped to the highest 8KB of the physical space. APRs 5 and 6 are used for assorted dynamic mapping functions.

  Now, let's have a look at a priviieged /PR task, before $SWSTK.


 User Mode      Top 3   User    APR        Physical
 Address        bits    APR    contents     Memory
 000000-017777   0     UISAR0 copy KISAR0   00000000-00017777
 020000-037777   1     UISAR1 copy KISAR1   00020000-00037777
 040000-057777   2     UISAR2 copy KISAR2   00040000-00057777
 060000-077777   3     UISAR3 copy KISAR3   00060000-00107777
 100000-117777   4     UISAR4 copy KISAR4   00120000-00137777 
 120000-137777   5     UISAR5 some value     8KB of memory  
 140000-157777   6     UISAR6 some value     8KB of memory
 160000-177777   7     UISAR7 copy KISAR7   17760000-17777777

  The /PR task, before $SWSTK, has copies of KISAR0-KISAR4 in APRs UISAR0-UISAR4. This meeans, the first 20K of the task maps the same physical  memory as the exec. Starting at task address 120000, is the task. APRs 5 and 6 will contain some value, that points to two APRs worth of typical physical memory. This is what contains your program in the /PR task - hope your code is smaller than 16Kb in length... And then APR 7 contains a copy of KISAR7, and points to the IO page.

  Now, when you do $SWSTK, you will be in Kernel mode, and using the Kernel mode APRs. They will have contents as described below...

 Kernel         Top 3  Kernel    APR       Physical
 Address        bits    APR    contents     Memory
 000000-017777   0     KISAR0  000000      00000000-00017777
 020000-037777   1     KISAR1  000200      00020000-00037777
 040000-057777   2     KISAR2  000400      00040000-00057777
 060000-077777   3     KISAR3  000600      00060000-00107777
 100000-117777   4     KISAR4  001000      00120000-00137777 
 120000-137777   5     KISAR5  copy UISR5  8KB of memory
 140000-157777   6     KISAR6  copy UISR6  8KB of memory
 160000-177777   7     KISAR7  177600      17760000-17777777

  You're executing in Kernel mode now, so you are using the Kernel mode APRs. The values in the User mode APRs don't matter. The only change from the normal settings of the Kernel mode APRs is that your task's APR 5 and 6 values have been copied into KISAR5 and 6. Your access to things is serialized now, so you can modify things to be more to your liking without thngs changing out from under you...if you know what you're doing.

  There are much better explanations and diagrams explaining all of this in the PDP-11/70 processor manual, and in the Task Builder manual, in the Privileged Task chapter. 

  Well, there's been a whole lot of typing now without any code or explanations thereof. OK, enough theory - we're actually tring to do something here. Heck. this blog entry is getting longer than the durn example program. Time to discuss what I actually did.

  So anyway, like I said, so long ago, I want to alter the byte value at offset  F.NLKS in the FCB for INDEXF.SYS, so I need the address of that FCB. A look at exec module THF11L.MAC gave me a general notion of how to proceed getting it, and a  perusal of the Data Structures and Lists manual tells me that this is recorded in the Volume Control Block of the disk, at offset V.IFWI. The VCB is pointed to by the UCB for the disk, at offset U.VCB. And the UCB is found by examining the chain of UCBs off of the DCB for the disk controller, looking for one with the right unit number. You find the right DCB by looking from the start of the DCBs at word $DEVHD. This is all pretty straight ahead link list following and value comparing. One wrinkle that affects finding $DEVHD and some other locations in the Exec, is that many of these locations are vectored. This means that their value as recorded in the system symbol table actually contains a pointer (a vector) to the real locations. You have to use the GIN$ (Get INfomation) directvive to make the Exec yield up their true values.

  Here's the directive DPB and its data structure....

        vecdpb: gin$    gi.vec,vecbuf,vecsiz

        vecbuf: .word   0           ;flags
        $kisr6: .word   kisar6      ;kernel apr
        $kisr5: .word   kisar5      ;kernel apr
        devhd:  .word   $devhd      ;device listhead
        pool:   .word   $pool       ;where everything used to go
        exsiz:  .word   $exsiz      ;size of the exec
        vecsiz = <.-vecbuf>/2

  And here's the invocation of the DPB.

        dir$    #vecdpb         ;Get executive vectors

  The table  of vectored locations contains a list of the symbols needed, specifiying the names you'll be using for them, and the directive looks up the real addresses for you.and fills them in.

  Anyhow, after doing the above,  the program does a middlin' amount of checking that the input made sense, and that this is a system where I can find the FCB and change it. Anything sketchy or exotic, it's outta there.

  So, like I said above, the program needs to traverse  the DCB linked list, starting at its head, $DEVHD, When we find a DCB with the correct name, it goes through the array of UCBs attached to that DCB until one is found that matches the unit number from the command line.

  The program does some more checks on the UCB stat field - makes sure that it's mounted, not mounted foreign, and not marked for dismount.

    bitb    #us.mnt!us.mdm!us.for,u.sts(r2) ;it it mounted right?
 
  Note that us.mnt has "negative" logic - bit set means not mounted, bit clear means mounted.. 

 Then, the program gets the address of the VCB (Volume Control Block) from the UCB at offset U.VCB.

        mov     u.vcb(r2),r4    ;addr of vcb to r4

 The VCB has the address of the Index File WCB (Window Control Block) at offset V.IFWI.

        mov     v.ifwi(r4),r4   ;addr of index file window block to r4

 And finally, the WCB has the address of the FCB (File Control Block) at W.FCB.

        mov     w.fcb(r4),r3    ;get addr of fcb from the wcb

  Given that address, the program does some checking to make sure that it has a value that is in the adress range of the F11ACP task. If it passes this scrutiny, we're finally there - mapping  another tasks memory using  APRs, the whole point of this post and program.

  We need to map the F11ACP task with one of  our task's APRs. Since we're a privileged task, you'll recall, we only have APR5 and 6 to work with. Our program is les than 8 KB, so it fits into APR 5. That means that APR 6 is not used - so it's the logical choice.

  But where do we need to map it? The first thing we have to do is to map it to the partition that contains the F11ACP task. Most every task loaded into memory exists in its own system created subpartition. We need the physical address of that partition. Fortunately,  the address of the TCB (Task Control Block) of the ACP task for a mounted disk is stored at offset U.ACP,  in the UCB of that disk.

      mov    u.acp(r2),r2    ;get tcb address of the acp

  Then we can get the PCB (Partition Control Block) address from that TCB, at offset T.PCB.

       mov    t.pcb(r2),r0    ;get pcb address

  After we save the existing contents of KISAR6...

      mov    @$kisr6,-(sp)   ;save current apr 6 value

  ...we can then load the address of the partition into KISAR6. The address of the partition is stored in the PCB at offset P.REL, in "APR" physical address style format - it's already shifted left 6 bits, so it can be loaded straight into an APR.

      mov    p.rel(r0),@$kisr6   ;map to the acp's partition...

  Alrighty then. Now any accesses we make via our APR6 (any task addresses between 140000 anf 157777) will go to the memory used by the  F11ACP's task  partition. Are we ready to access the lock byte now? Not hardly. The first thing that is in a task parition is the task header. We need to access it to find the address of a data segment of the task.

  There's two kinds of F11ACP tasks we could be dealing with - "regular" tasks, and I&D space tasks. These days, most everybody will be running I&D space F11ACP tasks. But, if you're on an 11 that doesn't have  I&D support, we have to be prepared for that eventuality. It makes a difference, because non-I&D tasks only have two address "Windows" (remember above when I said that task address space is abstracted by an uber concept called "Windows") and I&D tasks have 4 address Windows. We have to look into the task header (that we arecurrently mapped to) and get the starting address of the data window. 

  OK, so we will want to access the data windows in the task header. Set up to do that.

    mov    @#140000+h.wnd,r1   ;get the pointer to the window blocks
                               ;add 140000 to it, since we are now
                               ;accessing the partition (and thus
                               ;the header) by our APR 6

  h.wnd is the offset into the task header,  to the task window info.


  Then - which kind of task are we? We're still pointing at the TCB with r2, so we can check easily enough

    bit    #t4.dsp,t.st4(r2)   ;is this an i/d space task?

  If that bit is not set, then this is a normal task, and the data window is the first window.  We find the starting address of that Window 

; This case, we want to get the offset for window #0 - root d-space.
; We need to skip the first word, that is the count of windows in the task,
; and the offset to where the windows start ,w.boff

    mov   2+w.boff(r1),r4      ;window 0 for non I&D system


If it is an I&D task, the data Windows is the second Window. Add in w.blgh (len of a window)
 to the address expression to skip over to it.

40$: mov   2+w.blgh+w.boff(r1),r4 ;get the second window offset

  R4 now contains the offset in the partition to the desired data window. But, handily enough, it's an offset in APR, physical address  format - it's been divided by 64, upper bits trimmed off, and like that So we can add it to the location of the partition that is at p.rel(r), which is also in APR format, and get a value suitable for loading into an APR to point to physical memeory. We have no further need for the task header, so we can now map KISAR6  to  that data window.

    add    p.rel(r0),r4    ;add the  base physical address/32 of
                           ;partition
    mov    r4,@$kisr6      ;point kisar6 at the window


  Finally, we can think about using the address of the FCB that we retrieved, lo these many lines of code ago...well. almost. F11ACP uses APR5 to access that window, and the address it stored in the WCB reflects that. We are using APR6, so we need to add 20000 (octal) to that, so that our APR6 based access uses the right address.

    add    #20000,r3        ;adjust for access via KISAR6

   Finally, we're here - R3 points to the task address that maps to the physical location of the FCB for the Index File.  Check the FIle ID and Sequence Number, to be sure we have the right victim in our sigts. Then we can adjust the count in f.lcks(r3), and we're done. 

    mov     f.fnum(r3),fnum  ;save the file ID number
    mov     f.fseq(r3),fseq  ;save the file sequence number
    mov     f.nacs(r3),fnacb ;save the two bytes that have the...
                             ;..count of accesses and count of locks
    movb    f.nlck(r3),plocks  ;save current count of locks
 
  And then the remaining code does some safety checks and sets or clears, or just reports the value in f.nlcks(r3) as requested)

  When our changes to the lock count are done, all we have to do is restore APR 6, and fall through to the exit from the #SWSTK section. What could be easier?

    mov     (sp)+,@$kisr6    ;put original KISAR6 back

donex:  return               ;drop back to normal space


  Well, that's all done. Heck, it took longer to write about all of this than it did to code the program. You'll never find a worse, more confusing  and incomplete explanation of how memory management works on PDP11s.  I encourage you to seek out the DEC manuals (processor architecture manuals, and the Task Builder manual)  to get the complete whole story.

  Here's the program.



  Here's how to assemble and link it....assuming source is in directory [iul]

>mac iul,iul/-sp=[1,1]exemc.mlb/ml,[11,10]rsxmc/pa:1,[iul]iul
>
>tkb iul/pr/-cp,iul/-sp=iul,[3,54]rsxvec.stb/ss,[1,1]exelib.olb/lb

  And here's how to use it....

>run iul
Target?>DU0:       (whatever disk you're working on)


   Switches are
/CLR
/SET
/LIS

  So, to clear the locked bit,
.run iul
Target?>DU0:/CLR
Status 000001                       how did it go? 1 is success anything else is an error
Locks Before 000001          number of locks before change
Locks After  000000             number of locks after change
FNUM   000001                      File ID number - this better be 1....
FSEQ   000001                       File Sequence number - this better be 1 as well....
FNAC Before 000401          contents of word at F.FNAC(FCB). high byte is number of locks
                                                    low byte is number of accessors
FNAC After  000001             FNAC after change 
K6 before 020356                KISAR6 since before we fool with it
K6 after 013007                    KISAR6 while we fool with it
Window 122250                   addr of WCB

 To set it back on after you're done

>run iul
Target?>DU0:/SET
Status 000001
Locks Before 000000
Locks After  000001
FNUM   000001
FSEQ   000001
FNAC Before 000001
FNAC After  000401
K6 before 020356
K6 after 013007
Window 122250

To see what value it is and not change it...
>run iul
Target?>DU0:/LST
Status 000001
Locks Before 000001
Locks After  000001
FNUM   000001
FSEQ   000001
FNAC Before 000401
FNAC After  000401
K6 before 020356
K6 after 013007
Window 122250
or

Target?>DU0:

  The default action with no switches is to just list the state.

Once again, this is not supported, use at your own risk, This program changes mode to kernel and changes disk structures in memory - it's that sort of code. 

Friday, September 9, 2022

MicroVAX and 8086 smartcards for DEC Professional

   I recently came into possession of two unusual CTI bus cards, for the DEC Professional (you know, Pro-325s,Pro-350s, Pro-380s). These are apparently prototypes for never released options for the PRO line. One card has a MicroVAX II chipset on it, and apparently was for a DEC project called Meteor - a workstation with capabilities somewhere between a PRO-380 and a Vaxstation II.

  Here's pictures of it, front, and back and a close up of the card name








  It came with a memory daughter card - here's shots of it, back and front.






  The other card has an 8086 processor on it. It was apparently going to be used to allow Pro systems to run x86 software (MS Windows? On an RSX11 derived OS? The horror!).





 



  

  Presumably, "8086 SC" in the above means "8086 Softtcard". This card has had several dynamic RAM chips removed, which I am replacing before I do any testing.


Neither card had an option code on their retraction handles like normal PRO options do.

  When I get some spare time, I plan to put them in a PRO-380 and read/write the slot addresses for them and see what happens. Realistically, I don't expect to get very far without more info on them. If anyone out there has any more info or any software for these prototypes, I'd sure like to hear about it...


Sunday, August 21, 2022

Booting PDT-11/150 from TU58s and TU58 emulators

   A post or so back, I wrote up how to connect a TU58 (or emulator) to a PDT-11/150. Works great. But, you can't boot the PDT from a TU58 (or emulator - from here on, TU58 means TU58 or emulator - OK?), so you are still dependent on the RX01(ish) drives to get the system going. But there are a lot of PDT-11/150s out there with no software on floppies available, or with dead floppy drives. A way to boot from TU58 would enable people to load software or use them without the floppy drives.

  A look at the technical manual for the PDT, available at...

PDT-11/150 Tech Manual

 showed that the startup code for the PDT is in two 1K X 8 mask programmed ROMS on the memory board. I took a squint at the ICs in question, and saw that they were socketed - a good start - I'm in no mood to desolder a chip I can't replace if it gets destroyed in the process. They were mask programmed ROMs, part number NS82S2708. There is an EPROM  equivalent of this ROM, with the same pinout - the 2708. No sweat, I figured - sounds like a part from the common 27xx EPROM family. I can write new boot software, burn it to 2708s, and Bob's my uncle. I'll be done in an afternoon! Alas, that was not to be. Turns out that the 2708 is an early design EPROM. It requires three different supply voltages, and almost no hobbyist grade EPROM burners support it (for sure, mine doesn't). Pro grade ones that would support it are damned hard to find these days, and really expensive.

  So, that's about how these projects always go - a rush of initial enthusiasm, followed by a punch in the face from reality. Fortunately, this is an issue that has been dealt with already, by arcade video game  and pinball machine enthusiasts. Arcade games and pinball tables from back in the day often came with 2708 EPROMs. These hobbyists researched it and worked out that these can be replaced by 2716 2KX8 EPROMs, with a little work. The 2716 is a more typical 27XX part -  no exotic voltages required, and almost all EPROM programmers support it (most importantly, mine does). The 2716 is twice as big as a 2708 - you can program the 2708 image into the top or bottom half - the instructions below assume the code gets burned into the bottom half. You could even burn different programs into the two halves, and use a switch to select which one is active.  No need for that on this project.

  Here's the pinouts for the 2708 and the 2716

          2708                   2716
       
+--U--+                +--U--+
      A7|1  24|Vcc(+5V)      A7|1  24|Vcc(+5V)  
      A6|     |A8            A6|     |A8
      A5|     |A9            A5|     |A9
      A4|     |Vbb           A4|     |Vpp
      A3|     |*CS/PE        A3|     |*OE
      A2|     |Vdd           A2|     |A10
      A1|     |Program       A1|     |*CE/PGM
      A0|     |Q8            A0|     |Q8
      Q1|     |Q7            Q1|     |Q7
      Q2|     |Q6            Q2|     |Q6
      Q3|     |Q5            Q3|     |Q5
Vss(GND)|12 13|Q4      Vss(GND)|12 13|Q4
 
      +-----+                +-----+


Purty close, nicht wahr? Looking at the pinouts, you can see how to use a 2716 in a 2708 socket. You have to...

 - Connect pin 21 to pin 24 (2716 requires Vpp pin be at Vcc for reading)

 - Connect pin 18 to pin 20 (connect both output enable and chip enable to Chip Select)

 - Connect pin 19 to pin 12 (this selects the lower half of the 2 K ROM. Connect 19 to 24 to use
   upper half of 2716 instead. 

 - Don't connect pins 18, 19, and 21 to the 2716 from the board . The 2716 would be
   discomfited by the crazy 2708 required voltages (-5 and +12) present on those pins.

  The way the arcade guys accomplished this was to use two additional sockets, plugged into each other. On one of them, the bottom one, that will plug into the board, they did the first three steps - carefully soldering jumper wires between the indicated pins. On the next socket up, they got rid of pins 18, 19 and 21 (if you use machined pin sockets, you can heat the pins with a small soldering iron and push them out of the frame). Then they'd plug the 2716 into the top socket, plug the top socket into the bottom socket, and plug the bottom socket into the board where the 2708 once made its home.

  Here's a site that explains this. Unfortunately, it's been archived so it doesn't have its pictures anymore, but it's the best rundown of the solution I found.

Adapter for 2708 to 2716 swap


 And if you're looking for 2716s, you can also use a TMS2516 for a 2716. I didn't have any 2716s in the giant box o' salvaged obsolete through hole pin ICs, but I had plenty of TMS2516s, and they are pin and voltage compatible. There's a long story about why TMS2516s are equivalent to 2716s, but TMS2532s are not the same as 2732s, but, it's got nothing to do with this project, so I'll skip it (for now...).

  Anyway, enough of this hardware talk. That reminds me, what are the three most destructive things in computing? A system programmer with a soldering iron, a hardware tech with a patch tape, and a manager with an idea - and I'm two out of three!

  So, first step, I need to get the current contents of the factory boot roms, so's I can add the TU58 boot code into the normal initialization process. Hmmm...my EPROM programmer won't even read a 2708... Not a problem. The PDT tech manual reports that the boot roms occupy the addresses 170000-173777. I just booted RT11 from disk and used ODT to read the contents of that address range. This was better than reading the ROMs on a reader anyway - if I had read the two ROMs directly, on the EPROM burner, I would have had the additional step of combining the two outputs into one file, since the ROMS contain the bytes of each word - one ROM has the even bytes, one ROM has the odd bytes. The ODT just gave me the words as the PDT sees them, as combined words.

  Now I needed to convert the listing of the ROM contents into MACRO-11, so I can see where to add my evil magic. I have several tools that will disassemble an RSX object file (I usually use ORCAM), but nothing that will do it to a string of words like ODT produced. I wrote an EDT macro to translate the ODT statements in the form

173242/074524
173244/062560
173246/071440

to 

        .word     074524      ;173242
        .word     062560      ;173244
        .word     071440      ;173246

  Here's the result of that, in case there's any other hardware hackers out there that want an unmolested ROM listing for reference.

pdtrom.mac

 And then I assembled the resulting file to a .obj file - which I could then input to the ORCAM utility. Well, the results were a bit of a mess (when you look at machine decoded Macro-11 and you see a bunch of floating point instructions, you know you have some hand work to do) . Since this code was written to fit in a limited space, it had all kinds of programming tricks in it that confuse automated disassembly tools. Things like using the numeric value of other instructions as literals for arguments , and the like. Some of the coding stunts didn't seem to make a lot of sense - for instance, in places, the programmer created his own JSR - RTS technique using branches and jumps instead of just using the actual instructions. One good thing I found - even though the code was littered with these space saving tricks, there was over 600 bytes of unused space in the ROM, so there wasn't really any need for exotic tactics. That's plenty of space to load the TU58 boot routine.

  Here's the partially, poorly disassembled source. Feel free to correct and expand on my efforts. I only did enough to get this project done. Much of it is wrong. Some of the comments are in error as well. It might serve a starting point for someone else. It might serve as a source of amusement for those more skilled than I at reverse engineering.

pdtdis.mac

  A lot of manual disassembly of the resulting mess  let me find a place to put the boot code, and a place to let the user indicate that they want to boot the TU58 instead of the RX01 drives.. There is an unused space from 171630 to 172776. The boot routine is small, so I decided to load it at an even address, for convenience sake - 172000 - still plenty of space starting there.

  For a boot routine, I adapted the boot code from TU58FS, by Joerge Hoppe, who credits work done by Don North and the M9312 boot code. It's important to credit other people's work, nicht wahr? Here's the boot code I used. It's pretty self explanatory - sets the line speed for terminal line 1, inits the TU58, sends a boot request, gets 512 bytes and stores 'em starting at address 0 - then jumps to 0 and starts executing.

ddboot.mac

  Now I need a way to invoke that code when I want to boot from the TU58. There is a place in the original boot code where, if neither disk is loaded, the system asks you if you want to boot disk 0 or 1 - that gives you a chance to load the disk and try again. If you answer 0 or just carriage return, it retries disk 0. If you answer 1, it retries disk 1. If you answer anything else, it asks you again. It only took changing a couple of words to alter that behavior to be, if you answer anything besides 0 or 1, jump to 172000 and do the TU58 boot. I use a capital G...for Gleason...I recommend you do the same.

  It looks like this...

  Unload both disks and power on. The system will say

Read error

Type start unit number (0 or 1):  

  If you type 0 or 1, it tries to boot...disk 0 or 1. If you enter anything besides a 0 or 1, it will jump to the TU58 boot code. A banner will print

PDT-11/150 TU58 Boot LKG V01A01

and it will try to boot from the TU58. 

 Here's what it took to make this happen. In the boot rom, I changed the code at lines 5 and 6 in the code snippet below

  Original:
        CMPB    (R1)+,R2        ;typed a 1?
        BEQ     3122$           ;then boot from PD1:
        CMPB    (R1)+,R2        ;typed a 0? 
        BEQ     3120$           ;then boot from PD0:
        CMPB    (R1)+,R2        ;compare for space input
        BNE     3042$
3120$:  CLR     R3 
3122$:  MTPS    #340

*******************************
Modified:
        CMPB    (R1)+,R2     ;typed a 1?
        BEQ     3122$        ;then boot from PD1:
        CMPB    (R1)+,R2     ;typed a 0? 
        BEQ     3120$ 
        JMP     @#172000     ;not 0 or 1,jump to our TU58 boot code.
3120$:  CLR     R3           ;clear r3 to boot from unit 0
3122$:  MTPS    #340

  In binary, this change amounts to swapping lines

       .word   122102  ;173114
       .word   001351  ;173116

with
       .word    137     ;173114
       .word    172000  ;173116

 

  This boot rom should work with a real TU58, or a TU58 emulator. I have only tested it with ETU58, the RSX TU58 emulator I wrote a while back (see previous TU58 post for details). A few small changes were required to ETU58, to support a boot request from the PDT. This new version of ETU58 still supports booting from the disk like normal, and accessing the DD devices just like it did before.

  OK, how to put it all together? First, mod those two lines in PDTROM.MAC and save as file NROM.MAC (for new ROM) Then assemble it to produce a listing file. You don't need an obj file.  Use LISPAR (see a previous blog post for the Macro-11 List File Parser utility) to convert the listing to an Intel format load file, using the /SP (split) switch on the output to produce odd and even output files (that contain the odd and even bytes of the file, that go into odd and even EPROMs).

>mac ,nrom=nrom
>run lispar
MLP>nrom/sp=nrom

  That produces files NROM.EVN and NROM.ODD.

  Assemble DDBOOT.MAC to produce a listing file. Use LISPAR to produce odd and even Intel format files. Use /ADR:1000 to tell it where we want the code in "EPROM address space" - that will cause it to appear at 172000 when everything gets done.

>mac ,ddboot=ddboot
>run lispar
MLP>ddboot/sp=ddboot/adr:1000

  Edit nrom.odd and nrom.even. Replace the two lines that go to address 1000 with the two lines in each of ddboot.odd and ddboot.evn. Save the files and burn them to two 2716 or 2516 eproms. OK, that's a lot of work. If you're good with the state of the code as it is, I provide the current version of two files that need to be burned to the EPROMs below. 

NROM.EVN

NROM.ODD

Then replace the ROMs on the memory board with these EPROMs, inserted into the double scoket adapter described above. The Odd EPROM goes on the left, if you're facing the front of the machine - the even EPROM goes on the right. Make sure and match the "U" notch orientation of the ROMs - the "U" notch faces the front of the machine.

  Then, connect the TU58 or emulator to Terminal 1 on the PDT. Remove the disks from the two drives and power on. When it asks boot from 0 or 1 - enter any character, and it should boot from the TU58.

  So, sweet - everything works and we're all done with this project...well, not quite. When I went to put the lid back on the PDT...the double socket style EPROM adapter discussed above  sticks up too far for the top to fit.  I thought about this problem for a bit. I have a dremel tool and lots of cutoff disks....but no, as much fun as putting a hole in the top would be, it would look terrible and allow junk to fall down onto the memory board. A little more study showed that the only function of the upper socket is to make sure pins 18, 19 and 21 don't get connected to the circuit board. I can get the same effect  by cutting  pins 18,19 and 21 off of the EPROM, and keep the lower socket with the wiring adds on it. That lowers us by one socket. Why didn't I do this from jump street? Why did the arcade and pinballs guys use two sockets? Because, once you mangle up the chip pins, you can't erase and rewrite the EPROM  anymore....and believe you me, the new EPROM code took several edits before it worked.

 And, like I said above, my RSX TU58 emulator, ETU58, needed a few changes to support the boot request from the PDT. I named this version of it NTU58 (New TU58). Here it is...

ntu58.mac

To use, it's just like ETU58, from previous blog post. Build instructions are also in the file itself.

  If any of the 4 or 5 people in the world who are PDT-11/150 AND TU58  hobbyists try this, let me know if you have any problems.

  

Wednesday, June 22, 2022

DX protocol comms host software for DECmates

   Back in the day, DEC built some deskside/desktop machines based more or less on the PDP8, using aftermarket CPU chips from Intersil. They were marketed as word processing systems, and had operating systems similar to OS/8, called OS/78 and OS/278. There were a number of variants, and they had a variety of names - WT78,VT278, DECmate I,II,III and III+ and no doubt some others that I'm not aware of.  Hey, I'm a PDP-11 and RSX family collector - I don't know much about the 12 bit machines and their software - these names and OSes could easily be wrong or incomplete. Anyway, the word processing application they ran was WPS-8.

  These systems could support a communication protocol called DX, that allowed them to transfer and print files to/from a host machine, and do terminal emulation to log on as a user. Truth to tell, I don't know which members of this family of products supported DX - at least  some of them required an optional board, a DP278 , to enable it. There was also software that ran on the host machines to enable the use of these features. There were versions available for several of the PDP11 OSes - DX/11M (for RSX11M), DX/IAS, and DX/RSTS. Maybe more - these are the ones that I have heard of. 

  I had thought that the DX family of software was another lost product, but a couple months back, I was going through some old boxes, and found a copy of DX/IAS on a small 9 track tape. It might be of some interest, since it included the Fortran sources for the product. Although IAS is not widely used these days (or ever was, for that matter), the IAS system dependencies for this product look pretty minimal, and converting it to work on RSX or RSTS looks like it would be pretty simple. Usually, if there's no privileged kernel level code in an IAS program, and it doesn't use the timesharing subsystem, it's pretty easy to get it to run on RSX (and vice versa). Setting terminal characteristics usually needs a little attention, since the approaches to that are slightly different. If there's kernel level code involved - be ready for a challenge, since RSX and IAS are very different on the inside. But, there's none of that in this code.

  These days, It's tough to find anyone with a 9 track drive willing to read tapes  - and this was an 800 BPI NRZI tape - most hobbyists have the later, smaller tape drives that only handle Phase Encoded tapes at 1600 and 6250 BPI. I found a commercial site near me that could read 800BPI, at a charge of $75 a tape. Ouch...direct hit to the wallet...but, I hate to see a piece of DEC software disappear forever, so I bit the metaphorical bullet and got it read.

  They only did a so-so job of reading it. Fortunately, it was in DOS-11 format, written by FLX on an RSX system, so even though the headers and trailers from the tape were horribly mangled, the contents of the files and the 14 byte RAD50 string that identifies each one came out fine. The contents of the tape were all ASCII files - Fortran sources, a command file and a Macro-11 file, made it easy to see what was what. 

  If you get far enough to need one, there's a manual on using the DX software available on Bitsavers

DX User's Manual

  So here's the files from the tape. If any DECmate collectors get this to work on an RSX or RSTS host, let me know. And if you get it running on an IAS system, definitely let me know - since that means you're running an IAS system...


ACKNAK.FTN

BYE.FTN

CHRIN.FTN

DISPLA.FTN

DSKIN.FTN

DSKOUT.FTN

DXINIT.FTN

DXINS.CMD

FINTSX.FTN

FSUBS1.FTN

FSUBS2.FTN

FVALID.FTN

GETCHR.FTN

GETNUM.FTN

GETVAL.FTN

HEDOUT.FTN

INTSIX.FTN

INTTXT.FTN

LDSPAC.FTN

LOOKUP.FTN

MAKHED.FTN

MISC.FTN

NEWSET.FTN

PRINT.FTN

PUTHED.FTN

RCVMSG.FTN

RECV.FTN

REPLY.FTN

SEND.FTN

SETCHAR.MAC

SNDMSG.FTN

SPCFLX.FTN

SPECS.FTN

STROUT.FTN

TXTWP8.FTN

VALID.FTN

VALUE.FTN

WP8DEL.FTN

WP8DIR.FTN

WP8FLX.FTN

WP8LPT.FTN

WP8OUT.FTN

WP8PIP.FTN

WP8TXT.FTN

WPS2.FTN


Saturday, May 14, 2022

PDT-11/150 and TU58

   Recently, I've been playing around with a pair of old PDT-11/150s that I saved from a dumpster, years back. They are interesting machines - a PDP-11/03-ish processor, a pair of RX01-ish floppy drives, and a few RS232 ports, all in one (largish) desktop box. Back in the day, these got sold to do things like run a small office. These (and the PDT-11/130) could have been marketed as home computers early on, and beaten the IBM PC to the market, thus saving us from the horrors of Windows and the hideous ugliness of the X86 architecture. But, that's not what happened in this part of the Multiverse. Instead, the Dark Lord and Intel formed an unholy alliance, and together they made Orcs in mockery of Elves, and Trolls in mockery of Ents, and DOS in mockery of RT11, and NT in mockery of VMS, and on every side their foes fell reeling, defeated, one by one as they crushed them by sheer weight of numbers, their dread hosts darkening the plain (adapted from a USEnet post by Gary J. Robinson). But enough of that - I think everyone already knows how I feel about how things turned out.

  PDTs are fun to play around with but are....limited. The RX01 disk drives only hold 512 blocks each. One of them will be pretty much full by the time you get RT11, a few utilities, and BASIC loaded. The other one can hold data and programs you're developing/using.. It's still a little confining - you wind up with code and data scattered over a bunch of floppies, and have to change them a lot. And, as well, 8 inch floppies are getting old, scarce, and expensive.

  As well, PDTs are a little claustrophobic. Transferring files on and off of them is a bit of a challenge. Ethernet is right out of the question, and even if it had one, there wouldn't be enough disk  space for TCP/IP or  RT11 DECnet (and it's long lost, anyway - let me know if you come across a copy). Kermit would seem to be a natural  solution - after all, these things have an RS232 console, and 4 other RS232 ports. There's even a Kermit written specifically for these guys - KRTMIN, sized to fit on the available disk space. But, for whatever reason, I haven't been able to get KRTMIN to work when transferring files to the box. It will transfer files in the other direction just fine, but copying to the PDT errors out every time (let me know if you've ever gotten KRTMIN to work in that direction).

  So, it can be a pain in the sitz-platz. Pasting text  into the terminal emulator and capturing it on the PDT with PIP or KED can be done, but my terminal emulator chokes when I give it more than a few lines of text that way (yah, I know - I need a better terminal emulator). But this post isn't about my bad taste in software. Even if that worked great, it's a limited solution - it won't work for binary files.

  I was musing over this problem, and looking at the PDT-11/150 tech manual, when I noticed that the RS232 ports on this thing are very "DL11" like - they have an XMIT and RCV CSR, with the important bits defined in the same places as the DL11, and they have XMIT and RCV data registers, just like the DL11. That made me think of the TU58 - the disk-like tape drive  (or should I think of it as a tape-like disk drive?) that is controlled by a DL or DLV 11. I looked at the addressing of the registers for each terminal, and noticed that the CSR and vector for Terminal 1 is at 176500,300 - the same as the default location for a DL11 controlling a TU58. That's not really a requirement for the TU58 controller, but it did  made me think, I could probably get a TU58 to work on it.

  Normally, that might be interesting, but not that useful. I mean, who's got a working TU58 drive these days? And even if it was working, it doesn't add much space or significantly improve the ability to load files from another system (unless I had TWO working TU58s - one of them on another system).

  But, if Terminal 1 could drive a TU58, it could drive a TU58 emulator. I wrote one of those for RSX a while back (ETU58, described in another blog post). Coupling that with the TU58 large disk patch to the RT11 DD driver, it could support an emulated TU58 that had 65535 blocks. I could use SIMH to load files on the big disk image, copy it to an RSX system, and serve it to the PDT to Terminal 1 via ETU58. One drawback is the speed limitations of the PDT - Terminal 1's max speed is 2400 baud . But I don't plan to use this constantly for working storage - more as a place to put all of my RT11 resources, to load on and off the PDT as required, and a way to do file transfers on and off the PDT (transfer files to and from the virtual disk using SIMH, and then access the virtual disk on the PDT via the emulator.). The slow speed won't be that much of a problem for that. Besides, the RX01 drives on the PDT ain't all that speedy either.

  OK, should be no problem. I'll knock this out in 15 minutes or so, I figured. Well, it took a little longer than that. First thing I had to deal with is the fact that Terminal 1 on the PDT requires that pin 20, DTR, be asserted before it will transmit anything. Not a huge problem, I figure. Normally I'd just jump it to something that is asserted at that connector. But, no soap there - the PDT didn't assert any control lines, so jumping to other pins on that end was out. So, I'll need to get it from the other end. Except that, on the RSX system I was using to host things, MODEM support had not been SYSGENed in. That meant none of the modem control lines were asserted, and you couldn't set the terminal line  /REMOTE to get them to be.  None of the assorted other systems I had used with the TU58 emulator required this - they were all happy with just good ole pins 2,3 and 7, so it hadn't been a problem. But, no worries there - I eat SYSGENs for breakfast. I just changed the MODEM answer in the saved SYSGEN file and did another SYSGEN to get that feature turned on. Then I could set the terminal line /MODEM, and it would turn on modem signals. Then I connected pin 6 (DSR on the RSX end to pin 20 (DTR) on the PDT end. Now the PDT could transmit....but, since the RSX terminal line was set /REMOTE, now the RSX end  wasn't happy because  CD (carrier detect, pin 8) was not asserted. It was time for the universal fix for RS232 problems - on the RSX end I jumper 4 and 5 (RTS and CTS) together, and wired 6, 8 and 20 (DSR,CD and DTR together), and connected those three to pin 20 on the PDT end. Now both systems   could transmit and receive, both directions -I had achieved full duplicity. Such was life before USB. If you're using a different TU58 emulator, your experience will probably be quite a bit different. But if you're using another TU58 emulator, you probably know what's required.

  But enough about how we wired things up back in the Stone Age, when all we had was a soldering iron and a well worn copy of "Technical Aspects of Data Communication" by John McNamara to get computers talking to each other.  I connected the cable to Terminal 1 on the PDT, and tt5: on the RSX system. I set tt5 to /remote and 2400 baud.

>set term tt5:/remote/speed:2400

  I set the PDT terminal 1 to 2400 baud

.RUN SPEED.SAV
*/t:1/s:2400.               (don't forget the "." - otherwise it's an octal number)

 Then I made a 512 block data file, tu58.dsk, for the TU58 emulator. - doesn't matter what's in it. I ran the TU58 emulator on the RSX system

>run etu58
ETU>tt5:=tu58.dsk

 Then on the PDT, I tried to INIT the emulated TU58.

.INIT DD0:

  And...nothing. It just hung up forever. That was a bit of a disappointment. I thought we were there...So I broke out the HP 4951 data scope and had a look at what was happening. Turns out that the DD driver on the PDT starts up slightly differently than the other PDP11s I've used ETU58 on. But, no worries - I modded the startup code in ETU58 to accommodate this difference, and tried it again. This time it was all roses - worked great. The "disk" INITted, copied files to and from and did directories flawlessly. In any  case, your mileage with other TU58 emulators may vary.  Here's a copy of ETU58.MAC, in case you want to host disks on RSX - but, it's not required to serve images to PDT-11/150s - I reckon that most any other TU58  emulator would work as well.

ETU58.MAC

  OK, now all I had  to do is patch the DD driver to allow big virtual TU58 tapes. Various folk have done work in this area (Jörg Hoppe, Don North, and others), not sure who was first. Their work on this showed the size of the disk is stored only in the driver, and nowhere on the disk, so that's convenient. All I had to do is patch the DD.SYS driver file to make it support bigger disks. How to do that is covered well at 

TU58FS - File Sharing with a DECtape II simulator

 thanks to Jörg Hoppe. I should mention, that the location of the word to patch in the driver  varies a few bytes  plus or minus, between various RT versions - but it's easy to identify what word to change. In a stock DD or DDX.sys stock it will typically be within the first 50 or so bytes, and have the value as a word of 512. I patched up the driver, and made some big images. INITed them and copied things on and off - worked great 

So...

Copy DD.SYS to the RSX system where it's convenient to ZAP the 512. to 65535 (octal 1000 to 177777). Copy the patched DD.SYS to the PDT as DD64K.SYS. Also make a 64K block file to serve as the virtual disk.

On the RSX system...

PIP rt64kdisk.dsk=nl0:/bl:65600. (make it a little bigger than 64K, just in case)
PIP nt64kdisk.dsk/EOF                (set end of file to the end...of the file).

RUN ETU58
ETU>TT5:=rt64kdisk.dsk

  On the PDT...

.UNLOAD DD
.UNPRO DD.SYS
.RENAME DD.SYS DD512.SYS (you may want the original someday - you never know...)
.COPY DD64K.SYS DD.SYS

(reboot here - would a simple LOAD DD have been enough? dunno...I rebooted).

.INIT DD0:
DD0:/Initialize; Are you sure? Y

.DIR DD0:

 0 Files, 0 Blocks

 65467 Free blocks

  Lots more blocks now than 512...


So now I can  copy most everything I need from RT-11 to a large TU58 image, using SIMH, for the sake of speed and convenience,  and then have it online to the PDT-11/150 all the time - this will cut down a lot on the need to constantly swap floppies in and out. 

 If you give it a try, let me know if you have any problems. I like helping keep DEC"s quirkier systems on the air.




  

Wednesday, April 13, 2022

VAXstation 1 and PDP11 QBUS systems software install via ethernet programs (finally)


  Note - new versions of some of these programs are coming out this month. Watch for them as a new blog entry


   OK, after all of the previous confusing blog posts explaining how they work, here's some of the programs that actually do something.

   When this project started out all I wanted to do was load a disk image onto a VAXstation I without any physical hardware gyrations (floppies, tape drives, disks loaded on one system and moved to the VAXstation I, etc - you know, the usual).  As time went by I realized the same principles would also work for loading disks on QBUS PDP11's, and I actually have more occasion to do that than I do fooling with VAXstation I's - as a result, I wrote  PDP11  versions of the programs as well as VAXstation I programs, and have used them a heckuva lot more.

  So, for this project, there are  server programs and client programs.

 Server programs  run on a normal host system. There's nothing exotic or hardware dependent about the server programs - they are normal tasks/images that use supported techniques to send and receive packets on the ethernet and save/restore/check them to a disk file. Server programs' names all start with LOCAL (for VMS based server programs) or LCL (for RSX based server programs). The second part of the server programs name is determined by what function is being performed by the corresponding client. So, the VMS server program that serves blocks to a client program, that is writing the blocks to the target disk is named LOCALWRITE. The VMS server program that accepts blocks transmitted from the client, to save into a local disk image file, is called LOCALREAD. The VMS server program that compares a local disk image file to the contents of a disk on the target system is LOCALCHECK. In like wise, the RSX versions of these server programs would be called LCLWRT,LCLREAD and LCLCHK (but they haven't been written yet - they're on the way).

  Client programs are...different. They are not normal tasks/images. They run on the bare metal of the target system with no OS (heck, if you had an OS on the target systems, we wouldn't be doing all of this in the first place, nicht wahr?). They are assembled and linked special, and then get loaded onto the target systems via DECnet MOP, booting, as a system image. They are named similarly to the server programs - they start with REMOTE or REM (VMS or RSX), and the second part of the name refers to the function they are performing on the target - REMOTEWRITE is getting blocks off the ethernet from LOCALWRITE, and writing them to the MSCP disk on the target system. REMOTEREAD is reading disk blocks from the MSCP disk on the target and sending them to LOCALREAD to be stored in a disk image file. And like that for the check function, and for the RSX images.

  Clear so far? And here I want to point out that the server programs are not OS specific - that is, LOCALWRITE.EXE, running on a VMS host system, will work just fine with REMWRT.TSK running on a PDP11 system. Same goes for LOCALREAD.EXE and REMREAD.TSK, and LOCALCHECK.EXE and REMCHK.EXE. And the same symmetry will hold when LCLREAD, LCLWRT and LCLCHK server programs are written on RSX - they will support VAXstation I clients as well as PDP11 clients. But, like I say, currently, the only server programs are for VMS. Why am I writing this before the RSX server programs are done? Because I ain't writing them right away since I want to work on some other projects for a while....I'll eventually get back around to it. And probably some MicroVAX II client programs as well - that would be more widely useful than the VAXstation I ones. Lots more MIcroVAX IIs out there than I's. Anyway...here's the programs thus far...


  Update - this version of remoteread.mar and localread.mar has bugs and is being withdrawn. A new version is available in a later post on this blog. remotewritewrite.mar and localwrite.mar are withdrawn here, since new versions are available - they're in the later  blog post as well.

  Additional update - remwrt.mac has also been updated, and is available in a later blog post. The version listed here is withdrawn.


localread.mar

localwrite.mar

localcheck.mar

remoteread.mar

remotewrite.mar

remotecheck.mar

remread.mac

remwrt.mac

remchk.mac


  OK, so how do you use 'em? Here's an example. Suppose you have a MIcroPDP11 with an 11/73, an RQDX3, RD54 and DEQNA in it (a DELQA would probably work too - their software interface is not that different). You'd like to load software onto the disk. The first step would be to use SIMH or some other simulator to install RSX onto an RD54 disk image. If it's not already there, copy that image file to the system you'll be using as the server. Then, you'd edit remwrt.mac, the PDP11 client task for writing to the client disk, to indicate what file on the server system that image is in. The name is located at label filnam.

filnam: .ascii  /dua1:[mvax1]zombie.dsk/

  Save your change and assemble remwrt.mac

  >mac remwrt=remwrt

  Task build it...

>TKB
TKB>remwrt/-hd=remwrt
//
CORSIZ=32
STACK=0
PAR=GEN:0:160000
UNITS=0
/
>
  Ya gotta task build it funny, since it's not going to use an OS - its task is just a bunch of code that gets loaded. into memory like a system image.

  Alright, now you've got a system image that will request the disk image you want loaded. Next step, is to create or modify a DECnet node definition for the client node, on the system where the remwrt.tsk you just built is located. For this step you'll need to know the ethernet address of the target system. If you don't know it, read it off the card, or just try to boot it and read it from the failed load error messages on system consoles.  

For this example, let's say it's 08-00-01--02-03-04, and use the name zombie, as examples.

>
NCP>set node zombie addr 10.111 (address doesn't matter - make it unique for your site)
NCP>set node zombie service circuit QNA-0 hardware address 08-00-01-02-03-04
NCP>set node zombie  load file du1:[test]remwrt.tsk
NCP^Z
>

  Or you can use LANACP instead of DECnet to do this...if that's what you want...you know what to do instead.

  If you already have the node defined, you don't need to do all of this jazz every time, natch - just change it to match your current situation - usually just the filename changes.    

  OK, all set, client-wise. Now go to your VMS system and run LOCALWRITE.EXE. It will sit there waiting for a request from a client system. Go to the client system and tell it to boot from the ethernet, however it wants that done. On my test 11/73, I CTRL-C out of the autoboot and tell it to B XH0:

  When you do that, the system where REMWRT.TSK is, and where you set up  DECnet to load it, hears the MOP boot request, and  loads it into the client system. When it runs on the client system, It will then set up the RQDX and the ethernet card, while printing a truly eye watering amount of diagnostic messages, left over from development and that I'm too lazy to remove, then starts up and broadcasts a request for a server to feed it zombie.dsk. LOCALWRITE.EXE on the VMS system hears that request and starts sending blocks, which the client receives and writes.  You get a progress message on both systems every 1024 blocks or so. After a couple hours, it will print a done message, and now you have a runnable PDP11 - assuming you've done everything right. A couple hours is significantly faster than using something like VTserver or PDP11GUI to do the transfer on an asynch RS232 port.

  Here's what it looks like, up through the first 1024 blocks...

Commands are Help, Boot, List, Setup, Map and Test.
Type a command then press the RETURN key: B XH0

Trying XH0
Starting system from XH0
remwrt
deqna card vector
01FC
new deqna card vector
006C
old psw
FFE8
new psw
FFE8
init RQDX3 via RIP write
read and display RSA
0B40
current step value
0800
current step bit was set in rsa - write next value to rsa
step
8000
r3 shifted
1000
read and display RSA
1080
current step value
1000
current step bit was set in rsa - write next value to rsa
step
9900
r3 shifted
2000
read and display RSA
2000
current step value
2000
current step bit was set in rsa - write next value to rsa
step
0000
r3 shifted
4000
read and display RSA
4134
current step value
4000
current step bit was set in rsa - write next value to rsa
step
0001
r3 shifted
8000

RQDX3 Init complete
dskini returned
disk online touch IP
4000
0001
0000

disk characteristics
media type id
2564
4036
unit size
0004
BFA0
volume serial number
00BC
614E

RQDX3 Online complete

dskonl returned
Ethernet Address is...
08  00  2B  06  92  6D

starting DEQNA csr
1032
starting DEQNA csr decoded
CSR Set
SR  XL  RL  OK
CSR Clear
RE  NI  BD  IE  XI  IL  EL  SE  RR  CA  R2  RI
reset DEQNA csr decoded
CSR Set
XL  RL  OK
CSR Clear
RE  SR  NI  BD  IE  XI  IL  EL  SE  RR  CA  R2
Post reset DEQNA csr
1030
enter setup tmit
DEQNA setup interrupt
setup tmit fired
seek
send initack
got init packet
block delivered
0000
0000
block delivered
0000
0400
(note- the block delivered count is in hex, so 0000 0400 = 1024 decimal)

  Correspondingly, here's the message that occurs on the server system running LOCALWRITE.EXE. The double write of Newblk: 0 is a bug I haven't bothered to run down yet. Note that these counts are in decimal...so 1024 = 1024.

$ run localwrite
dua1:[mvax1]zombie.dsk
Newblk: 0
Newblk: 0
Newblk: 1024


  To write a disk image onto a disk in a VAXstation I, it's very similar. Edit REMOTEWRITE.MAR and change the file name at label filnam to match the name of the image file to use.

filnam: .ascii  /dua1:[mvax1]undead.dsk/

  Save the edited file. Assemble it.

$ mac remotewrite

  Link it, sort of funny, so it's a system image.

 $ link/notrace/nodeb/system=0/header remotewrite

  Make a system, image with SIMH.

  Use NCP and make an entry, just like for the above example.


$ mcr ncp
NCP>set node undead addr 10.112 (address doesn't matter - make it unique for your site)
NCP>set node zombie service circuit QNA-0 hardware address 08-00-01-02-03-04
NCP>set node zombie  load file dua1:[test]vaxzombie.dsk
NCP^Z
$

  OK, all set, client-wise. Now go to your VMS system and run LOCALWRITE.EXE. It will sit there waiting for a request from a client system. Go to the client system and tell it to boot from the ethernet, however it wants that done. Things proceed from there just like for the PDP11 case.

  Alright, writing disk images works pretty good. But after a while I wanted to save the work I'd been doing on  these systems. It occurred to me that relatively minor changes to the code could produce client and server programs that worked the opposite direction - read blocks from the local disk and send them across the network to a server program, to save in an image file.

  To do that, it's almost exactly the same as above. Edit remread.mac or remoteread.mar, and change the filename at label filnam: to whatever you want to call the resulting image file. Assemble and link. Make NCP changes to get it loaded when the client wants to boot. Run LOCALREAD on a server system. Boot it up and off it goes. When it's done, you'll have an image file of the contents of the disk on the client.


  OK, there's the basics - read and write. But, I'm pretty paranoid - how could I be sure that the entire disks were getting read and written OK? I wrote a set of server and client programs that will compare the contents of a client's disk to a disk image file. LOCALCHECK.MAR, REMOTECHECK.MAR and REMCHK.MAC.

  Ya set 'em up just like the above - edit the filename at filnam, of the image file to check the disk against. Assemble & link, set up network boot. Run the server program and boot the client system via ethernet. The programs will start comparing block checksums. You'll get progress messages, and eventually a successful exit...or lots of messages about disk differences. Either way, good to know.

  And that's our story thus far.