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 (file IDs 6 and 7).

  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), IF6 (file ID 6, the first Index File Extension Header) and IF7 (file ID 7, the second Index File Extension Header). 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 8 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, IF6, or IF7{ 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, IF6 IF7 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 take a disk block number as input - LOAD BLOCK, WRITE BLOCK, GET and PUT commands.

  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 8 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 cimpler?

  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, IF6, IF7 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, IF6, or IF7 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 cpmmand 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 ifh                                     load index file header into ifh buffer
load if6                                     load 1st index file ext header into if6 buffer
load if7                                      load 2nd index file ext header into if7 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 ifh                                  dump index file header as ascii bytes
dump ifh/<radix>                 dump index file header as specified radix bytes
dump if6                                  dump index file header as ascii bytes
dump if6/<radix>                 dump index file header as specified radix bytes
dump if7                                  dump index file header as ascii bytes
dump if7/<radix>                 dump index file header as specified radix bytes
fetch @200                              display byte at loc 200, current radix, in the current block
deposit 10@123                    deposit 107,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, with 123 in current radix
write header 123                   write current block to header 123, with 123 in current radix 
write bitmap                           write the bitmap out
write home                              write current block to home block
write ifh                                     write current block to index file header
write if6                                     write current block to 1st index file ext heade
write if7                                     write current block to 2nd index file ext header
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 ifh                                      copy index file header from its buffer to the current buffer
copy if6                                      copy  first index file extension header from its buffer to  current buffer
copy if7                                      copy  first index file extension header from its buffer to the current buffer 
checksum home check       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. 

opind.mac

  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 - tht way, if things go bad, you haven't lot any ground.

  Please let me know if you find any bus 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....