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 if 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.... 


Wednesday, September 20, 2023

RSX11D V4 boot problem update

   A few posts back I posted about finding RSX11D V4, on a deceased DEC engineer's RK05 pack.. 

  That post presented it, but with a boot problem that required a manual step or two to get it to boot. The boot problem occurred both in SIMH and on real hardware.

  Tony Lawrence read the post, and figured out the problem (along with some others). In less than 24 hours. He said I could share his analysis, in a shortened form (his work was incredibly detailed). Thanks, Tony! Heres a note from him, summarizing the problem.

"The boot loader abuses a hardware-dependent "feature" of an old RK11 disk controller and reloads its word count while the operation is still in progress, so to keep the disk controller going (with what it was doing, because the only thing to stop it would be an error or the WC reaching 0): until it reaches the end of memory.  More modern controllers (and certainly, simh) can't be used with such a "trick",as like I said previously, they are numb to the register write while the operation is on-going, and in case of simh, the operation just stops when it reaches the word count of 65534 (because the write is not time-simulated, it's a one-shot operation, internally, of basically one read off of the disk image -- so the new WC would not have ever been noticed)."

  He included a disasasembly of the code in question, and pointed out that in the process of doing all this, the boot sector loads the system image on top of the boot sector, which isn't a problem since the code that overlays the boot sector, from the system image, is the same.

  In another paragraph, he pointed out...

"I can't know why the developers chose to use this boot hack with sizing the memory using RK11 -- because that's basically what they were doing.  At the boot time, the file structure of the disk is obviously unknown, but the first 245. blocks following the initial Disk Address (DA), do load the operating system.  Since they kept reloading the WC, it'd cause the controller to keep on going, loading up all the following sectors (with basically unstructured garbage) until the bus timeout -- address does not exist -- converted to the NXM error by the controller.  Should they load fewer than 128K because of the NXM condition (e.g. 96K phys mem), the end of the image wouldn't be read in, and the system would crash (as the tail of the image file contains something which looks like the STD -- the System Task Directory).  If the NXM occurred past the 128K, it's all good and everything can actually continue! So it was not considered as an error at all, but an attempt to load as much as possible from that initial DA and to make sure the memory is not tight.  There could be a problem, though, with such a logic, if the OS image was located closer to the end of the drive, then it could have triggered an OVR (overrun) controller error before the end of memory reached, and the boot would fail (would loop to address 0 to start over again).  Anyways, it was a very bad example of how things should NOT be done! LOL"

  He even included a patch to remove the manual steps that I had been using to get the image to boot.

"All in all, if you open the disk image (the .dsk file) with a binary editor, go to locations 146 and 6421146, and enter the following to replace the next 7 words:

  Tony also pointed out that if you do a SYSGEN on this system, you'll have to apply the patches again, pointing at the new RSX.SAV file. I'm working on an automated way to take care of that - stay tuned for another update when I get it done.

xxx146: 105737  ; TSTB @#177404  ;  Complete? 
xxx150: 177404  ;
xxx152: 100375  ; BPL 146                   ;  Loop if not
xxx154: 005737  ; TST @#177404     ;  Error?
xxx156: 177404  ;
xxx160: 100707  ; BMI 0                        ;  Loop back to boot if so
xxx162: 000240  ; NOP

Then your system will boot right away!  (You can actually skip patching the boot sector, locations 146+, and only patch locations 6421146+ -- note this is an offset in octal)."

 For anyone that has an interest in RSX11D V4, I include below the PDP11.INI and RSX11DV4PAT.DSK, with the above desribed patch.

rsx11dv4pat.dsk

pdp11.ini

  Using them, here's what the boot looks like now

C:\simh40\PDP11\rsx11d\rsx11dv4>pdp11

PDP-11 simulator V4.0-0 Current        git commit id: ab3e07a4
Disabling XQ

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

  Of some interest, but not related to the boot problem, he pointed out a few curious things in the file structure of the disk. One was that there are files that weren't completely deleted - their checksums weren't deleted as they should be. As well, there are a number of files with really crazy looking update dates - here's a couple of examples.

RUN.TSK;1           (111,2420)      9./9.         C  01-APR-74 22:38  [1,5]    [RWED,RWED,RWE,R] 
32-SEP-73 00:01(9.)

INI.TSK;1           (112,2421)      17./17.       C  01-APR-74 22:38  [1,5]    [RWED,RWED,RWE,R] 
32-SEP-73 00:00(9.)

 I'm guessing these are results of Files-11 bugs from long ago.




Monday, August 21, 2023

Installing software on Microvax and MicroPDP11 systems - update

   Recently, Hans-Ulrich Hölscher did some experimenting with the programs I wrote that allow copying a disk image from any VMS or RSX system to a MicroVAX I or PDP11, that has no software already loaded, via ethernet (see several blog posts, with VAXstation I" and "install" in their titles).

  I was surprised when he reported a several things I hadn't realized about those utilities. I had assumed that they wouldn't run on SIMH systems, or on MicroVAX IIs, due to differences in their internal architecture from the MicroVAX Is and real PDP11s I wrote and tested them on. Ulli pointed out that they actually worked on SIMH instances of MicroVAX Is, and as well on SIMH images of MicroVAX IIs, and, very likely, real instances of MicroVAX IIs.

  As well, he reported that DELQA ethenret cards work as well as DEQNAs.

  I checked it out and his reports were correct. I reckon...1 - that the architectural features I used to make this work with MIcroVAX/VAXstation 1s were also present on the MicroVAX/VAXstation II, and...2 - SIMH MicroVAX I and II emulators also include all of those features. Bravo, SIMH developers!

  OK, so he reported that they worked, and indeed produced a good disk copy, but they emitted a zillion error messages when run with an emulated system as the target. A little study showed what that problem was - I had used timing loops extensively in the downloaded code, and SIMH and real MicroVAX IIs run a whole heckuva lot faster than the real MIcroVAX I hardware does - so things were timing out and retrying a lot more than they needed to.

  Another dive into the MicroVAX architecture documents produced a solution - there is an Interval Timer included in the MIcroVAX architecture. If enabled, it will produce an interrupt every .01 seconds. This will allow me to produce a delay of fixed time, regardless of how fast the machine or simualtor it is running on.

  The clock interrupt is controlled by the ICCS (Interval Clock Consoltr/Status) register, internal register #^X18. When bit 6 is set in it, it will produce an interrupt via the vector at SCB offset ^XC0 every 1/100 of a second. When bit 6 is clear, the clock interrupts are turned off.

  So, setup the vector for the clock interrupt.

        mfpr    #^X11,r7        ;move scb addr to r7
        moval   tick,^XC0(r7)   ;point vector C0 at routine "tick".

  Here's the interrupt routine. Not much to it, nicht wahr?

tick:   incl    clock
        rei

  Then, whenver I wanted to delay for some fixed amount of time, regardless of processor speed, I enable the clock interrupt, clear the clock variable, and wait until the variable "clock" reached the number of 1/100ths of a second I needed.
 
 A macro is used to invoke it.

;+
; Wait for specified number of .01 seconds ticks
;-
        .macro  waitm   beat
        pushl   beat
        calls   #1,waitm
        .endm   waitm

  Here's the routine the macro invokes..

;+
; Wait for a specified number of "ticks"
;-
        .entry  waitm,^m<r2>
        movl    4(ap),r2        ;# of ticks needed to r2
        clrl    clock           ;start fresh
        mtpr    #64,#^X18       ;Enable timer interrupts 
1$:     cmpl    r2,clock        ;had enough?
        bgequ   1$              ;please sir, I want some more
        mtpr    #0,#^X18        ;turn off the interupts when done
        ret

  On the plus side, it was simple and easy to write. On the negative, it will totally not be of any use if I ever start using multiple streams of execution. As is, It has to be used with caution if used in an interrupt routine, since that has the potential to step on the use of it in the non-interrrupt code. For now I elect to just be mindful of that when coding. Sketchy as hell, but, in this no-OS environment, what isn't?

  As well as taking care of the timing loop issues, these new versions address a deficiency that Ulli pointed out - the disk unit targeted had to be assembled into the program. He pointed out that being able to enter what unit to target would be a lot more flexible, and less prone to writing on the wrong disk by mistake. In support of that, he figured out how to get input from the console, and provided examples to demonstrate how to specify the unit number to target at run time. Thanks again, Ulli!. I also added the ability to specify what file to load at run time as well.

  Here's new versions of remotewrite.mar,localwrite.mar, localread.mar, remoteread.mar and sendcheck.mar. Note that the packet format changed, so that the older versions of these programs won't work with the newver versions - new remotewrite won't work with old localwrite, and so forth. New versions of the PDP11 programs are coming soon.

      *** New versions loaded 1-SEP-2023 ***








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...