Wednesday, June 22, 2022

DX protocol comms host software for DECmates

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

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

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

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

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

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

DX User's Manual

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


ACKNAK.FTN

BYE.FTN

CHRIN.FTN

DISPLA.FTN

DSKIN.FTN

DSKOUT.FTN

DXINIT.FTN

DXINS.CMD

FINTSX.FTN

FSUBS1.FTN

FSUBS2.FTN

FVALID.FTN

GETCHR.FTN

GETNUM.FTN

GETVAL.FTN

HEDOUT.FTN

INTSIX.FTN

INTTXT.FTN

LDSPAC.FTN

LOOKUP.FTN

MAKHED.FTN

MISC.FTN

NEWSET.FTN

PRINT.FTN

PUTHED.FTN

RCVMSG.FTN

RECV.FTN

REPLY.FTN

SEND.FTN

SETCHAR.MAC

SNDMSG.FTN

SPCFLX.FTN

SPECS.FTN

STROUT.FTN

TXTWP8.FTN

VALID.FTN

VALUE.FTN

WP8DEL.FTN

WP8DIR.FTN

WP8FLX.FTN

WP8LPT.FTN

WP8OUT.FTN

WP8PIP.FTN

WP8TXT.FTN

WPS2.FTN


Saturday, May 14, 2022

PDT-11/150 and TU58

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

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

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

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

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

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

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

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

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

>set term tt5:/remote/speed:2400

  I set the PDT terminal 1 to 2400 baud

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

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

>run etu58
ETU>tt5:=tu58.dsk

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

.INIT DD0:

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

ETU58.MAC

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

TU58FS - File Sharing with a DECtape II simulator

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

So...

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

On the RSX system...

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

RUN ETU58
ETU>TT5:=rt64kdisk.dsk

  On the PDT...

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

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

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

.DIR DD0:

 0 Files, 0 Blocks

 65467 Free blocks

  Lots more blocks now than 512...


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

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




  

Wednesday, April 13, 2022

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


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


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

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

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

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

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

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


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

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


localread.mar

localwrite.mar

localcheck.mar

remoteread.mar

remotewrite.mar

remotecheck.mar

remread.mac

remwrt.mac

remchk.mac


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

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

  Save your change and assemble remwrt.mac

  >mac remwrt=remwrt

  Task build it...

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

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

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

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

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

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

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

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

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

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

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

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

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

RQDX3 Online complete

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

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

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

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


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

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

  Save the edited file. Assemble it.

$ mac remotewrite

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

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

  Make a system, image with SIMH.

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


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

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

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

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


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

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

  And that's our story thus far.


Wednesday, January 19, 2022

Using the PDP11 T-Bit trap

   Recently, I've been writing a lot of PDP-11/73 code that runs with no OS. After a little practice, I've gotten the hang of it, more or less, and after you're used to it, it's really no harder than writing code for RSX. In some ways, it's easier...Device drivers? Don't need em! Rules? There are no rules!

  Where it suffers in comparison is the debugging tools available. The machine itself provides Micro-ODT, a limited subset of RSX ODT - basically you can read and change values by address or register number.

  So that leads to the old classic technique of diagnostic print - adding code to the program that prints out values as it executes, as you look for where a problem is occurring. And that works pretty well, although it can be a bit of an effort, and time consuming as well.

  On one of these projects, I ran into a problem where an RTS instruction was being overwritten by...something (this programming environment doesn't support read only PSECTS for code). After attacking with diagnostic prints and Micro-ODT, I wasn't getting anywhere - I couldn't home in on exactly when it happened, or what was the culprit. There were thousands of lines of  code,  and some of the code was interrupt driven, so things weren't happening in a simple linear fashion.

  It occurred to me that I could use the T-bit trap to determine exactly what instruction was overwriting the RTS.

  The T-bit is a bit in the PDP11 processor status word (bit 4, mask value 16.). When set, it causes each instruction executed to  trap via vector location 14. You can load this trap vector to point to a routine, which does...whatever it needs to do. It can  then return by doing an RTT (Return from Trap)  instruction.  Basically. There are some more fine points to it, but for this project, that's it in a nutshell.

  So, I coded up a routine to check the value of the mauled RTS instruction after each instruction. When the routine detects that the RTS has been changed, it halts the machine, to allow for further inspection via Micro-ODT. Additionally, it stores the return PC on the stack each time it is called, in a simplified circular list. When the T-bit is set, this code will execute after every instruction. It will give me a log of where it's been, and halt right after the instruction that mauls the RTS instruction. Should take me right to the scene of the crime, nicht wahr? Here's the code.

  Lessee. a few notes about the routine first...the PC list is just a block of words. The first word is used as the address of the next free entry in the list. When the list is full, the index is set back to the beginning of the list - thus it's more or less "circular". Label ZAZEN is the address of the RTS routine that is getting clobbered. At TBITX, we check to see it it is still 207 - the octal value of an RTS instruction. If so, proceed. If not, it's like Jim Morrison said... "WAIT! There's been a slaughter here!" Halt and figure out what just happened. Oh, and push is a macro that translates to mov    arg,-(sp), and pop translates to mov    (sp)+,arg.


ttrap:
        push    r0               ;save r0
        mov     ttab,r0          ;address of next free entry to r0
        mov     2(sp),(r0)+      ;save return PC in that entry
        mov     r0,ttab          ;update next free entry pointer
        cmp     #ttab+2000.,r0   ;are we at the end of the table?
        bge     ttrapx           ;if not, pass on
        mov     #ttab+2,ttab     ;if so, reset to top of table

ttrapx: cmp     zazen,#207       ;is the RTS instruction still there?
        bne     ttrapy           ;if not, halt
        pop     r0               ;restore r0
        rtt                      ;get back and execute the next
                                 ;instruction

ttrapy:  halt                    ;something overwrote the RTS!
                                 ;break out the .LST file and
                                 ;get to looking!

ttab:   .word   ttab+2           ;stores addr of next free wd in ttab
        .blkw   1024.            ;1000 words, plus some slop at end


  OK, now all I gotta do is load the vector, set the T-bit, run the program, and open the champagne. Warmed by the mellow glow of my own cleverness, I proceeded to do just that. I added this at the top of my program.

   mov     #ttrap,@#14   ;load t-bit vector with a debug routine
   mov     #340,@#16     ;load a regular sort of PSW in the vector
                         ;priority 7 - don't wanna get interrupted

   psw = 177776          ;16 bit address of the PSW
                         ;we're using 16 bit addresses for this
                         ;project - it's a utility, not an OS.

   tbit = 16.            ;bit 4 is the T-bit, 16. as a mask value
   bisw    #tbit,@#psw   ;this is an 11/73, so I can directly access
                         ;the psw, or use MTPS.

   But - it didn't work. I tried it again, and displayed the PSW after I set it. It didn't "take" - the bit was not set. I tried it again using MTPS (MoveToProcessorStatusword ) instruction - but. no soap there either.

  A little research found a footnote in one of the PDP11 processor handbooks, that pointed out that you can't set the T-bit value like the rest of the bits in the psw - you have to create a PC/PSW pair of words on the stack,with the bit set in the psw word, then let it take effect when that word gets put into the psw during an RTT instruction. OK, then, easy enough to do - I pushed a return address and new PSW on the stack, and then did an RTT instruction to make it happen.


   push    #60       ;push a PSW value with T-bit set, priority 1,
                     ;on the stack
   push    #zzz      ;push the address of the rest o' the program
   rtt               ;then return from trap, setting the T-bit and
                     ;resuming execution

zzz:    (rest of program goes here)


  And that set the T-bit just fine.

   At that point, I got a trap to my routine after every instruction. When the RTS instruction was overwritten, the machine halted, and I used Micro-ODT to have a look at the PC log buffer, to see what instruction overwrote it, and how it got there. Only...the log showed that the instruction that did it was part of a timing loop, that merely incremented a register, and tested its value. It didn't write memory at all, much less write to where the RTS instruction was. The instructions pointed to by the PC's stored in the list leading up to it were all the same, more iterations of the timing instructions. There was no way that this caused the overwrite. Yet, here it was. A little more thinking made me realize, that if the CPU hadn't overwritten the instruction, then it had to have been DMA from a controller card. I suspect that the RQDX3 was doing the overwrite - the RTS instruction was located adjacent to an RQDX3 buffer, and the RQDX3 accesses host buffers via DMA.

 I fiddled with the RQDX3 code for a bit, and couldn't figure out why it was machine gunning DMA where it wasn't welcome. So, for a solution, I put a couple or words between the RTS instruction, and the RQDX3 buffer, and the overwrite landed on those words instead of the RTS instruction. Not sure exactly why the RQDX3 is writing a word before its buffer starts, but, there's plenty I don't know about how the RQDX3 works. Maybe it's alignment related. But, it's results that I'm interested in on this project... The spice must flow. All's well that ends - and the T-bit trap gave me the clue I needed to figure it out.


Thursday, January 6, 2022

VAXstation 1 Software Install - part 5 - RQDX3 IO

  The main difficulty in reading and writing disk blocks with the RQDX3 was a lack of documentation. There's no programmers guide to the RQDX3. There are generic guides to using MSCP, and there is some info about programming UDA50s available, which was useful since MSCP devices are all programmed pretty much the same. It took quite a bit of experimentation to get simple single block reads and writes to work. But, all's well that ends. Bob Schor's commented disassembly of the RT11 MSCP bootstrap was very helpful. The  best doccos I found for  doing this project were found online in the usual places...

 Mass_Storage_Control_Protocol_Ver_2.4.0_Jun92.txt. 
AA-L621A-TK_UnibusPortDescription_1982.pdf
AA-L619A-TK_MSCP_BasicDiscFnsV1.2_Apr82.pdf

  QBUS configuration rules put the first RQDX3 at  172150, which translates to ^X20001468 in MicroVAX 1 IO address speak. If you have a second RQDX3 in a MicroVAX 1, well, you're on your own. It will go in floating address space. The interrupt vector of the 1st RQDX3 is 154, which we don't care about at all, since we don't use disk IO interrupts in this project - it's hard enough to get all of this to work without trying to  overlap disk and ethernet IO.

  The RQDX3 only has two registers, which doesn't make it easier to deal with, since they are  overloaded to hell and gone, doing a dozen different things, depending on what's happening. They are rich in state, and it's up to you to keep track of what state things are in. The two registers are called IP and SA. 

 The IP register has two uses. When written with any value, it causes a hard reset of the controller.   When read while things are operating. it causes the controller to initiate "polling", whatever that is. Just kidding, I know what that is. I'm just not all that interested in it for this project. 

  The SA register (where do they get these crazy names?) has four functions (see? I told you, they overload the hell out of these two registers). When read during initialization, it sends and receives data related to initialization. When written during initialization, it sends information to the controller. When read during normal operation, it returns status and error info. When a 0 is written to it, at any time, it tells the controller that a purge has been completed, whatever that is. Seriously, we don't use it in this program, so I don't need it.

  The real communication with the card is done a lot like the way the DEQNA does it - through lists of buffers that the card DMAs in and out of memory, willy-nilly, anytime it feels like it. Again, just kidding. You can control when it happens. More or less.

  But enough of these vague generalities - let's get right on to initialization. 

  To get ready for this, you need to prepare a table of four "steps" - longword values that will be passed into the RQDX3 during initialization. Bob Schor's commented disassembly of the RT11 MSCP bootstrap was very helpful in figuring this part out  The table looks like this...


    istbl:
    ; Step 1 - assorted configuration bits        
               
.word   ^X8000  
                        ;Step 1 bits
                        ;[15] = 1,
                        ;[14] (WR) = 0,
                        ;[13:11] (cmd  ring size as power of 2) = 0
                        ;[10:8]  (resp ring size as power of 2) = 0,
                        ;[7]  (Interrupt Enable/Disable) = 0
                        ;[6:0] (int vect/4) = 0
                        ;command and response rings = 1 element
                        ;no interrupts
                        ;no interrupt vector.

    ; Step 2 - Ring base address, low 16 bits. Entered here as 0,
               since PIC coding requires that we fill it in at                       runtime
     zazz:     .word    0

    ;Step 3 - Ring base address high 6 bits (always 0 for us),
              and Purge/Poll bits (likewise, 0 for us).
              .word   0 

    ;Step 4 - The GO bit. Finishes up the initialization                            .word   1

    ;End Marker - a null longword to signal the end
              .long   0 


  Since the table of values used for initialization requires the address of the command and response rings (to be stored in location zazz, above), let's discuss setting them up here, even though they don't get used until we do the ONLINE command.

  We indicated that we have only one command and one response packet, in the second entry above. You specify how many you have as a power of 2 - we entered zero for each there, so 2 to the 0th power is 1. RP and CP are the response and command packets, below. You put their addresses into the command and response rings table. Gotta do almost all of this at run time, since we're writing PIC here. Each entry of a command packet in the rings table consists of the address of the packet, and an ownership flag word - when it's ^X8000, we are saying that the the RQDX3 owns it. When the RQDX3 finishes processing it, it sets the flag to be non-negative - that's how we know it's done, and that we can use it again - and more important, when doing a read or write command, we then know that the card is through and the data is now valid

rp:    .blkb    64
cp:    .blkb    64

rings:
rpd:    .word    0
        .word    0
cpd:    .word    0
        .word    0


                moval    rp,rpd
                movl    #^X8000,rpd+2
                moval    cp,cpd
                movl    #^X8000,cpd+2

    
            moval    rings,zazz


  OK, Once all of this is set up, to start the initialization, you write anything into the IP register. CLRW writes a 0 to it.

    ;Define some symbols for the registers
           rcsr   = ^X20001468     ;RQDX3 CSR starts her
           rip    = rcsr           ;1st reg is called I
           rsa    = rip+2          ;2nd reg is called SA

           clrw    @#rip

  I should mention that longword accesses aren't allowed to the IO space on the MicroVAX - use word instructions. Also, data structures need to be longword aligned, or difficult to debug problems can occur.


  So that starts this moving. Now, read a word from the SA register. If it's negative, an error has occurred.  If it's positive, check and see if the initial step value bit is set in it (the initial match  value is ^X0800). If that bit is set, shift the match value one bit left, and then write the first value from ISTL into the SA register. Get another value out of the SA register, and test it for negative and bit match. If so, write the next istbl value to SA.  Repeat these steps until the match value hits ^X8000. If you get that far successfully, then the card is now initialized.


  OK, then you have to put the card into the online state. It's a whole 'nother story. The ONLINE command uses the command packets and rings we discussed above. Let's talk bout the contents of the packets themselves.


 The command packet for the ONLINE command is laid out thusly... 


                   31                             0               
                  +-------------------------------+              
                  |   command reference number    |              
                  +---------------+---------------+              
                  |   reserved    |  unit number  |              
                  +---------------+-------+-------+              
                  |   modifiers   | rsvd  | opcode|              
                  +---------------+-------+-------+              
                  |  unit flags   |    reserved   |              
                  +---------------+---------------+              
                  |           reserved            |              
                  +-------------------------------+              
                  |                               |              
                  +---        reserved         ---+              
                  |                               |              
                  +-------------------------------+              
                  |  device dependent parameters  |              
                  +-------------------------------+              
                  |           reserved            |              
                  +-------------------------------


  Command reference number - we don't use it - we're doing one command at at time, so we don't need to keep track of them. Unit number for me is always gonna be 0. If you need a different unit number - you know what to do.  The opcode is mscp$k_op_onlin, ^X0009. Modifiers - don't need any. Unit flags, likewise. Device dependent parameters - none. All in all, it's a pretty simple packet.

  The response packet is where the RQDX3 tells us how the command went. For the ONLINE command, it is laid out like this...

                   31                             0               
                  +-------------------------------+              
                  |   command reference number    |              
                  +---------------+---------------+              
                  |sequence number|  unit number  |              
                  +---------------+-------+-------+              
                  |    status     | flags |endcode|              
                  +---------------+-------+-------+              
                  |  unit flags   |multiunit code |              
                  +---------------+---------------+              
                  |      reserved         |spndles|              
                  +-------------------------------+              
                  |                               |              
                  +---     unit identifier     ---+              
                  |                               |              
                  +-------------------------------+              
                  |     media type identifier     |              
                  +-------------------------------+              
                  |           reserved            |              
                  +-------------------------------+              
                  |           unit size           |              
                  +-------------------------------+              
                  |      volume serial number     |              
                  +-------------------------------+              


  Command reference number and sequence numbers - I don't need 'em. Endcode - forget about  it. The status is where the status of the action is returned - we check it for errors. All the rest, don't need 'em. The media type identifier, unit size and volume serial number are interesting, but...don't really need 'em for this project


  OK, so you've set up these packets, and have gone through initialization as per above. Now, to execute the ONLINE command in the command packet,  write anything but zero into the IP register to launch things into motion. Then, loop until the value in rpd+2 is not negative, which means the online command has completed. Check the response packet status field and see if there was an error (success will be zero, anything else was an error). What could be simpler?

            mov    #1,@#rip

  Then you loop on the value in rpd+2 until it is non-negative

            1$:    tst    rpd+2
              blss    1$

  OK, all of this work, and all we've done is put the disk successfully online. Now, let's see if we can write a block (that we've presumably received on the ethernet - remember the whole point of this project?)


  Writing a block (and reading one, for that matter)  is not all that different from the ONLINE process. You set up the Command and Response packets, set up the RIng table, and then write a non-zero value to the IP registers. The main difference is in the command packet - it's a little different.


                   31                             0              

                  +-------------------------------+             
                  |   command reference number    |             
                  +---------------+---------------+             
                  |   reserved    |  unit number  |             
                  +---------------+-------+-------+             
                  |   modifiers   |  caa  | opcode|             
                  +---------------+-------+-------+             
                  |          byte count           |             
                  +-------------------------------+             
                  |                               |             
                  +---         buffer          ---+             
                  |                               |             
                  +---       descriptor        ---+             
                  |                               |             
                  +-------------------------------+             
                  |      logical block number     |             
                  +---------------+---------------+             
                  |   entry id    | hrn or entloc | (optional)  
                  +---------------+---------------+             



  You have to enter a byte count (512, since we're dealing with disk blocks), a logical block number (they start at 0), and a buffer descriptor. The format of a buffer descriptor includes support for all sorts of exotic situations, none of which apply here, using a QBUS and an RQDX3. It boils down to, put the address of the (read or write) buffer in the first longword, and zeroes in the other two. Unit number - we're still using 0. The opcodes for read and write, mscp$k_op_read and mscp$k_op_write, are ^X21 and ^X22. When you get the packet set up, set up the cpd, rpd and rings table again (if you need to), and once again write something to the IP register to make the magic happen. Loop on the value of rpd+2, just like for the ONLINE command, to wait for it to complete. And, if everything has gone just right, your block of data should have been read or written....


  In using the above techniques to read and write, I noticed that every few hundred thousand reads or write, the RQDX3 hangs up - never indicates completion. When that happens, all I do is go through the initialization and online steps again, and then do the read or write again.

  And a note about the returned statuses. a status of 0 is success. Errors take a myriad of formats. The most common one I've run into is invalid input data. This error code consists of offset*256+1. The offset refers to the offset in the command packet that is invalid - so for instance, a returned status of ^X1C01  - the 1 in the low bit tells us, invalid input. Divide it all by 256 and you get ^X1C - that's an offset into the command packet of 28, which is the logical block number field - you get a 1C01 error when you try and write to a logical block number that doesn't exist (ie, too high for the size of the disk).

  So, that's a sketchy, incomplete and confusing summary of how to talk to an RQDX3 in the simplest way possible. Poorly written, I'm well aware - but, this, along with the documentation  sources I mentioned above should give you a fighting change to make an RQDX3 do something. And pretty much everything I've described applies to RQDX3's when used in a QBUS PDP11 as well as in a QBUS MicroVAX. Actually, when I wrote the PDP11 versions of the code for this project, it was easier than the MicroVAX version - PDP11 code loads at 0, instead of 7000, so I didn't have to write it PIC. And the IO page addressing is simpler on the PDP11, which made finding the cards easier.

 Next and final installment - the programs that use all this info to  load and save disk images to a VAXstation/MicroVAX 1, and QBUS PDP11s as well.



Saturday, September 25, 2021

VAXstation 1 Software Install - Part 4 - Accessing the DEQNA

   OK, everybody make yourself comfortable - this is a long one....

  Next I needed to write routines to read and write to the DEQNA and RQDX3. It sounds simple enough. I've written device drivers for RSX11 and VMS before. This time I won't even have an OS in the way. There are no rules! But, my previous drivers were all either software only devices (that is, using a device driver to get a QIO interface for my kernel mode routines), or simple devices that had a simple CSR and input and output data buffer registers. DEQNAs and RQDX3s are much smarter devices than that (for a while, I was thinking they were both much smarter than I was). They expect complex data structures to be set up in memory, and they DMA data in and out of them as they see fit, whenever they feel like it, with no rhyme or reason  At least that's the way it seemed. But, I managed to puzzle them out.

  The key for both of them was to just implement the minimum subset of their functions that I needed for this project. Both devices have complicated support for having queues/lists of multiple IOs  and data buffers active at one time, with attendant complexity for managing them. This application doesn't need that - if it works faster than asynch RS232, it's a win, so I could omit a lot of code dealing with multiple simultaneously active IOs.

  There was information about how to program the DEQNA in the DEQNA Ethernet Users Guide - EK-DEQNA-UG-001. And some of the info in the DELQA manual (EK-DELQA-UG-002) applied to the DEQNA, and parts of it were explained better there. If you choose to follow this twisted path to getting the DEQNA to work, you'll likely want to read both of them. There were also some clues to be found in the RUST-11 project by Ian Hammond. Another place I looked for examples was the DEQNA driver in 2.11 BSD (yes, I was so determined to get this to work I even read UNIX code). I had a look at the VMS and RSX drivers, but they were a little too...recondite...to help much. The PDP11 boot code contained in the DEQNA roms was of some help as well.

  So, on to the DEQNA. Let's discuss some of the problems I ran into getting it to send and receive.. One pitfall I ran into here was figuring out why the DEQNA wasn't interrupting. I described this as well in Part 3. I tried a lot of things before it occurred to me that the default machine  IPL after power on might not be below 4, the interrupt level. Code for this is in Part 3.


  Some other unexpected problems - The DEQNA routines took a while to get working, since I didn't notice some things I wasn't expecting. To be fair, these were mentioned in the doccos. Two bits in the CSR (RI and XI) are cleared by writing a 1 to them. One bit in the CSR (IL) is considered set when it is 0 and clear when it is 1 (a curious definition of set and clear, nicht wahr?). And longword alignment of all the data structures is absolutely necessary.
                           
  Additionally, all reads and writes to device registers on the MicroVAX 1 have to be words or bytes - longwords aren't welcome there.

  I also had forgotten that ethernet packets had to be a minimum length to be considered valid, so sending was a problem when I had them too short. These problems would have been apparent sooner, but with no OS, there were no friendly informative error messages - just a lack of results, or even worse, cryptic mis-results. When it doesn't work...change something and try again...again and again. Anyway, I padded the disk block request  messages that were too short with some nonsense text, and those sends started working. The data messages all had a disk block in them, so they were plenty long already.

  But enough about pitfalls (and pratfalls) - let's talk about what you actually have to do to send and receive on the ethernet using a DEQNA.

  First. let's talk about the device registers of the DEQNA. There's 8 of them. How do we find them? The doccos say that the first DEQNA in a system has these  registers located starting at 174440. That's the octal 16 bit IO page PDP-11 style address. That ain't gonna do any good here. But let's think about what it means. 174440 means it is located 14440 octal bytes into the IO page on a 16 bit PDP-11, which begins at 160000. We have to translate that to a 22 bit MicroVAX1 IO page address. According to the MicroVAX 1 architecture manual, the MicroVAX 1 IO page starts at  ^X20000000 (bit 29 set and above).  So, ^X20000000 + 14440 octal is ^X20001920. What could be simpler? If you have two DEQNAs (DEQNAae? DEQNAnen?), the second one will be at ^X20001950, but, I got my hands full on this project with just one, so we'll speak no more of a second DEQNA, OK? If you're the kind of person who needs two ethernet adapters on a MicroVAX, you're the kind of person who can figure out how to get it to work..

  Anyway, to make things more interesting, the DEC engineers made some of these registers have multiple functions. If you do a simple read of the the first 6 registers (word registers, not longwords, remember?), you get the 6 octets of the ethernet address of the card, in the six low bytes of the words read.. This gave me something simple to do to check that I had found the card in the IO page.

        15                                                                                                  0
        +----------------------------------------------+ 
    |       reserved      |    address byte 1      | ^X20001920
    +----------------------------------------------+ 
    |       reserved      |    address byte 2      | ^X20001922
    +----------------------------------------------+
    |       reserved      |    address byte 3      | ^X20001924
    +----------------------------------------------+ 
    |       reserved      |    address byte 4      | ^X20001926
    +----------------------------------------------+ 
    |       reserved      |    address byte 5      | ^X20001928
    +----------------------------------------------+ 
    |       reserved      |    address byte 6      | ^X2000192A
    +----------------------------------------------+            

  Simple enough, nicht wahr?  Six reads and you're done...

  

adrbuf: .blkb    6

        movl     #^X20001920,r6
        moval    adrbuf,r7
        movl     #6,r8

rloop:  movb     (r6),(r7)+
        addl     #2,r6
        sobgtr   r8,rloop

        dump     #6,adrbuf
        halt

  Here's what it looks like - tested on a MIcroVAX 1 that has a DEQNA with an address of 08-00-2B-26-2F-84

>>>B XQA0
ATTEMPTING BOOTSTRAP

08002B262F84

 And we got the 6 bytes of the ethernet address.


  While we're talking register addresses, let's save a little memorizing of hex numbers and define some symbols for them.

        dadr1  = ^X20001920     ;address byte 1
        dadr2  = ^X20001922     ;address byte 2
        dadr3  = ^X20001924     ;address byte 3
        dadr4  = ^X20001926     ;address byte 4
        dadr5  = ^X20001928     ;address byte 5
        dadr6  = ^X2000192A     ;address byte 6

        drbdll = ^X20001924     ;receive bdl addr low register
        drbdlh = ^X20001926     ;receive bdl addr high register

        dtbdll = ^X20001928     ;transmit bdl addr low register low
        dtbdlh = ^X2000192A     ;transmit bdl addr high register

        dvec   = ^X2000192C     ;interrupt vector register
        dcsr   = ^X2000192E     ;csr



  OK, that's when you're reading them. When it comes to writing them, the first 2 registers aren't used. The third and fourth registers, at ^X20001924 and ^X20001926 are where you store the 22 bit address of the receive buffer descriptor list, whatever that is. Just kidding, I'll discuss it later.  Also, writing the second of these registers is the "Go" signal for a read to start.

        15                          05                  0
        +-----------------------------------------------+
        |          low 16 bits of RBDL address          | ^X20001924
        +---------------------------+-------------------+
        |                           |high 6 bits of addr| ^X20001926
        +---------------------------+-------------------+



  In like wise, the next two registers, at ^X20001928 and ^X2000192A are where you write the address of the transmit buffer descriptor list. More to come on that as well. As well, writing the second of these registers is the "Go" signal for transmitting a packet.


        15                           05                 0
        +-----------------------------------------------+
        |            low 16 bits of TBDL address        | ^X20001928
        +---------------------------+-------------------+
        |                           |high 6 bits of addr| ^X2000192A
        +---------------------------+-------------------+


  The next register is read/write, at ^X2000192C. It's where you write the value of the DEQNA's vector. At power on time, it's 774, the standard diagnostic vector.  We leave it at that (it's as good a value as any, in this case).

        15            10 09                       02   0
        +---------------+-------------------------+--+--+
        |   Reserved    |                         |RR|RR| ^X2000192C
        +---------------+-------------------------+--+--+

  RR means reserved. Reserved reads back as 0.


  And finally, the good ole CSR. 

        15                                              0
             +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 
    CSR |RI|RR|CA|OK|RR|SE|EL|IL|XI|IE|RL|XL|BD|NI|SR|RE| ^X2000192E
        +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 

  RR still means reserved. Let's run down what the rest of these abbreviations  mean - it'll help give you a false sense that you know what this card is doing...

RI - R/W - Receive Interrupt Request. Set when the DEQNA has fully received a packet. The Interrupt Service Routine must clear this bit, if it ever wants to run again. Treacherously enough, to CLEAR this bit, you have to write a 1 to it, which any sane person would think is the way to set a bit - but...not this bit, not here...

CA - RO - Carrier. This reflects the state of Carrier Sense. We don't use it. 

OK - RO - Fuse OK. You know that fuse in the bulkhead of the Ethernet connector, that you've always wondered what the point of it was? This signal tells you if it's blown or not. No fooling. Now you know why it's there.
 
SE - R/W - Sanity Timer Enable. This timer can be used to reboot the whole danged machine if the Ethernet gets all hung up. Since the last thing I need is the system rebooting while I'm trying to get something done, I don't mess with this.

EL - R/W - External Loopback - This bit and IL are used to put the DEQNA into an assortment of loopback modes for testing.  I don't need these - if Wireshark doesn't see traffic, I'll know there's a problem.

IL - R/W - Internal Loopback - see above explanation. This bit has the additional  wrinkle of being set at boot and reset time, to prevent a troubled DEQNA from machine gunning the network. It has to be cleared to allow transmits.

XI - R/W - Transmit Interrupt Request - When set by the DEQNA, this bit indicates that a transmit has completed all the way. You have to clear this bit (note well - this is another treacherous bit of design, that is cleared by writing a 1 to it) to enable any more transmit interrupts.

IE - R/W Interrupt Enable - .Set this bit to enable transmit and/or  receive interrupts, when XI and RI are set as well. 

RL - RO - Receive List Invalid - When set, it indicates that the receive list is empty. It is set at power up and cleared by reset. We are only using one item receive lists, so this bit isn't as important as it could be.

XL - RO - Transmit List Invalid - Like RL, only for transmit. 

BD - R/W - Boot Diagnostic ROM - The DEQNA has a boot/diagnostic ROM on board. Since it's written for the PDP-11, it's not much use here on a MicroVAX.  I read it while testing, since moving it to a buffer was something I could get the card to do, to see if I was talking to it at all. There's a few gyrations to go through to get it to read it out - if you have any interest in doing that, may I recommend the DEQNA User's Guide?

NI - RO - Nonexistent Memory Interrupt - This gets set when you use nonexistent memory. A bunch of side effects happen. It's a bit of a mess. I recommend avoiding it, by being careful where you read and write.

SR - R/W - Software Reset - Set this bit to reset the DEQNA. Clear it when you want to come out of reset.

RE - R/W - Receiver Enable - When set it enables receiving packets. It is cleared by power up and reset.


  OK, this list of complex and interacting bits should be enough to tell you, this ain't gonna be a matter of loading a data buffer and watching a ready bit, like a DL11 or a line printer. And we haven't even discussed setup of the card yet, which is it's own special challenge.


  Let's talk about how you really talk to the card - buffers. Buffers are read and written from/to the card using DMA.  Each buffer has a Buffer Descriptor that describes the buffer. The address of the Buffer Descriptors gets stored in the registers described above, so's the DEQNA will know where to get data to and from. Buffer Descriptors are kept in lists.

  The format of the transmit and receive buffer descriptors is thus...for each buffer, there is a buffer descriptor like this...

              15141312111009080706            0 Bit #
              +-+-+---------------------------+
      Flags   | | |                           |
              +-+-+-+-+-+-+-+-+-+-+-----------+
Descr. Bits   |V|C|E|S|x|x|x|x|L|H|AdrHi6bits |   
              +-+-+-+-+-+-+-+-+-+-+-----------+
              |  Buffer Address Low 16 bits   |
              +-------------------------------+
              |         Buffer Length         |
              +-------------------------------+
              |         Status Word 1         |
              +-------------------------------+
              |         Status Word 2         |
              +-------------------------------+
 
             Additional Buffer Descriptors )if any)follow


   There are only two bits defined in the Flags word, and, irritatingly enough, they don't have official names - we'll call them bits 15 and 14. They, along with bits 15 and 14 in Status Word 1 are used as a handshake between the MicroVAX and the DEQNA about who's got control of the buffer. 14 and 15 values are...

          15  14
           1   0   DEQNA not yet using this buffer
           1   1   DEQNA using this buffer


  The Descriptor bits ...

        V - 15 - Valid - when set, indicates that this descriptor is valid
        C - 14 - Chain - when set indicates that this descriptor points to another descriptor                                    instead of a buffer
        E - 13 - End of message - (transmit descriptors only) - When set indicates that this                                   descriptors buffer contains the end of the packet and it's good to be transmitted.
        S - 12 - Indicates that this descriptor points to a setup packet
         11:08 - undefined
        L - 07 - Low byte only - (transmit descriptors only) - when set indicates that that this                                 buffer ends on a byte vs word boundary
        H - 06 - High byte only - (transmit descriptor only) - when set indicates that this buffer                             starts on a byte boundary.
        
  You can have a big long list of descriptors, one after another in the list, describing lots of buffers, and can even indirect off to another list of them. but, we're not doing any of that here. One buffer at a time each for transmit and receive will be plenty for this project. To indicate that this is the last and only buffer descriptor in play,  throw in a few empty longwords, so the DEQNA will know there aren't any more.

  Note the the buffer length field is the size of the buffer, in words, as a negative number. Man, those hardware guys...

  As well, to simplify things, I'll only transmit word aligned buffers with an even number of bytes, so we don't have to worry about setting Descriptor bits H and L

 Wow, this is a lot of typing, and we haven't even discussed initializations and setup yet. And the RQDX3 is even worse than this. Well, can't stop now, let's reset and init  the card. The card is in the reset state when it is powered on. We clear the whole CSR, which, among other things, clears the S/R bit, which takes us out of reset. 

        clrw    @#dcsr          ;reset the card

  Then we set the bits we need for normal operation. This includes setting bit IL, which, annoyingly enough, clears it (which is how we need it).

        movw    #^X141,@#dcsr   ;set RE, IE and IL (to clear it)

  We set Receive Enable and Interrupt Enable, since here at the start we want to be able to pseudo receive the setup packet, and we want an interrupt when it completes. We clear IL by setting it, since we don't want any loopback going on.


  Speaking of setup, it's time for that part of the movie. Setup consists of telling the card what addresses to enable. We only need to use two addresses. One is FF-FF-FF-FF-FF-FF - all one's, the broadcast address. This is used while we are looking for a load host. The other one is the legit address of the card, which for these examples is 08-00-2B-AA-BB-CC. 

  Setting this up on the DEQNA is...funny. You have to lay the addresses out that you are interested in, in a data structure with a less than obvious pattern. Then you create a transmit buffer descriptor that points to that structure as a buffer, and has bit 12 (the S (setup) bit in the above diagram) set in the address descriptor bits. When you signal the DEQNA to pick it up (by setting the buffer descriptor's address in the transmit descriptor registers, it DMAs it in and "pretends" to transmit it. Instead of putting it on the wire, though, it sets up the addresses in the structure as addresses it will use. Then it acts like it received it and you have to process it as a received packet before you can get to any real traffic. This was definitely designed by a hardware engineer instead of a software guy.

  Anyway, here's an example of how that structure looks, for addresses 08-00-2B-AA-BB-CC and FF-FF-FF-FF-FF-FF. You have to rotate 'em and lay them in vertically. Additionally, the first column  is all 0's. As well, even though we only have two addresses we care about, it's recommended that you fill in all of the possible 14 addresses with valid addresses - so we just duplicate the one non-broadcast address we are using. Complicated, nicht wahr?


         1st set of 7 addresses
                                
        07                     00  Bit #
        +--+--+--+--+--+--+--+--+
        |08|08|08|08|08|FF|08|00|  0  addr offsets
        +--+--+--+--+--+--+--+--+        
        |00|00|00|00|00|FF|00|00|  8
        +--+--+--+--+--+--+--+--+
        |2B|2B|2B|2B|2B|FF|2B|00|  16     
        +--+--+--+--+--+--+--+--+
        |AA|AA|AA|AA|AA|FF|AA|00|  24
        +--+--+--+--+--+--+--+--+
        |BB|BB|BB|BB|BB|FF|BB|00|  32
        +--+--+--+--+--+--+--+--+
        |CC|CC|CC|CC|CC|FF|CC|00|  40
        +--+--+--+--+--+--+--+--+
        |00|00|00|00|00|00|00|00|  48
        +--+--+--+--+--+--+--+--+
        |00|00|00|00|00|00|00|00|  56
        +--+--+--+--+--+--+--+--+

         2nd set of 7 addresses

        07                     00
        +--+--+--+--+--+--+--+--+
        |08|08|08|08|08|08|08|00|  64
        +--+--+--+--+--+--+--+--+        
        |00|00|00|00|00|00|00|00|  72
        +--+--+--+--+--+--+--+--+
        |2B|2B|2B|2B|2B|2B|2B|00|  80     
        +--+--+--+--+--+--+--+--+
        |AA|AA|AA|AA|AA|AA|AA|00|  88
        +--+--+--+--+--+--+--+--+
        |BB|BB|BB|BB|BB|BB|BB|00|  96
        +--+--+--+--+--+--+--+--+
        |CC|CC|CC|CC|CC|BB|CC|00|  104
        +--+--+--+--+--+--+--+--+
        |00|00|00|00|00|00|00|00|  112
        +--+--+--+--+--+--+--+--+
        |00|00|00|00|00|00|00|00|  120
        +--+--+--+--+--+--+--+--+

  It's madness, but, ya gotta give 'em what they want . Here's the MACRO to set up that buffer.

sbuf:
 
         .byte     0,  08,^XFF,  08,  08,  08,  08,  08
         .byte     0,  00,^XFF,  00,  00,  00,  00,  00
         .byte     0,^X2B,^XFF,^X2B,^X28,^X2B,^X2B,^X2B
         .byte     0,^XAA,^XFF,^XAA,^XAA,^XAA,^XAA,^XAA
         .byte     0,^XBB,^XFF,^XBB,^XBB,^XBB,^XBB,^XBB
         .byte     0,^XCC,^XFF,^XCC,^XCC,^XCC,^XCC,^XCC

        .long   0,0,0,0

         .byte     0,  08,  08,  08,  08,  08,  08,  08
         .byte     0,  00,  00,  00,  00,  00,  00,  00
         .byte     0,^X2B,^X2B,^X2B,^X28,^X2B,^X2B,^X2B
         .byte     0,^XAA,^XAA,^XAA,^XAA,^XAA,^XAA,^XAA
         .byte     0,^XBB,^XBB,^XBB,^XBB,^XBB,^XBB,^XBB
         .byte     0,^XCC,^XCC,^XCC,^XCC,^XCC,^XCC,^XCC

        .long   0,0,0,0

        sbuflen = . - sbuf


  So, to make this take effect, like I've been saying, you have to sort-of transmit it. To do this, create a transmit buffer descriptor for it.

        .align  long                                            ;make sure its longword aligned


sbufd:  .word   ^X8000          ;flags - it's valid and unused
        .word   ^X8000!^X2000!^X1000  ;address descriptor bits
                                ;it's valid, setup and end of message
                                ;and the high 6 bits of bufadr are 0
        .word   sbuf            ;low 16 bit addr of setup buffer
        .word   -sbuflen/2      ;length of the setup buffer in words
ss1:    .word   ^X8000          ;status word 1
ss2:    .word   ^X8000          ;status word 2

        .word   ^X8000          ;flag of next buffer
        .word   0               ;which is null

        .long   0               ;some empty longwords...
        .long   0               '...to indicate...
        .long   0               ;...the end...
        .long   0               ;...of the list


  OK, we're getting there. There's one little wrinkle in the data structure above. Remember way  back in Part 1, where I mentioned that we had to write all this as Position Independent Code? Well, just writing

         .word    sbuf

  Will cause the assembly time value of SBUF to be deposited at that address. But we need the run time address of SBUF there, since we don't load at a normal address. Not a real problem - we add the lines

        movaw   sbufd,r6        ;load addr of buffer desc to r6
        movaw   sbuf,r5         ;get run time addr of xbuf
        movw    r5,bfd$w_loa(r6);store low 16 bits of addr in bd 

  These three lines get the relocated, run time address of sbuf, and truncate it to 16 bits as it is stored in the buffer descriptor (bfd$w_loa is set elsewhere to be 4, the offset to the low 16 bits of the address in the descriptor).


  In order for setup to complete, like I've been saying, you have to pseudo transmit the setup buffer. But in order for it to complete all the way, you have to pseudo receive it. For that we need a receive buffer, a receive buffer descriptor, and set the two receive buffer descriptor registers on the DEQNA. This will get it ready to "receive" the setup buffer when we sort of send it.

  We'll need a receive buffer....nothing special about that.


        .align    long

rbuf:   .blkb    2000      ;longer than we'll ever need
        rbuflen = . - rbuf ;record its length

  Then we'll need a receive buffer descriptor, to describe it.   

rbufd:  .word   ^X8000      ;flags - it's valid and unused
        .word   ^X8000      ;addr desc bits - it's valid, and the...                              ;...high 6 bits of setup buff are 0
        .word   rbuf        ;low 16 bits addr of setup buffer
        .word   -rbuflen/2  ;neg length of the setup buffer in words
ss1:    .word   ^X8000      ;status word 1
ss2:    .word   ^X8000      ;status word 2

        .word   ^X8000          ;flag of next buffer
        .word   0               ;which is null

        .long   0
        .long   0
        .long   0
        .long   0


  Then, just like the transmit descriptor, fix up the address of the buffer.

        movaw   rbufd,r6        ;load addr of buffer desc to r6
        movaw   rbuf,r5         ;get run time addr of xbuf
        movw    r5,bfd$w_loa(r6);store low 16 bits of addr in bd 

  Then load the address of the descriptor in the receive descriptor register of the DEQNA, and clear the high 6 bits register (in that order). The manual says, load the low order 16 bits first, and then the high order 6 bits last, which is the receive GO signal to the card. All throughout this project, it's assumed that we stay within the 16 bit address limit, even though the QBUS on the MicroVAX 1 is 22 bits large - this is a complicated program, but not a big one, so the upper 6 address bits will always be 0.

        movw    r6,@#drbdll     ;load low addr of setup bfd into csr
        clrw    @#drbdlh        ;clear high word and start it all up

  OK, now to finally actually tell the DEQNA to do setup. To pseudo transmit the setup buffer, we have to load the two DEQNA transmit buffer registers with the address of the transmit  buffer descriptor. Again, we load the low order 16 bits first, and then the high order 6 bits last, which is the GO signal to the card to "send" the buffer..

        movaw   sbufd,r6        ;load addr of buffer desc to r6
        movaw   sbuf,r5         ;get run time addr of xbuf
        movw    r5,bfd$w_loa(r6);store low 16 bits of addr in bd 

        movw    r6,@#dtbdll     ;load low addr of setup bfd into csr
        clrw    @#dtbdlh        ;clear high word and start it all up

  Now we can just spin until the receive interrupt is done, which signals the end of the setup process. Nothing to it, right? Piece of cake so far. We set a flag word in the interrupt receive routine to indicate that this setup receive is complete. We just blbc loop on that flag word until it's set. 


setwat:  blbc    setupdone,setwat  ;spin until deqna init completes


 Additionally, in the receive interrupt routine, for this setup and for normal receives, we reset the CSR to ^X141, reset the fields in the receive descriptor, and reset bit RI in the CSR (by writing a 1 to it), to get ready for the next receive.

  Now that setup is done,. we can send packets out on the wire. Set up the transmit descriptor just like we did for setup, but don't set the setup bit (^X2000). In the buffer for a normal transmit, enter the 6 byte destination address, the 6 byte address we're using for the card, and a two byte packet type code. We're using 60-06 - that's reserved for DEC customers )and that's us!).

The buffer looks like this....destination address, source address, and two bytes of type. Then the message we want to send, followed by some fillter to make the packet long enough.

xmbuff:  .byte  ^XFF,^XFF,^XFF,^XFF,^XFF,^XFF    ;dest addr
         .byte    08,  00,^X2B,^XAA,^XBB,^XCC    ;source addr
         .byte    06,^X60                         ;type
         .ascii /Payload to transmit goes here/
         .blkb   48   ;filler - payload has to be at least this long

                     xmbufflen = . - xmbuff





  To receive, it's just like the receive we described during setup. 

 OK, now we can send and receive. In broad strokes, what the program does is broadcast a message to all stations (to address of FF-FF-FF-FF-FF-FF) looking for someone who can load the file we need. A normal VMS program running on the load host is listening for that, and replies that it can provide that file. The program running on the MicroVAX then sends packets that contain the number of the  block it wants next. The program on the load host sends back a packet containing that block. The MicroVAX program writes that block, and then requests the next one. We'll talk about what it takes to write the data blocks via the RQDX3 in the next section.