Tuesday, February 17, 2026

Tracing execution of a program without sources on RSX11M-Plus

   Recently, a colleague, Mark Matlock, has been working to correct some issues in a  program that runs under RSX11M-Plus. No sources are available for that program, which presents some challenges.

  If this was being done under VAX/VMS, it would be trivial. There, you can RUN/DEBUG any program, whether it was linked /DEBUG or not. and get control of it, set breakpoints, examine data and instructions, and modify both as needed. Once you do that to see what needs to be changed, the PATCH utility allows you to use the information gathered via DEBUG and easily make permanent changes to the executable image.

  But, if "ifs" and "buts" were candy and nuts, we'd all have a happy Christmas. This program runs under RSX, where the tools are not so rich as in VAX/VMS. 

  The RSX family has a middlin'  selection of debugging tools available. Of the ones I've used, there are the good, (PDP-11 Debug, and Everhart's DDT22),  and the not so good (ODT, aka Odious Debugging Tool - hope you have up to date printouts of your program's listing, map files, and an HP16 calculator handy). But all of them require task building the tools into the executable task image. That's not gonna happen here, since there are no source or object files available.

  I gave this some thought. There is a bit in the Processor Status Word (henceforth to be referred to as the PSW) called the T-bit. When set, it causes the processor to trap through vector 14 after each instruction is executed. A while back I'd written some code that runs in a no OS environment, that used the T-bit trap to interrupt execution after every instruction, and copy the instruction to a circular buffer. Something like that would be handy here, to see where the problems spots were and what was being executed when they occurred. (detailed in my blog entry "Using the PDP11 T-bit Trap).

 But that code ran on a machine with no OS (Rules? There are no rules!). There are no rules!

 This time around, we have to follow the RSX rules to get the work done.

  So, first, we need someplace to write the address and instruction values as each trap occurs. The easiest place to store data while running in a system sort of mode (like via the trap vector) is Pool, the Dynamic Storage Region, aka primary pool. It's a relatively scarce resource, but not as scarce as it used to be since secondary pool and directive regions came along. We can allocate around a couple thousand bytes, on most systems, by calling kernel routine $alocb in a privileged task.

         mov     #poosiz,r1      ;size of data block
         call    $alocb          ;allocate it from pool
         mov     r0,pooadr       ;store its address in pooadr

  So that's where the data will be written. OK, so now we need a place to put the code that is pointed to by vector 14, and will be run whenever the task executes an instruction. My first impulse was to also load that into Pool, since that's the way we did things way way back in the day, when if you said RSX, you were talking about RSX11D. But, way back then was before Instruction and Data space separation was implemented in RSX. I&D space means that, your code uses a different mapping of 16 bit addresses to 18 or 22 bit physical memory, depending on whether it is executing code or accessing data. It's a great feature that can double the amount of space available to your task...if it's got the right mix of data and code.

  That means, for instance, that the 4000 in the two lines of code below don't necessarily access the same physical memory when they access 16 bit virtual address 4000.

4000:    CLR R0
              MOV    #240,4000

  The PDP11 can tell by the access mode of the instruction whether it's an I space (the fetching of the instruction in the first line) or D space (the second arguent of the second line) access.

 There are 3 sets of memory mapping registers on the larger and newer PDP11s. They're called APRs - Active Page Registers. The three sets are for the 3 processor modes - Kernel, Supervisor and User modes. Each mode has 16 APRs. Each set of 16 APRs is divided into 2 sets, one for mapping instructions and one for mapping data - I&D, ya dig?  Each APR maps up to 8 KB of 16 bit space to up to 8 KB of physical memory. So, in each mode, 64KB of instructions and 64 KB of data can be addressed at a time.

When you are executing via the T-bit trap, your code is using  RSX's kernel mode  APRs. The RSX kernel maps primary pool using Data space APRs - so if you copy code into primary pool and try to execute it from there, it will actually look somewhere else for it - wherever the Kernel I space APRs are pointing. But, there's an easy alternative. There is a region RSX maintains called ICB pool. It is used for Interrupt Control Blocks, which contain some code along with some data. Since they have to be accessible both as code and data, RSX puts them into low core, where the kernel has set the I and D APRs to both point to the same physical memory locations. The upshot of that is, I can load code into ICB pool via MOV instructions, which use the Data  APRs, and then  trap to the same location and execute that code there, which uses Instruction APRs.

  ICB pool is also a limited resource, like primary Pool, but we only need a handful of bytes of code to get the PC and they value it points to at each trap written to primary pool. ICB pool is allocated thusly

        mov     $icavl,r0       ;icb pool listhead to r0
        sub     #2,r0           ;subtract 2, since that's how it's done
        mov     #codsiz,r1      ;size arg goes into r1
        call    $aloc1          ;call general pool allocator
        mov     r0,codadr       ;store the address           


  Shucks, we're getting into TL;DR territory and haven't really talked much about the code yet. I'm thinking of changing the name of this blog to TL;DR.

  Alright, alright alright...I wrote a  program that allocated some Pool and ICB pool. Logical names are created (POOADR and ICBADR) that contain the addresses of the two allocated blocks, so you can see what they were for debugging, and so that the other programs involved  in this project will know where they are

TRACSPACE.MAC

TRACSPACE.CMD

  Next comes the program that prepares the Pool area as a circular buffer, then loads the routine that saves PC addresses and instructions into that Pool buffer, into ICB pool, and sets vector 14 to point to that routine. It's a separate program from TRACSPACE, because you can rerun it to clear out the data space and reload new tracing code, without reallocating Pool, which if you do too many times, you'll run out - and that's bad.

TRACLOAD.MAC

TRACLOAD.CMD

  It would be good to be able to stop and start tracing - you only have so much room in the curcular buffer, so it's useful to be able to stop and start the program tracing as needed. TRACON allows you to stop, start and check on the status of tracing. It also allows you to specify a number of instructions to delay before starting to trace. The default state of tracing is OFF, figuring that more often than not, you need to get the program being traced to some known state  before you start.

TRACON.MAC

TRACON.CMD

  These three programs get the system loaded and ready to start tracing. Now all I need is to set the T-bit in the PSW of a program you want to trace.

  I could have written a program that finds the task in memory and modifies its PSW. But that sounded like a lot of work. Instead. I'd like for the program to start with the T-bit already set.

  I had a look at where a typical task gets its initial setting of the PSW. It turns out that there is a word in the task header (the header is a part of the task's file on disk, that gets loaded into memory when a task is installed, loaded and run) that is documented as the initial value of the PSW, at offset H.IPS in the header. That sounded good - I thought to myself, "almost done". All I had to do is modify the task image so that word has the T-bit set, and it will wind up set and trapping when it runs. I wrote TRACTASK.MAC, a program that opens a task file, locates that word, and sets or clears the T-bit, bit 4. Most tasks have the value 0XF00F in that field (some have 0xF80F - not sure why - that 8 would indicate use of the alternate register set on some PDP11 models - but AFAIK, no DEC OS uses the alternate register set. Anyway, that's not what we're here for now...).  After it's set, it is 0XF01F (or 0xF81F, depending).  Here's the program that makes that change in a task

TRACTASK.MAC

TRACTASK.CMD

   I INStalled the modified task and ran it. Then I checked the circular buffer in pool. Nothing. Well, that was a disappointment. I had thought I was done. Even stranger, I dumped the task file and had a look at the header to make sure the T-bit was set. It wasn't. I assumed I had just mistakenly not set it, or there was a problem with my setting program. I set it again, and dumped it out to make sure the T-bit was set. It was. I re-installed it and ran it again. Still no results in the Pool buffer, Even crazier...I dumped the task file out again. The T-bit in the header was now clear again. I repeated this test a few times, and concluded that the act of INStalling a task causes (at least) that bit in the header to be rewritten in the task file - who knows what else. I've never heard that INStalling a task can cause it to be modified, but, there you have it. 

  I had a look at the sources for INS.TSK. I found a line of code that overwrites the value of the PSW in the header in memory when the task is being installed.

        MOV     #170017,H.IPS(R4)  

   And further on, there's some complicated logic that writes the header back to the task file. Crazy, nicht wahr?

  Anyway, not a real problem. I copied INS.TSK to INZ.TSK. Then, in the copy, I  ZAPped the 3 words  of that instruction to octal 240 (the NOP instruction, which does nothing at all, which is one of my favorite things). I installed INZ.TSK with a different task name. I created a new task to do all this, because if I messed up patching INS.TSK....how was I gonna INStall the original unpatched one?

>INS INZ.TSK/TASK=...INZ

  and then used it to install a test program to be traced

>INZ TEST.TSK

  Then I ran the modified program again, and checked the pool buffer for traces. Still nothing. At this point, this was going like most of my internals projects go.

 Internals Projects Life Cycle 

But, I'm too stupid to know when to quit, so I GREPed every .MAC source file on the system for H.IPS, figuring that RSX was trapping it somewhere else as well as in INS.

  And I found it....in three separate places in LOADR.MAC (the curiously named source file for ...LDR).

  Two instances of 

       CMPB    #17,140000+H.IPS ;SUCCESSFUL READ ?

  and one of

        CMPB    #17,H.IPS(R0)   ;VALID TASK IMAGE?

  17 octal is bits N, Z V and C set...and T bit clear. In all three of these places, if the CMPB is not equal, they branch off somewhere that fails the load of the task. Geez, DEC sure went to a lot of trouble to avoid letting people load a task that has the T-bit already set. In any case, copying LDR.TSK to another file with a different name  and trying to install it and use it, like I did with INS.TSK, wasn't going to work here. LDR is special - it doesn't get loaded and run like a regular task (an old Zen koan for RSX - "what loads ...LDR?). It's INStalled and FIXed by VMR during a SYSGEN. Little known FIX fact - after a task in INStalled and FIXed, you can delete its .TSK file and it will still run fine). I could patch the LDR.TSK file and then VMR it into a new system...but, if something goes wrong I wind up with an unusable system until I SYSGEN a new system. Not a good outcome...

  Instead, I decided to patch ...LDR in memory. It's FIXed, so it's not going to move around or reload from its disk image, so in memory changes will stick until a reboot. I could have used OPE to patch it, but that would require using specific addresses. Plus OPE is a pain in the sitz-platz to use in a command file. Instead, I wrote LDRFIX, a program that searches for the three places where ...LDR monkeys with the initial PSW, and NOPs them out. Searching for these instances give this a tiny chance of working on some other version of RSX. Doing it as an in memory patch means that if  sometihing goes wrong, a reboot erases those changes and gets you back to normal - without a big SYSGEN or VMR repair effort.

LDRFIX.MAC

LDRFIX.CMD

  OK, now I run the program I wanted to trace (so long ago in this story). and, success!. PCs and instruction  get recorded for every instruction executed.

  It's a tortured complicated path we took to get here, so let me try to sum it up a little bit..  

Have a look at your free Pool space and ICB pool space. Use RMD.

If needed,modify defintions POOSIZ in TRACSPACE, TRACLOAD and TRACREAD. Make sure that it's not too big for the space available, and is the same in all 3 programs. Check the  value of ICBSIZ in TRACSPACE, make sure it's not too big.  Assemble and link them, and  TRACON and LDRFIX as well.

@TRACSPACE
@TRACLOAD
@TRACREAD
@TRACTASK
@TRACON
@LDRFIX

  TRACSPACE, TRACLOAD, and LDRFIX need to be run once after each system reboot. Task INZ.TSK needs to be created by zapping INS.TSK, just once. It needs to be INStallted once per boot. See below for details.

  TRACLOAD can be rerun later if desired,  to clear out the Pool buffer and reload the vector code.

  TRACTASK needs to be run once on a task you want to trace, to set its initial PSW. Be aware, that if you forget or have some reason to INStall it using INS instead of INZ, the T-bit will be cleared by INS, and will have to be reset again with TRACTASK.

  TRACON needs to be run to control the state of tracing. SInce the default state is OFF, you'll have to run it to set the state to ON. It needs to be done once per boot.

TRACREAD can be run to have a look at the contents of the Pool buffer. Or you can just use OPE /KNLD to view the buffer, using the value of logical POOADR (sho log pooadr/all).

RUN TRACSPACE to create blocks in Pool and ICB pool.

RUN TRACLOAD to load the trace code and point  the T-bit vector at it, and prepare Pool for the circular buffer.

RUN LDRFIX to to patch out the T-bit tests in ...LDR 

RUN TRACTASK to toggle the T-bit in the task to be traced's header. It toggles the bit on and off each time you run it on a task. 

Copy [1,54]INS.TSK to INZ.TSK. Use ZAP in absolute mode to depost 3 words of 240 at location 14706. Zap's a funny little utility - you can't enter the filename on the command line, gotta just enter ZAP, then give the filename and /AB switch to the ZAP> prompt. Anyway....

>ZAP
ZAP>inz.tsk/ab
_14706/
000:014706/ 012764
_240
_000:014710/ 170037
_240
_
000:014712/ 000014
_240
_
000:014714/ 016401
_X
>

  Once per boot, Install INZ.TSK as task ...INZ

>INS INZ/TASK=...INZ
>

Then, once per boot, use INZ to INZtall the task you have toggled the T-bit on a few steps ago

>INZ PROGRAM.TSK

If you want tracing to start immediately, use TRACON to set tracing on. TRACLOAD sets tracing off when it prepares the trace buffer, because you usually don't want to trace from the very beginnning of a program. You can also set tracing to start after a specificed number of instructions. Here; we set it on from the start.

RUN TRACON
TRACON Version V01A01 - 12-JAN-2026 - Control-G Consultants
Input and output radix is Octal.
HELP command will print a summary of confusing commands.
TRC>
help
Start   - Sets number of instructions to skip before tracing starts
On      - Begin or resume tracing
Off      - Stop tracing
Check - Print current value of tracing control word
Exit     - Exit the program
Quit    - Exit the program
Exit and Quit do the same thing
Check value, high bit set indicates tracing is off, clear idicates on
I call this "Yes, we have no bananas" logic.
TRC>ON
ON
>

  And now, finally, you can run the program in question and accumulate some traces. If you're still reading this now, after all of this persiflage, you must really want to trace some program's execution....

>RUN PROGRAM

  You can read the trace buffer using OPE, to just read the contents of  the pool block, using the address contained in the logical  pooadr (>sho log pooadr/all) - it was also printed out when you ran TRACSPACE. Or, you can dump  out the traces using TRACREAD.

TRACREAD.MAC

TRACREAD.CMD


TRACREAD will read the oldest entry and print htme in order up to the most recent.

>RUN TRACREAD

TRACREAD Version V01A01 - 14-FEB-2026 - Control-G Consultants

Pool block address is
055750
Circular index value
000010

Control Word
100000

012701
000006
007073
000010
000011

...und so weiter. 


  TIme for a disclaimer. These programs change mode to kernel, allocate scarce critical resources, change APRs to map all over the place, and modify things in important programs. I can pretty much guarantee there are bugs in them.  There's plenty that can go wrong in this scenario. Use at your own risk. Let me know if you find any problems. Heck, let me know if you ever use any of  this stuff, or even read this far.


Monday, December 15, 2025

New version of OPIND, the RSX11 disk tool

  OK, time for a new version of OPIND. Lots and lots of bugs fixed, mainly in the location, display and editing of index file extension headers, and file headers in general. 

  Also added some new features. Suzuran from the Usagi Discord suggested adding a CLEAR and FILL block capability. I added that capability, although with a different syntax than he suggested. Thanks for the suggestion, Suzuran.

ZILCH - puts all 0's in the Current Block (the word clear was already used for clearing bits in the index file bitmap).

ZILCH BLOCK 123 - puts all zeroes in block 123, and writes it to disk

FILL WITH 123 - FILLS Current Block with byte 123.

FILL BLOCK 123 WITH 321 - fills block 123 with value 321 and writes to disk

  I also changed the command input prompt, from Yes?> to one that displays the current input radix, HEX>, DEC> or OCT>. This was done because it's easy to forget what the input radix is currently set to, and getting it wrong can cause some real damage.

  Here it is...

 OPIND.MAC


 It's assembled and built the same as before

>MAC OPIND=OPIND
>TKB OPIND/PR:0=OPIND



Wednesday, October 29, 2025

New version of OPIND, the RSX11 ODS-1 disk investigation and patching utility

  So here's a new version of OPIND, the RSX11 ODS-1 explore and fixup utility.

   Truth to tell, the previous version of OPIND was...not very good. I only worked on it long enough to get it able to patch the disk corruption problem I had at the time. It did the basics, but a lot of the fancier functions had bugs in them, and it was hard to predict what radix particular numeric inputs were expecting. As well, it was unclear whether some commands acted on in-memory copies, or directly on actual blocks in the file. This new version has had a lot of debugging done, and a slug of enhancements made. Now you can specify the input radix - HEXadecimal, DECimal, or OCTal. There are also new features that allow easier access to the Index File extension headers.

  So let's talk a little bit about how OPIND works and how to use it.

  In this version, there are several data buffers where disk blocks live. The main one is the Current buffer. It is where disk blocks are read from and to, and is the default place where commands act unless a different buffer is specified.

  The other buffers are HOME (contains the Home block), IFH (Index File Header), IF1 IF2,IF3,IF4,IF5 (the first through fiftth Index File Extension Headers). These are one block sized headers. There is also the Bitmap buffer, a special case among special cases, which contains the Index File Bitmap, and can be up to 16 blocks long. These all get special locations because they are...special...they impose the ODS  structure on the disk.

  These special buffers'  LOAD commands store them in their special locations, not the Current buffer like LOAD BLOCK and LOAD HEADER do. As a result, they have to be COPYed to the Current buffer, to be modified if necessary. Then they can be written to the file, to their approriate locations by their WRITE commands (their WRITE commands write from the Current Buffer, to their location in the file. If you expect, for instance, WRITE HOME to write from the HOME buffer to the file....well, you'll be disappointed).

  So, for example, to patch something in the HOME block, you could LOAD HOME (which is also done automatically at startup BTW), then COPY HOME, which copies it to the Current buffer. You can check the contents  of the Current buffer with FETCH commands, and change contents of the Current buffer with DEPOSIT commands. When your changes are complete, you can then choose to WRITE HOME (or IFH, IF1 through IF5 - note - IFx, where x is a number, refer to the xth extension header in the chain, not the index header at ID x) and the contents of the Current buffer are copied to the approriate place in the file. Note that at that point, the old contents are still in the HOME buffer, so you'd likely want to do a LOAD HOME to refresh it from the file, so it reflects your recent changes.

  At startup, HOME, IFH, IF1 through IF5 and the Bitmap buffer are loaded from the target disk. That is, if the disk isn't too screwed up. If the Home block or the other special blocks  are damaged, lots of the advanced functions that locate by file ID or bitmap bit, or special  buffer name aren't going to work, and your first job will be sorting out the damage and fixing it with the simple commands that only depend on a disk block number  - LOAD BLOCK, WRITE BLOCK, GET and PUT commands, und so weiter.

  GET and PUT deserve some mention. They will read and write the current block. This "cut and paste" buffer survives across invocations of the utility - so, for instance , you could use the utlity on a good disk, to load a block of insterest into the Current buffer. You could save the block with a PUT command, and exit the program. Then you could start it up again, this time targeting a problem disk, and use the GET command to load that previously saved block into the Current buffer. perhaps edit it a bit, and then do a WRITE HOME (or, if that's not working so hot, WRITE BLOCK 2) command, to store in in the problem disk's HOME block location.

  Then there's the Bitmap buffer. It's 16 blocks long, so it won't fit in the Current buffer (which is one block long). It gets edited in its special buffer via TEST ID, CLEAR ID and SET ID commands. These edits can then get copied back to the file, direct from the Bitmap buffer, via the WRITE BITMAP command. What could be simpler?

  Lessee....let's explain how DUMP works. DUMP with no arguments dump the Current buffer. DUMP HEADER 123 reads header 123 from the file and dumps it. DUMP BLOCK 123 reads the block 123 from the file and dumps it. Both of those variants expect the specified number in the currently chosen radix. DUMP HOME, IFH, IF1 through IF5 and BITMAP dump the contents of those special buffers. They don't read fresh from the file. All variants of the DUMP command respect the output radix switches /HEX /OCT /DEC /ASC /R50. 

 And a word about the checksum functions. File headers and the Home block contain checksums of their contents. If you edit one of those, you need to update the checksums before you write them back to the file. So, suppose you LOAD HOME, then COPY HOME to copy it to the Current block. You edit it by using FETCH and DEPOSIT. Now you need to CHECKSUM HOME UPDATE, to update the checksum fields. Then you can WRITE HOME, which will write  the newly modified and checksummed block into the disk's home block. For a header, you;d read it into the Current block via the LOAD HEADER xyz command (or COPY IFH, iF1 through IF5)  for those special headers). modify it in Current, Checksum it with CHECKSUM HEADER UPDATE, and then WRITE to...where it needs to go, depending on what it is. There is also CHECKSUM HEADER CHECK and CHECKSUM HOME CHECK, which will evaluate the Checksum of a Header or Home block, in the Current Buffer.

Numbers entered as input are interpreted according to current radix
The default input radix is hexadecimal, unless changed by radix command
Output radix is ascii unless set by command line switch.
Output radix switches are /HEX /OCT /DEC /ASC /R50 (specified as /<radix> in the command summary below. /R50 comes in handy when looking at headers, since file names in headers are encoded in RAD50.

radix hex                                input numbers interpreted as hex
radix oct                                 input numbers interpreted as octal
radix dec                                input numbers interpreted as decimal
load block 23                        read block 23 into current block
load header 23                     read header 23 into current block
load bitmap                           read bitmap into bitmap buffer
load home                              read home block into home buffer
load boot                                load boot block into current buffer
load ifh                                     load index file header into ifh buffer
load if1 (though if5)            load selected index file extenstion header into current buffer
dump                                        dump current block as ascii bytes
dump /<radix>                      dump current block in specified radix (eg, dump /hex)
dump block 23                      dump block 23 as ascii bytes
dump block 23/<radix>     dump block 23 as specified radix  bytes (eg, dump block 23/oct)
dump header 27                   dump header 27 as ascii bytes
dump header 27<radix      dump header 27 as selected radix  bytes
dump bitmap                        dump the header bitmap as ascii
dump bitmap/<radix>       dump the header as specifed radix bytes
dump home                           dump home block as ascii bytes
dump home/<radix>          dump home block as specified radix bytes
dump boot                             dump boot block as ascii bytes
dump ifh                                  dump index file header as ascii bytes
dump ifh/<radix>                 dump index file header as specified radix bytes
                                                     dump if1 through if5 works like dump IFH                                                   
fetch @200                              display byte at loc 200, current radix, in the current block
deposit 10@123                    deposit 10,current radix,at location 123,current block
put                                              save current block in  file - like a paste buffer
get                                               get saved block from paste file, make it current
write block 123                      write current block to block 123 in file, with 123 in current radix
write header 123                   write current block to header 123 in file, with 123 in current radix 
write bitmap                           write the bitmap out
write home                              write current block to home block in file
write boot                                write current block to boot block in file
write ifh                                     write current block to index file header
write if1 (through if5)          write current block to selected index file extension header in file
test id 123                                 test bit 123 in current radix in the header bitmap buffer
set id 123                                  set bit for header 123 in current radix in header bitmap buffer
clear id 123                              clear bit for header 123 in current radix in header bitmap buffer
copy home                               copy home block from its buffer to the current buffer
copy boot                                 copy boot block to current buffer
copy ifh                                      copy index file header from its buffer to the current buffer
copy if1 (through if5)           copy selected index file extension header to current buffer
check  home checksum      check the home block checksums the  current block
checksum home update    update the home block checksums in current block
checksum header check    check checksum in header in current block
checksum header update  update checksum in header in current block

  So much for Version 2. Version 3 will have search functionality, an ANALYZE command that displays the fields in the special buffers. and the ability to follow and operate on the blocks in a particular file.

  But enough of this silly documenting, It's turning into a case of TL;DR. Let's get down to the code. 

   Here's the newest version of OPIND.MAC, with changes to how Index File Extension Headers are handled, and the new commands allowing operation on the Boot block.


UPDATE - see later OPIND blog post for the newest version of OPIND, with new features and lots of bug fixes.





  To build, 

>mac opind=opind
>tkb opind/pr:0=opind

  It's built /PR:0 so it can do logical block IO to disks, no matter how they are mounted or who has them allocated. LIke I've said - you can do some serious damage with this ultility, so you better understand what you're doing. If you're trying to patch up a corrupted disk, it would be better to make a physical copy of the disk in question, and mount that copy /FOR, and then have at it with OPIND - that way, if things go bad, you haven't lot any ground.

  Let me know if you find any bugs or have any problems.,,

  

Saturday, May 31, 2025

MSCP and DUP talking to RQDX3

  I have a few BA23 systems here. Among others, one is an 11/73, and the other is a MicroVAX I (I have a taste for minimalism). They each have RQDX3 cards in them to access MFM disks. 

  MFM disks...as the bosun said in GC Edmondsons's "The Ship That Sailed The Time Stream", regarding batteries, "They ain't diamonds" - meaning, they aren't forever. I've gone through a lot of RD54s, RD53s, RD32s and RD31s. The ones I have left that still work howl like banshees, running on their square dry bearings.

  So they needed to be replaced, before I was left with what is usually my favorite thing...nothing. Enter David Gesswein's MFM disk emulators. These are great products. Quiet and dependable. I bought six of them and figured, I'd have the RD53/54s replaced in a day. One pair went into my PRO380 and worked great as two RD53s. The only wrinkle there is that you have to extend the power off/shutdown timing in their parameter settings, and power the PRO on twice, since the PRO disk controller doesn't give the emulators time to start up before deciding there's no disk present. But, the second time, it works great....and really quiets down the PRO.

  The 11/73 and the MIcroVAX 1, different story. No fault of the emulator cards, though - I had a series of cascading failures. An RQDX3 failed in one of them, and two of my spares were also bad. A new drive cable I was using turned out to be bad. And, while plugging and unplugging the drive cables repeatedly, I managed to bend pins in two of the BA23's sockets. Those pins are soft as butter. As well. a couple of RD32s I was using to test with turned out to be bad. This made for a lot of linear combinaitons of parts that wouldn't work....and it got worse as the parts were swapped around between systems trying to test.

  While these struggles were going on, I was using VMS and XXDP to test the results of the  swapping around, looking for the faults. They each took a middlin' amount of time to test with. I wanted something that would give me a quick pass/fail sort of answer as to the health of the controler, cabling and hard drive or emulator.

  A few blog posts back. I detailed how to talk MSCP to an RQDX3 in a downloadable image, for loading software via ethernet. It was the work of a moment to hack that program around to where it just issued MSCP commands to the RQDX3 and disk, sufficient to see if anybody was home. I won't bore you again with the details of how to do that - see the previous blog post if you're interested.

  But, while trying to get the two systems running again, I became far more interested in working on this utility. With a little work, I realized I could make it into a useful tool for poking at MSCP controllers and disks, beyond just seeing if they were alive or dead. I added the ability to do several MSCP functions as well as testing for life. While doing that, I became curious about DUP. DUP stands for Diagnostic/Utility Protocol. It is mentioned in scant detail in the DEC doccos about MSCP. It is used to load and control diagnostic and utility programs in MSCP controllers - thus the name DUP. The doccos do spell out the format of messages used to request DUP functions - the format is essentiially the same as MSCP control message format - but none of them that I could find spelled out how to actually send these commands to  DUP.

  I finally puzzled out the answer. The command packet format is the same as MSCP (although with different opcodes and success status values) - but it turns out that the two words BEFORE the command packet determine which protocol is used.  For example, here's part of a a typical MSCP command packet.

                  31                              0
                  +-------------------------------+
                  |   command reference number    |  0
                  +---------------+---------------+
                  |   reserved    |  unit number  |  4
                  +---------------+-------+-------+
                  |   modifiers   | rsvd  | opcode|  8
                  +---------------+-------+-------+
                  |  unit flags   |    reserved   | 12
                  +---------------+---------------+
                  |           reserved            | 16
                  +-------------------------------+              

   

  What nothing in the usual sorts of doccos tells you is that the two words previous to it determine who gets it. The packet is actually two words longer, with the top two words referenced as a negative offset from the start of the command packet. I finally found this little bit of info in a diagram in the MSCP patent application. Here's how it looks on a command packet.

                  31                              0
                  +------+--------+---------------+
                  | Type | MT & CR|    pktlen     | -4
                  +---------------+----------_----+
                  |   command reference number    |  0
                  +---------------+---------------+
                  |  reserved     |  unit number  |  4
                  +---------------+-------+-------+
                  |   modifiers   | rsvd  | opcode|  8
                  +---------------+-------+-------+
                  |  unit flags   |    reserved   | 12
                  +---------------+---------------+
                  |           reserved            | 16
                  +-------------------------------+              

  "Pktlen" is a word, and "Type" and "MT&CR" are bytes.If the packet length and the protocol type fields (pktlen and Type)  are zero, then MSCP will handle the packet. If the packet length is correct for the opcode specified, as a DUP opcode, and the type field is 2, then DUP will handle it. By blind good luck, my previous MSCP programs worked just because by happy accident, the two words before the command packet just happened to be 0, so I got MSCP successfully.

  The credits and msgtyp bytes are used for more complicated schemes involving simultaneously active IOs. They're  always 0 for my usage.

 Anyway, enough prattle about figuring out how to invoke DUP. Let's talk about this utility. The utility works like the disk installing software in a previous blog post, via DECnet MOP download. No software or OS required on the target machine.

$ mcr ncp set node negato load file dua0:[mvax1]mscp.exe
$ mcr ncp sho node negato char
Node Volatile Characteristics as of 18-SEP-2021 10:01:36
Remote node =   10.103 (NEGATO)
Hardware address         = 08-00-2B-26-2F-84
Load file                = DUA0:[MVAX1]MSCP.EXE

  Boot the MIcroVAX via network, and it should load mscp.exe

>>>B XQA0
ATTEMPTING BOOTSTRAP




                                  MSCP X02A02
                            Control-G Consultants



  You'll be prompted for the disk number. Then you get a "ribbon" of commands.

Enter disk unit number:

0
Enter command (HELP HALT INIT AVAI ONLI STAT READ WRIT CLEA HACK GETD DIRE)
 
Help - prints out a page of not particularly helpful information.
HALT - Halts the processor. Handy if you get scared, or if the control panel is not right next to you.
INIT - Initializes the RQDX3. Needed before any other command (except HELP) is used. If you don't, then it will hang.
AVAI - takes the disk offline, which makes it available to be put online again.
ONLI - takes the disk online
STAT - shows the state of things
READ - reads a block. Gotta go ONLINE first. It will prompt for a block number
WRIT  - writes a block...works like READ
CLEA - clears out the block buffer. The buffer is saved between read and write, so you could
"cut and paste" a block to another block. This clears it back out to zero, erasing anything you  previously read.
HACK - DUP command - Possibly the most interesting DUP command. This will load a block of PDP11 code into the MSCP controller and execute it as a task there. The PDP11 code is in the program between "ZAP:" and the definition of "ZAPLEN". The program included in the utility just bloinks the write protect lights. More research will show if anything useful can be run this way. It's necessary to go ONLINE before using HACK command.
GETD - DUP command - Get DUP status
DIRE - DUP command = This executes a local command called DIRECT, which will return a list of programs present on the controller. Does nothing useful here, since RQDX3's have no built in programs. But, on other MSCP cards, who knows? As is, it serves as an example of how to execute an MSCP controller built in program.




  I should mention...this is a utility that speaks directly to the disk controller, using poorly documented and unsupported features....a disk could get hurt if things go wrong, so if you use it on a machine with good data on the disks, make sure you have a backup before ...experimenting. Don't come crying to me if you manage to erase a disk or blow the format off of it....

Saturday, November 30, 2024

RSXstation - QVSS VCB01 graphics on a PDP-11

   A while back, I got a couple of VCB01 cards in a lot of other MicroVAX cards I wanted. The VCB01 was used in the VAXstation I and VAXstation II to provide simple graphics, and mouse and keyboard. This was referred to as QVSS graphics - Qbus Video Sub System. It's a relatively simple card - plugs into a Q22 bus, and has 256Kb of memory for screen memory, and a scan map that determines what lines are mapped where on the screen. And, like I said, it also supports a mouse and a keyboard. It displays one plane of monochrome.  Not the most capable graphics card in the world, but, I got to wondering - could it work on a Micro-PDP11?. Micro PDP-11s have a Q22 bus - could the VCB01 provide graphics on a PDP-11? Graphics for RSX sounds interesting. A look at the VCB01 doccos didn't turn up anything that would prevent it from working.

  I have a BA23 box test system that has a PDP-11/73, 1024KB of memory, an RD32 and assorted other options in it. I stuck the VCB01 in the box, after all the other cards on the bus - apparently the VCB01 can act funny with bus grants, so it gets to be last,  where it can't screw up as much. I powered on the machine, and it went into its normal self test. Interrupting the self test lets me use the "M" command - display the hardware map. I saw all the usual things, CPU, memory, CSRs. I additionally now saw a new 256 KB area of memory at 16000000, and a set of 16 CSRs at 177200. Both of those are what's expected with a VCB01. So that's a good start - the hardware is visible to the system, and didn't look like it stepped on any of the other hardware already present.

 Commands are Help, Boot, List, Setup, Map and Test.

Type a command then press the RETURN key: M

15.206 MHz

Memory Map

Starting     Ending       Size in      CSR           CSR    Bus
Address    address      K Bytes    address      type    type

00000000 - 03777776     1024       17772100     Parity  Qbus
16000000 - 16777776     256                                         Qbus <------This is the 256K of VCB01                                                                                                             memory

I/O page Map

Starting     Ending
Address    address

17760440 - 17760456
17765000 - 17765776     CPU ROM or EEPROM
17772100                        Memory CSR
17772150 - 17772152
17772200 - 17772276     Supervisor I and D PDR/PAR's
17772300 - 17772376     Kernel I and D PDR/PAR's
17772516                        MMR3
17773000 - 17773776     CPU ROM
17774440 - 17774456
17774500 - 17774502
17776500 - 17776516
17777200 - 17777276      <----------------------------------These are the VCB01's 16 CSRs.
17777520 - 17777524     BCSR, PCR, BCR/BDR
17777546                        Clock CSR
17777560 - 17777566     Console SLU
17777572 - 17777576     MMR0,1,2
17777600 - 17777676     User I and D PDR/PAR's
17777744 - 17777752     MSER, CCR, MREG, Hit/Miss

17777766                        CPU Error
17777772                        PIRQ
17777776                        PSW

  I booted RSX11M+, and it completed successfully. Man, was I surprised! RMD showed that RSX only knew about the usual 1024KB of memory - that's good, it didn't detect and try to use the 256KB on the VCB01, so I wouldnt have to fight with RSX over using it for graphics. So far, so good....time to hook up a monitor, I reckoned. I had an old DEC VR100 taking up (a lot) of space, left over from my old VAXstation 100, that was specced to be compatible with the video output of the VCB01. I hauled it out and and dusted it off. It turned on and displayed...well, not a raster, but some brightness on the screen, so I went to work making a cable from the DB15 on the VCB01 to the three BNC connectors on the monitor - Video, Horizontal Sync and Vertical Sync.

  This took a while and a lot of experimentation. I hooked up an oscilloscope to the three outputs and saw that the video was present, but the sync signals were missing. Turns out that I really needed to program the 6845 CRT controller on the VCB01 before it would know what kind of sync to generate, and when.  This looked like a complicated slug of calculations, about a page's worth, to get 16 values. This stuff looked like Buck Rogers math, so I looked for an example somewhere to copy from (I remember when the motto of DECUS was "steal from your friends"). I tried the values in the  VAXstation 100 Architecture Manual, figuring, they'd be close. Some  sync signals now showed up on the osillloscope,  but no soap - nothing on the VR100 display.  Totally nothing, actually, now not even the former bright glow it had before I hooked up the cables from the VCB01. Now I'm thinking,  the VR100 is dead. No brightness on the screen. Was it damaged by the strange sync frequencies that were produced while I was trying to program the 16 values? That was apparently a problem thing, back in the day. I dunno. I'll have to dig into it and see what's wrong. Could just be a coincidence. Maybe an aged capacitor gave up the ghost right as I needed to use the monitor. Looks like I'l have to troubleshoot it like an old TV. But that's a project for another day. I pushed the deadly high voltage box of puzzles and surprises aside on the workbench, and looked for a substitute display. Josh Densch, a VAXstation 100 owner,  pointed out that a NEC Multisync monitor would work, so I got a cheap one from Ebay. Apparently they will sync up to most anything, without blowing up. 

  So I hooked it up and had another go. Success! I got some more or less  random patterns on the screen at power on, no doubt due to whatever random values were in the 256K of pixel RAM on the card. But the screen didn't look right, it was tearing and flashing and rolling -  the sync clearly was still an issue.

Apparently, the 6845 has to be set just right before you get a usable display. For whatever reason, the VAXstation 100 values I had weren't doing the business. I tried a bunch of settings for similar graphics cards that used 6845s on hobby gear (back in the day, the 6845 was a popular hobby microcomputer CRT controller) - still no luck. I started looking for the sources to the QVSS driver from the MicroVAX, to copy the initializaion of the 6845 from, but, nothing to be found. I posted a query on the Vintage Computer DEC forum, and user Bitly provided the initialization values that QVSS uses, which turned out to be in the DECWindows sources. Thanks again, Bitly!. I plugged in those values, and, success! A more orderly pattern appreaed on the screen, still looking wild but no tearing and rolling.  Here's what it looked like.



Why the VAXstation 100  Architecture values for its 6845 didn't work - another DEC history mystery.

  Now that the display seems stable, it's onward to accessing it while running RSX. But this is a PDP-11. A task can only address 64KB, as 8 8KB chunks, at a time, using 16 bit addressing. You can't directly access the 128K of pixel space from a simple task. But we can access anywhere in the Q22 address space, by changing the mapping of a tasks' APRs - an address page register. A task's 8 APRs map the 8KB chunks of task logical space to the 22 bit space of the Q22 bus. I could map an 8KB chunk of a task's address space to 8KB chunks of the 128K of pixel memory on the VCB01 starting at physical address 16000000.

    So I wrote the typical sort of task to test something like this - it's built as /PR privileged to give it access to the IO page, where the APRs live.  The 256K of pixel memory is effectively a  1024X768 pixel array.  Program setlin abstracts that as x,y values. When it comes time to write to the memory, it first converts the x,y coordinates to an index into the equivalent 98304 byte array, with the low 3 bits indicating which bit in the byte needs to be set. From there, we look up what value to enter in an APR to reach it. Then we switch to Kernel mode and put that value in KISAR6 (Kernel Instruction Space APR 6), to map the 8KB chunk that contains that byte. Why Kernel mode? Why KISAR6? It's what similar DEC utilties do. If you're interested, have a look at the code. I did a previous blog entry that does a poor job of describing mapping things via APR, have a look at that, or better yet, have a look at the RSX Task Builder manual to find out more. 

 

  In any case, now I can write programs that set bits by X,& coordinates on the screen (the upper left corner is 0,0. X increases to the left, and y increases going downward). Here's some examples.







  OK, they aren't great art, but I don't have any ready to go drawing software for RSX.


  Here are programs that zero out or set all the bits in the pixel memory. They write entire bytes instead of setting bits, in the interests of speeding things along.

  This one zeroes out the pixel memory.

clear.mac

  This program writes all ones to the pixel memory.

set.mac

  Here's a test program - it draws some boxes, and some lines. It addresses individual bits/pixels, has more logic in it than the above two. It draws the RSX11 banner and a grid. 

  setlin.mac


  These programs have to be assembled and linked...special, to find all the symbols and routines invovled. Here's the commands for setlin.mac as an example

mac setlin=[1,1]exemc.mlb/ml,[11,10]rsxmc/pa:1,[vcb01]setlin
hft setlin/pr/-cp=setlin,[3,54]rsxvec.stb/ss,[1,1]exelib.olb/lb

  The mac commands  assume that the source files are in directory [vcb01]. If they aren't...you know what to do. The taskbuild commands assume that you have removed ...TKB and installed HFT (the Hybrid Fast Taskjbuilder) in its place.

>rem ...tkb
>ins $hft

 HFT is not required for these programs to work...but, ya gotta use the fastest task builder you can find....if you're on an older version of RSX11M+, use TKB.


Here's the command files to assemble and link them

clear.cmd

set.cmd

setlin.cmd


  So, this is all middlin' interesting. One big problem, though, is that this is slower than Christmas. I mean S-L-O-W. You'd think it was coming in via 1200 baud modem.  For some reason, it takes forever to draw something. Part of it is the test programs were not written to be fast - to write a pixel, you have to call several subroutines and do a $swstk for each one. Also I was experimenting with passing all args on the stack, instead of in registers. Doing dozens of pushes and pops for every bit set doesn't help speed things up. Hey, these are proof of concept test programs, not production grade. Ideally, this should be implemented as a driver (I'll get around to doing that). Maybe there's something wrong with my test system. But I wonder, if the VCB01 is just intrinsically slow on an 11, and that's the reason that it was never supported on 11s. Or it's bugs in the programs. Dunno yet. I want to get it running faster, so I can do some  fun things,  like using the pixel memory as a disk, and get to watch the blocks fly in and out on the screen. Or maybe as a swap disk, work files or task memory, like the classic Alan Frisbie story. As the story has it, he  realized that an early memory mapped graphics display was being sold cheaper than just buying the memory it contained. He started running tasks out of that memory, and jokingly asked the manufacturer to add the ability to select what task to run next using the light pen.

  Anyway, let me know if you give any of this a try. I'd love to hear about other results.


Tuesday, April 16, 2024

RSX utility for converting Logical Block order virtual RX50 disks to Track, Sector order.

   Several posts back. I posted a utility, TSTOLBN, that converts RX50 disk images that are in the same layout as a physical RX50 diskette (I call that Track, Sector or TS  order - it  has the blocks in a track  interleaved every other block, and the track  starting sector  is skewed by two sectors/each track, plus the last track is at the first physical track on the disk) , into just a string of blocks, in numerical order - the format used by RSX and VMS virtual disks, and SIMH disks (I call that Logical Block order, LBN). 

  That does the business for all of my needs - I use RX50 images as files on P/OS, RSX, VMS and SIMH, and they all need LBN format. Additionally, if you have an LBN order virtual disk, you can read and write it directly ro a real RX50 diskette, and the MSCP/Pro Controller will take care of converting it to/from the physical Track Sector format on the disk for you. But I've been noticing of late that people are needing RX50 images that they can use in assorted external disk emulating gizmos - and they all required the same format as a physical RX50 uses - TS order.

  Several folks have written converters in C and Python to convert LBN to TS order. But I prefer to work in the DEC environment as much as possible, so I needed a tool to do this conversion, that runs purely in the RSX and P/OS environment, no Windows/Linux/Python/C compiler required.

  So I took a little time and, using the previous converter (TSTOLBN, Track Sector To LBN) as a base, created a utility called LBNTOTS (Logical Block To Track Sector).

  Really, it's almost the same program - just had to make a few changes to the interleave and sector translation arrays, and the funny first track/last track logic. And I cleaned it up a little in general. 

  Here's the source code...


lbntots.mac


To make...
 >mac lbntots=lbntots
 >tkb
 TKB>lbntots=lbntots
 TKB>/
 Enter Options:
 TASK=...LTT                   ;stands for LBN To TS
 LIBR=FCSRES:RO       ;this is optional - saves memory on a busy system
 //
>

 To use...
 >run lbntots
 LTT>outfile=infile

 or install it and use direct from the command line

 >ins lbntots
 >LTT outfile=infile

 Outfile extension defaults to .dsk, infile extension to .dsk


Monday, January 22, 2024

Install prorgams via ethernet without OS - update to PDP11 version

  So my 11/73 system, in a BA23, has recently developed a memory problem and won't boot. The ROM diags show...

Expected data   = 052525
Bad data            = 052524
Address             = 02740032

 Looks like one bad bit on a chip. I replaced the card with a spare card, but it's too small and now RSX can't boot, I want to run some memory diags on the failed card, hoping that they will be able to give me some more clues as to which chip has carked it.

  My XXDP RX50 only had disk diags on it, so I needed to add QBUS memory diags, VSMAC0 and VMJAB0 (sometimes listed as CVMSAC0 and CVMJAB0).

  I used SIMH and an RL02 XXDP disk image to create a new XXDP RX50 disk image, comtaining the memory diags and the oft used ZRCHB0, the MFM disk formatter. Next I needed to copy the RX50 image to a physical floppy.

  I have a set of tools for this - the client/server disk copying tools I wrote to load VMS and RSX on systems without OSes. They should work fine on an RX50, since they are controlled by an RQDX3. 

  I had a look at them, and relaized that the VAX server tool, LOCALWRITE, had been upgraded a while back, and the PDP11 client tool, REMWRT, was no longer compatible with it. LOCALWRITE and REMOTEWRITE had been updated to allow entering what unit number to write to, and what file to download (per suggestions and help from Hans-Ulrich Hölscher), as well as some changes to the communiction protocol used between them. (When copying to a PDP11, the VAX LOCALWRITE server is used, since I haven't written LCLWRT yet, a PDP11 version of the server task).

  So I prodced a new version of REMWRT with the needed changes. They aren't interesting enough to describe. Here's the new version of REMWRT.MAC

remwrt.mac


  Please see earlier blog post for the instructions about how to assemble, link, load and use it.

 So, I used LOCALWRITE and REMWRT to copy the XXDP RX50 image to a real RX50 on the system in question. Copy worked fine, disk boots OK. Next step - take out the spare memory card and reinsert the bad one, and boot XDP and run the diags. Here's hoping for some useful info....