HP-IPL/OS Disk Programming

5/1/05 - contents of this page are subject to change... this is an area of active development.
3/6/07 - still developing :) just not this text anymore so may not exactly match current code.
7/20/07 - out-of-date disk builds removed, procedures should be mostly the same. Older versions of XDOS had a bug in XDEL (left a value on the stack), never noticed until I tried to call it from another IPL program, corrected in XDOS 1.26 and in the v1.1 builds. Also modified XLOAD so that it executes -TBG if present to prevent failure when loading systems without a TBG interrupt handler present.
8/30/08 - added info/links for the new library system.

Disk drive programming can be tricky business, especially considering all the different drive types that can be hooked up to a HP21xx minicomputer or simulator. Hard-coding disk functions to a particular drive prevents its use with other drive types, so we developed a device-independent solution which uses a collection of low-level block-oriented disk words that interface with device-specific driver code, reducing all drives to a few simple functions. Transfers are performed 1KW at a time regardless of the actual sector size, allowing disk software to be written that works without change on a variety of drive types. Presently drivers are available for the 7900 disk using the 13210A interface, the 7906 using the 13037 interface, and for Bob Shannon's custom IDE interface. More drivers are planned.

Skip ahead to...

Currently available files concerning disk programming...


Dos Progress...

For awhile all we had for a "dos" was TDOS, which is limited to 32 files and uses file numbers rather than names. Crude to say the least but it proved the concept, I developed it using a 7900 disk then Bob ran it on his IDE disk and despite its limitations it was better than nothing. Then came XDOS in April 2005... much better! Whereas TDOS only supported 32 files with no filenames or directories (all it knew was block offsets derived from file number), XDOS supports up to 64 volumes each containing up to 64 files with up to 16 character filenames. XDOS has commands for adding, deleting, renaming, and changing to volumes, saving system binaries, transfering files to and from alternate memory, displaying file contents, listing volume and file directories, deleting/renaming files, loading binary systems or IPL files, and various recovery and import/export functions... pretty much everything needed to operate HP-IPL/OS from a disk.

XDOS is based on a draft spec from Bob called "Simple File System" (SFS) which I've had for awhile but for various confusing reasons haven't been able to fully implement. The SFS specs consisted of two parts, a disk layout and a set of words for accessing disk files. XDOS implements the disk structure of SFS but not the file access words. In XDOS the only way to "process" files is copy to alternate memory and process there, or just go straight to the disk and do what's needed block by block. For operation purposes this is fine and in some cases the only way to get the job done. A "dos" needs to operate at a low level and doesn't fit with a high-level file programming model like SFS so I found it hard to implement SFS without a dos. Now I have a way to operate and debug the system so I started writing sfs.ipl again [...]

5/12/05 - SFS lives! Presently 0.66 seems to be hanging in there, came out about 2KW in size. The present configuration borrows the 16Pad and FindEntry subs form XDOS, same code is copied into sfs.ipl and commented out for reference and to adapt to other configurations. The SFS ipl provides only file-access words, it contains no words for formatting the disk, manipulating volumes, renaming files, listing directories etc and relies on XDOS for these things. Still very early, needs lots of testing and possibly changes or bug fixes if problems are detected.

5/15/05 - fixed XINIT and XSHOW, added $VOL to push the current volume name (from the XCV variable set by XDOS). Also added ?SFS that prints CW, drive#, vol# and file# for each of the 4 SFS buffers, handy.

5/16/05 - the XSHOW bug-fix in build .67 probably wasn't really a bug, after fiddling with it realized I was pressing enter so reworded the prompt to say "space" instead (but ended up being smaller because of other tweeks), but now I found another XSHOW bug... when listing binaries it fails to stop at the end of memory and locks up in an indirect loop. Press Q to exit when binary listings get past 77000 or so, will look into it when there's other fixes to make.

5/26/05 - SFS 0.67 had a serious bug I should have noticed but didn't until I tried to write a file copy. The problem was <FILE and >FILE saved the specified buffer number to the same internal variable causing major malfunction... separated the buffer number variables for SFS 0.68, don't know what I was thinking. While I was fixing that I also modified ?SFS to list filesize and pointer for open files, and modified XDIR so instead of printing dir block info (same numbers can be had from LSVOL) it prints current drive number then volume name.

6/1/05 - SFS 0.69 posted, binaries include the slow library system.

6/15/05 - posted experimental 7906 build, has all the "stuff" except no boot extension yet, fixing to write one that incorporates a few new features over the 7900 bootext, including auto-adapting to slot# and an option to halt to give an opportunity to enter config info manually. Coming soon...

6/19/05 - posted "TestB" version of 7906 build with the config stuff. Note that unlike my 7900 boot extension this one runs from 2012 to write to disk - there is a HLT in 2011 for when things go wrong and execution wraps. SR must contain the disk controller slot in bits 6-11 (if not in slot 16). Set bit 15 of SR before running to write boot extension to fixed platter. Only drive 0 is supported for booting. After writing it halts with code 44. This boot extension can patch its own slot addresses if the disk controller is moved, rewriting is not necessary. To reconfigure slots when booting from disk, set bit 5 of SR to halt after loading then enter TTY and BACI slots into bits 0-5 and 6-11 and press run. Set bit 12 for baci console (not tested), set bit 15 with bits 6-11 clear to disable autostarting with a TTY console. Autostarting is required for baci to work. When booting from papertape, when it halts to reconfigure put TTY and BACI slots in SR as described, set P to 70 and run. Normal running from location 2 has not been altered.

This is about the simplest scheme I could come up with, most of the functionality is in a tiny CFGE word which sets 70 to jump to itself and clears 72. If run from 70 it sets the slots, puts the SR value in 72 and runs from 2. !SDC picks up 72 and if non-zero sets TTY or BACI console depending on the setting of bit 12, clears location 72 and runs from 2 again to reconfigure the console. CONFIG itself has been expanded to allow setting the slot of the new HPIB driver (also not tested), and sets slot vectors up to 47 to dummy subs if any slots are changed. It is not necessary to have !SDC and CONFIG present, CFGE is enough to reconfigure via SR and run from 70.

Here's an edited dump of a simulated 7906 SFS/XDOS session from this morning, as I arranged files to my liking and removed the baci and hpib drivers and high-level config stuff from my personal build.


Library Updates and Original Speculations...

8/30/08 - This new implementation of the library does away with the binary byte to mark the end of the library (although can still read the old format, if adding code the mark won't be removed). The transfer utilities are now named D>L, LDEL became LFORGET because it removes everything after a segment too (like FORGET), added A>L and L>A for copying alt mem to the library and vice versa, and MS>L for copying a library file from papertape into the library. Putting multiple words into a library segment to run-and-forget requires that the first word be the same name as the library name, and also be the word that runs the application. For example, to put the following contrived app in the library...

VARIABLE COUNT
DEFINE PCOUNT
COUNT GET PNUM END
DEFINE DOCOUNT
#0 7 +DO COUNT INDEX PUT PCOUNT +LOOP END

...rename the main word (DOCOUNT in this case) to (say) MAIN and add a new word with the desired name that runs MAIN, print the components to alternate memory using redirection, then copy it into the library...

RENAME DOCOUNT MAIN
DEFINE DOCOUNT
"MAIN" $DEFADR DUP IFZ DROP ELSE EXECUTE ENDIF END
ZAM 2 MSUSER >MS ;clear and redirect prints to alt mem
EXPLAIN DOCOUNT ;add the new "chain" word that runs it first
"VARIABLE COUNT" $PRINT CRLF ;manually print any variable lines needed
EXPLAIN PCOUNT ;add any subwords needed by the app
EXPLAIN MAIN ;add the renamed main app word
"LIBEND" $PRINT CRLF ;manually print LIBEND line to end the segment
CONSOLE ;undirect MS and console
"DOCOUNT" A>L ;copy to library (assumes it's open)

Another way to make a library app is to write it directly...

OCTAL DEFINE DOCOUNT
"MAIN" $DEFADR DUP IFZ DROP ELSE EXECUTE ENDIF END
VARIABLE COUNT
DEFINE PCOUNT
COUNT GET PNUM END
DEFINE MAIN
#0 7 +DO COUNT INDEX PUT PCOUNT +LOOP END
LIBEND

...then use "name" A>L (if written with AEDIT) or "name" MS>L (if a PC file attached to PTR). It is also possible to convert existing IPL apps/files to use with the library by changing the CONSOLE at the end to LIBEND (and maybe remove comments to make smaller). It's still a manual process but still quite useful, one of the main advantages of loading utilities from the library is it does not disturb alternate memory, which may contain something being worked on.


11/19/07 - The "fast" library system
uses machine code to search for ";;" markers, makes a HUGE difference
in operation speed. The old system might have taken a minute to search
for a file, now it only takes a second or so and without having to use
an index system. A new SETLIB word permits changing the library file
and saves the details in an array variable so it sticks with DGEN. An
LBUF variable defines which SFS buffer is used for the library. An
LCLOSE word saves changes and releases the buffer, LIB reopens the last
library defined/opened by SETLIB. Operationally it's pretty much there.

5/17/05 - This "slow" implementation is a bit simpler than I originally envisioned, no variables are needed to hold the "current" library and to switch to another library simply open another one (using a new LIBOPEN or similar word). Rather than variables and stuff to set them just DEFINE MYLIB drive "volume" "filename" LIBOPEN END then entering MYLIB would load the library without further complication. Assuming the library-opener gets named LIBOPEN.

In general mostly nameless terms, from my perspective to make a library system useful I need to be able to...

Tell it with a variable what buffer to use. Default 3 to use the last SFS file buffer but can be changed with a PUT in case there isn't that much memory available. The minimum practical SFS / Library configuration would be 32KW each for system, alternate, buffer 0 and buffer 1 (128KW total) with the library assigned to buffer 1 leaving buffer 0 available for programmed file operations.

Open a specific library file into the assigned buffer. If the file doesn't exist it should create an empty library in the required format, then open it. I've written this code, haven't tested but just a straightforward use of DIRECTORY OPEN and CNF. Because SFS should be off when running non-SFS utilities that write to disk (-SFS) need a quick way to get the library back but that's trivial as noted above.

Close the library file, writing changes to disk if modified. For efficiency I don't want things that save to the library to write to disk every time they're used, and a mistake could corrupt the library, therefore nothing is written to disk until the library is closed.

Run something from the library and remove it from the dictionary after the program exits. Very simple (fast) syntax, something like R name. This is fairly easy to program using an easily-parsed index table, but requires a lot of code and extremely slow using non-seeking stream methods like MSSCAN which is ok for papertape but I don't want to wait 25 seconds for it to find something that takes 1/2 second to run. SFS has SEEK, works great and since only a pointer is updated, it's extremely fast. Once found, loading, running and forgetting the IPL code is easy.

Load something from the library, leaving it in the dictionary, same process as above but skip run/forget.

Write a single high-level word in the dictionary to the library (in memory). This is where it gets pretty tricky. I could put an index table at the very beginning but that puts garbage in front of a simple text viewing of the library. Putting the index at the end makes the filesize of the library always the same, worse. Both methods put hard limits on how many IPL chunks can be addressed. Instead the index should be appended to the last IPL chunk. The best arrangement I've discovered so far is:

(a) OCTAL DEFINE WORD
definition END
LIBEND
(b) OCTAL DEFINE ETC
perhaps an existing IPL defining multiple words
starting with an ETC word
LIBEND
[space pad if not even] (remaining is binary, no more CRLF's)
(i) [000000]
[WO] [RD] [000004] [offset a]
[ET] [C ] [000003] [offset b]
[offset i] (last word points to 0 before index)

All offsets are in bytes to permit feeding them directly to the SEEK command. The last 16-bit word of the file (always even in size) contains a SEEK pointer to the zero marker that starts the index, this marker location must contain zero to be a valid library file. Note that the names are stored in the same mannor as HP-IPL/OS words, a listing of the library contents would resemble a WORDS display. A little inconvenient as far as listing names but this method is very efficient (4 words per chunk of IPL) and permits use of existing HP-IPL/OS subroutines to parse the command parameter and convert the name to the internal TB1/TB2/TL format. Note that IPL chunks should end in something besides CONSOLE, which precludes running and forgetting unless that functionality is hard-coded into the IPL chunk itself. I'd rather specify. Whatever the chunks end with, that word would do the CONSOLE, and provide run/forget if that's what was requested.

The format allows entire multi-word IPL files to be considered one chunk, so need something that copies an IPL disk file into the library, automatically changing the normal CONSOLE at the end into whatever word is needed to allow run and forget to work. Entire packages consisting of dozens of words can be run then removed so long as the name in the index is the same as the first word of the package, that can be arranged.

Need to delete words from the library. This will probably be tricky since it is not desireable to leave holes, everything after the deleted word needs to be moved down to fill the space, and index offsets adjusted accordingly. Just a thing, nothing too difficult.

Finally could use a word that lists the contents of the library, XSHOW would work (listing the entire file) but something that looks like the WORDS display would be more useful.

As usual all of this is subject to change as [or if] code is written. If anyone has any comments let me know.

A Slow but Simple Approach to Library Files...

[the following is really outdated... left to document HP-IPL/OS history...]

5/28/05 - The index-based system of library storage is likely a superior method, especially for speed, but the algorithm involves a few tricky operations like temporarily storing indexes at the end of the file's space but backwards to avoid introducing artificial limitations like the number of words that can be stored. Then there's error-checking while all that's going on to limit it before there's a collision. Same old stuff but I just went through all that and my desire to implement a library scheme exceeded my time I have for programming, so I went back to MSSCAN just to get something working. Scanning an input stream (any stream let alone one that has to USPACE twice for every byte read) is extremely slow, taking up to 20 seconds or more to find a word to load, but at least it works.

The original implementation used the existing msscan.ipl words but I discovered a serious problem with the old RUN&DEL, in that it kept the address to delete on the stack while the word was running, very very dangerous and prevents using the stack to pass parms. Boy did it prevent... random pokes, ouch. So I modified it to save the address in a variable to allow typical utilities to function when running out of the library.

6/1/05 - This implementation includes msscan and adds words for using as a library system...

LIB loads or creates LIBRARY in the volume LIB which must already exist on drive 0.
"name" D2L saves a definition in the dictionary to the library (but not to disk).
"name" R loads and runs a word from the library then removes it from the dictionary.
"name" L loads a word from the library, leaving in dictionary.
"name" LDEL deletes word and all following words from the library.
LDIR lists word names in library.

Nothing is written to disk unless the library is closed (3 CLOSE), if desired a library save command can be defined to contain something like 3 CLOSE LIB to save the library then open again.

This scheme allows large multi-word IPL chucks to be run/removed provided the word carrying the chunk name appears first, this is the case if MKWORD/ENDWORD is used to encapsulate subs vars and all into a single word. At the moment I don't have a file-to-library fuction but I don't think it would be all that difficult. IPL-chunks must end with LIBEND rather than CONSOLE so they can be run then deleted, arrange accordingly. There must always be an ascii 128 at the end of the library file to tell MSSCAN to stop.


Installing and Using XDOS

XDOS lets a HP-IPL/OS user save, load and show files to and from named disk files arranged in named volumes. For convenience XCV contains a number that defines the entry number of the "current" volume. Changing to a volume sets XCV and other words use XCV to know what volume to operate in. This gives an illusion of traveling, under the hood it specifies the volume for the lower-level functions so the user doesn't have to specify it as often. XDOS includes words for creating, renaming, deleting, and changing to volumes, transfering alternate memory to and from files, listing the volume or directory blocks, and for renaming and deleting disk files. The formatter and other utilities are in their own ipl file, load as needed.

The first thing to do is get XDOS and the utilities into memory. XDOS requires the words in dms.ipl and disk.ipl, and a working disk driver must be in place. If TDOS is loaded and you don't need it anymore, FORGET +DB to get rid of it. Attach xdos.ipl to papertape reader then LOAD, then attach xutils.ipl to paper and LOAD that too. At first it's ok to load xdos.ipl on top of TDOS to make both available, just be careful not to use TDOS to save files to a XDOS volume because no directory entry is added and XDOS will eventually overwrite it.

Next, "format" the disk using XINIT or XI79 if using a 7900 disk. The init process puts geometry numbers into disk block 6 and resets the volume names to all-space. Nothing outside of block 6 is changed. XINIT prompts for the number of volumes, and the number of files in the last volume. Intermediate volumes are allocated 64 files each. To get the maximum SFS "partition" size of ~260 megabytes enter 100 octal for both numbers. XI79 is hard-coded to divide the disk into 3 volumes of 12 files each. Once the init has been run, nothing else is supposed to ever change the geometry numbers. If for some reason block 6 gets corrupted, rerun XINIT (or XI79) then run VRECOVER to add generic volume names to entries that appear to point to a non-empty directory.

Now create a volume to work in. If there are TDOS files present do "TDOS" MKVOL right after XINIT to protect the files, then optionally "TDOS" CHVOL XRECOVER to create a directory for those files (note.. takes awhile to determine filesize by scanning the file itself). If you just don't go there the files should be safe. Using a 7900 formatted with XI79 this protects only the first 12 TDOS files, make another volume if necessary to protect more. Finally (or right after formatting if not preserving anything) make a volume to work from, something like "MYSTUFF" MKVOL then "MYSTUFF" CHVOL to make it "current". If you DGEN at this point, then booting the disk or entering DBOOT will take you back to this location. "filename" XSAVE saves the current system to a file, "filename" XLOAD loads a system or ipl file. XDIR lists the directory and the current volume. Note... the current volume concept is strictly for operating convenience, nothing to do with SFS (but an SFS program is free to make use of the current volume code if it helps).

Caution... do not use XDOS commands until XINIT, XI79 or another SFS-compatible formatting utility has been run first to write a valid volume index table to block 6. Operating with random data would not be good.

The xdos.ipl file provides the following words...

"VolName" MKVOL - creates a new volume and overwrites the directory block with zeros
"VolName" CHVOL - makes named volume current
"VolName" "NewName" RENVOL - renames a volume
"VolName" DELVOL - deletes a volume after confirming
LSVOL - lists available volumes and their stats
XDIR - prints current volume name/stats and lists the files it contains
ZAM - writes zeroes to alternate memory
"FileName" bytes AM2F - copies specified number of bytes from alt memory to a file
"FileName" F2AM - copies a file into alternate memory and sets DirInfo array
"FileName" XSAVE - saves the current running system to a file
"FileName" XLOAD - loads a system binary or ipl file
"FileName" XSHOW - shows file contents as text or hex if binary
"FileName" "NewName" XREN - renames a file
"FileName" XDEL - deletes a file after confirming
XCV - variable containing current volume number
WKBUF - constant containing address to use as a 1K buffer
VolInfo - 5-location array containing current volume stats
DirInfo - 6-location array containing current file stats
FindEntry - subroutine for searching for volume or directory entries
GetVol - subroutine that uses XCV to load VolInfo with current volume stats
GetFile - subroutine that searches for a named file, if found pushes location and sets DirInfo
PutFile - subroutine that uses DirInfo to create a named file, if successful pushes location

The xutils.ipl file provides...

XINIT - general-purpose SFS formatter, run first for IDE
XI79 - formatter for the 7900 drive, 3 volumes of 12 files each [now in its own file]
"FileName" bytes MS2F - copy MS input to a file (not redirection)
"FileName" F2MS - copy a file to MS output (not redirection)
"FileName" ABS2F - import ABS from MS input to a system file, prompts for ra and exit
VRECOVER - recovers deleted volumes, use after using XINIT to fix block 6
XRECOVER - recovers files in current volume and attempts to guess size

When importing IPL files you must know the filesize, use a dos prompt to determine file size in decimal. After attaching the PC file to the MS input device (default is papertape reader) then If the file is say 12345 bytes long use something like DECIMAL 12345 OCTAL "MYFILE" MS2F to copy to a file. Get the size right, if MS is read past the end of the input stream it essentially locks up until you feed it the specified number of bytes. To copy from something other than papertape then preceed the MS2F command with MSBACI or another MS driver.

When importing ABS or other binary files the transmission medium must accept all characters without terminating, there might have trouble trying to punch it through BACI when BACI is also the console but who knows might work. More likely the trouble will be getting the PC terminal software to send a binary with no formatting without barfing, some terminals try to echo the garbage with unpredictable results. Papertape is the most reliable HP<>PC method, and is the only method under simulation. Typically when running under SIMH I ctrl-e to get the sim prompt then enter attach ptr whatever.ipl followed by run 2 (or c to continue but that doesn't redisplay the prompt which bothers me so I reset a lot...) to return to the HP-IPL/OS prompt and do whatever.

ABS2F checks the imported file to see if it has 124003 in location 2 (a jump vector, required to load), if not present then it prompts for a startup address and asks if it should add an exit vector at locations 77/76. To my knowledge this is specific to HPBASIC and restores HP-IPL/OS after entering BYE. The XDOS load function saves the currently running system in alternate memory before loading another system, so long as the newly loaded system doesn't use alternate memory you can return to the previous system by executing location 77000. Successive calls to 77000 swap the two systems back and forth. The exit vector prompted for by ABS2F puts 77000 in location 76 and JMP 76,I in location 77.

The F2MS command copies a HP disk file to MS output, by default papertape punch. The output file must already be attached. On a real machine the best bet is rig up software to capture a raw serial stream then MSBACI "filename" F2MS, unless you're lucky enough to have a punch emulator. Under SIMH punching to a file works fine, attach ptp filename.

VRECOVER and XRECOVER utilities allow recovery of deleted volumes and files, especially useful if something corrupts block 6 requiring the use of XINIT. The new volumes and files are given generic filenames like VOL000000 VOL000001 FILE000007 etc. For shorter names DECIMAL first, don't forget to OCTAL afterwards. The number indicates fileslot, rename with VOLREN as needed. I just tried XRECOVER after copying a file then deleting it, got back my file plus a garbage file. VRECOVER and XRECOVER aren't supposed to touch entries that already have a name so theoretically they won't hurt anything. XRECOVER scans empty entry filespaces, if 124003 is present then assumes its a binary system and fills in the bytesize as 177400, otherwise it scans the file word by word until it encounters a zero, doubling to fill in a byte size that's always even. Not perfect but IPL files are tolerent of appended characters so in practice this seems to works fine.

Copying a file can be accomplished by copying to alternate memory using "file" F2AM then saving to a new file using "file" #bytes AM2F - use XDIR to determine #bytes, or get from DirInfo 3 ADD GET. For example: "sourcefile" F2AM DirInfo 3 ADD GET "destfile" AM2F. Copying between volumes can be accomplished by inserting "volname" CHVOL after the F2AM command.

Presently XDIR only works correctly in OCTAL mode, in decimal the formatting isn't right and numbers larger than 32767 are displayed incorrectly. Octal numbers are more useful when most everything else is in octal but eventually I'll probably clean up the decimal display.


Using SFS for File Access

Bob Shannon's Simple File System allows the programmer full read/write access to files by linking them to HP-IPL/OS' "MS" (mass storage) stream system. SFS provides commands for attaching a directory block (volume) to a buffer, opening files by copying them to the buffer, reopening a file at the last access point, attaching MS out to an open file for writing, attaching MS in to an open file for reading, setting the byte pointer for random access, closing the file writing it to disk if changed, creating a new file and deleting a file. When redirected MSBIN MSBOUT MSWIN MSWOUT MS$IN MS$OUT MSCRLF and anything using them (including LOAD) can be used for reading and writing files. MSPAPER (or CONSOLE including after an IPL load) restores normal papertape MS I/O.

SFS uses subroutines from XDOS as written must be loaded after XDOS, also needed at minimum to format the disk and make a volume to work from. Total requirements for sfs.ipl are extra.ipl create.ipl, dms.ipl, double.ipl, disk.ipl, ide.ipl 7900.ipl or another disk driver, and xdos.ipl. The xutils.ipl file is required for formatting but can load after sfs.ipl, FORGET when done. For convenience sfsbins.zip contains binary builds for Bob's IDE disk and for the 7900 disk, tested under SIMH but maybe it will work on the real thing.

The main SFS words are...

"name" buffer drive DIRECTORY - loads specified directory into specified buffer
"name" buffer OPEN - copies specified file into specified buffer with pointer=0
"name" buffer REOPEN - copies file into buffer with pointer=last access point
buffer >FILE - redirects MS output to buffered file for writing
buffer <FILE - redirects buffered file to MS input for reading
buffer byte SEEK - sets pointer for next read or write
buffer CLOSE - close the file, writing buffer to disk if anything was written
"name" buffer CNF - create a new empty file
"name" buffer DEL - deletes a file
buffer RELEASE - marks buffer as unused

SFS supports up to 4 buffers max, buffer number must be 0-3. Before a file can be opened, reopened, deleted or a new file created there must be a directory in the buffer. >FILE and <FILE should only be used with open files, if not no error message is displayed (those are machine-coded words) but FSS is set to indicate error and nothing useful happens. SEEK and CLOSE only work if the specified buffer contains an open file. RELEASE can be used at any time but if a file is open all changes including last access pointer are abandoned and nothing is written to disk. XDOS commands that write to disk don't work unless all SFS buffers are released first to avoid potential tangles.

The FSS variable contains file system status, FSS GET PNUM to view, FSS 0 PUT to clear. Several variable arrays contain information about directories and files attached to buffers. See sfs.ipl for error codes and what the various variables do, some contain vital information about where things are so don't change them except for FSS. CWA array entries can be manipulated if you know what you're doing.

Programmers can also select the proper alternate memory page directly using USPACE with the buffer number plus 1 on the stack then manipulate alternate memory directly. ALWAYS 0 USPACE WHEN DONE! if you access alt mem directly. The streaming subroutines do this automatically so this is only a concern when the programmer uses USPACE (as always but with files also in memory it becomes very important). After making changes set the appropriate bit in CWA (locations 200-203) to mark the buffer as dirty so CLOSE will write it back.

Terminology note... generally "directory" refers to a 1KW disk block containing file information, and "volume" refers to the collection of files defined by that directory block. As I write it, you change to a volume to do stuff in that volume with XDOS, and you load a directory to access files listed in it with SFS. In normal use the terms are interchangeable.

SFS example...

"WORK" MKVOL ;make a volume using xdos
"WORK" 0 0 DIRECTORY
"FILE" 0 CNF
"FILE" 0 OPEN
0 >FILE "Hello, file" MS$OUT MSCRLF
0 CLOSE
"WORK" CHVOL ;change to volume using xdos
"FILE" XSHOW ;display file just created
"FILE" 0 REOPEN
0 >FILE "More stuff" MS$OUT MSCRLF
0 CLOSE
"FILE" XSHOW ;line was appended
"FILE" 0 OPEN
0 <FILE MS$IN $PRINT ;print 1st line of file
0 CLOSE
"FILE" 0 REOPEN
0 <FILE MS$IN $PRINT ;prints 2nd line of file
0 CLOSE
0 RELEASE ;if done to use XDOS commands that write

Note... be careful using MS$IN to read unknown files, if CRLF is not encountered or an SFS error occurs the X-stack overflows. Another reopen, read, print would have produced this error because the file only contained two lines, this is how MS$IN works. If an attempt is made to read past the end of file, 0 is returned and FSS set to 601. Of course MS$IN doesn't know that so it piles up 0's until the X stack overflows. For better control use MSBIN/MSBOUT to read and write bytes directly, for example...

;assumes dir already loaded
FSS 0 PUT ;clear previous errors if any
"FILE" 0 OPEN
0 <FILE
DO ;until eof
MSBIN ;get byte
FSS GET IFNZ ;if eof error
DROP 1 ;drop 0 and push 1 to terminate
ELSE
PCHR ;print char whatever
0 ;push 0 to keep going
ENDIF
UNTIL ;eof
0 CLOSE

Some disk operating systems set EOF immediately after the last byte was written but as currently written SFS doesn't flag an EOF until it tries to read past the end. I'm not sure which way is more correct, perhaps setting EOF after the last byte is read would make coding a bit easier (but not by much), however that means FSS would contain a non-zero word when no error took place. As written the FSS=601 code is an error that occurs if an attempt is made to read past the end of file which makes more sense to me, and permits more simplistic EOF detection of text by just looking for the 0.

SFS Utilities

The SFS utilities package is now in sfsutils.ipl, these are the original words...

;------------
; "DefName" D2F - writes def to file of same name in current vol
; must be high-level def, otherwise prints error message to file
OCTAL DEFINE D2F
$DUP $DEFADR IFZ ;if DefName not found
$PRINT " not found" $PRINT
ELSE ;DefName found...
#0 DRV GET $VOL DIRECTORY ;set buffer 0 to current volume
FSS GET IFNZ $DROP ELSE ;if error exit otherwise
$DUP #0 CNF FSS GET IFNZ $DROP ELSE ;create file, exit if error
$DUP #0 OPEN MS_SAVE #0 >FILE >MS ;open file, redirect print to file
$DEFADR PDEF "CONSOLE" $PRINT CRLF ;print loadable definition to file
<>CON MS_RESTORE #0 CLOSE #0 RELEASE ;undirect, close and release
ENDIF
ENDIF
ENDIF
END
;------------
; "file" destdrv "destvol" FCOPY - copies a file
; text/ipl files only, does not set LA/RA
OCTAL DEFINE FCOPY
#0 SWAP DIRECTORY ;load destination directory in buffer 0
FSS GET IFNZ $DROP ELSE ;if error drop filename otherwise
#1 DRV GET $VOL DIRECTORY ;load current volume directory in buffer 1
#1 $DUP OPEN ;open source file
FSS GET IFNZ $DROP ELSE ;if error drop name otherwise
#0 $DUP CNF #0 OPEN ;create and open destination file
MS_SAVE #1 <FILE #0 >FILE ;redirect MS outs to dest file
#1 FSA INC GET ;push 1 then byte filesize
+DO MSBIN MSBOUT +LOOP ;copy every byte
MS_RESTORE #1 CLOSE #0 CLOSE #1 RELEASE #0 RELEASE ;clean up
ENDIF
ENDIF
END
;------------
CONSOLE

5/26/05 - these are early versions, need to figure out a way to make FCOPY update the load/run addresses since CNF can only create text files. SFS is designed for file access and does not use the LA/RA directory fields, perhaps we need a utility for setting them. An automatic workaround is do like XDOS does and if location 2 contains 124003 then make LA/RA=2, ok for now but eventually we're going to want to use the fields for binaries besides entire systems, a more general solution would be "file" buffer loadadr runadr SETBIN or similar. [6/1/05 modified SFS to include the necessary arrays, to fix the example add code after the copy loop that copies the LAA and RAA array entries before closing]

5/27/05 - fixed minor D2F bug, removed extra $DUP. D2F and FCOPY have become "must have" utilities, still aren't in a IPL file yet but I've typed them in manually several times after finding myself without them.

6/1/05 - uploaded better sfsutils.ipl package that takes advantage of SFS 0.69's LAA and RAA arrays to properly set the load and run addresses when copying binary files. Modified FCOPY and added FMOVE which works the same way except deletes the source file after copying, and BCOPY which quickly copies one buffer to another using DMS techniques, updating the appropriate file stat directories. FCOPY and FMOVE include the core code from BCOPY for speed but are self-contained to allow running from the library without requiring BCOPY. See comments for more compact forms if BCOPY is kept in memory, not sure how often file operations will need to be programmed. Anything stored only in the library cannot easily be used for programming since loads do not take place until the console appears. BCOPY is a bit large so presently it's in the utilities package but could end up in the main SFS package.

8/13/05 - updated the sfs.ipl and sfsutils.ipl packages, now version 0.70, fixing bugs in FCOPY and FMOVE, and moving the very useful BCOPY word into the main SFS package. Also added a FDUP word which duplicates a file to a new filename in the current directory. At the moment these changes have Not been incorporated into the pre-made builds, until then if you want the new versions FORGET the old packages and reload the IPL files from papertape. The bugs were insufficient error-checking, be careful when using the old FMOVE which deletes the original file if the destination file already exists even though the copy did not take place. If this happens use the XRECOVER utility to restore the source file.


Low Level Disk Operations

The disk.ipl file defines low-level words that permit device-independent disk programming...

high low SBLA - seeks to a specified 1KW block
memadr R-1K - reads the current 1KW block into memory then increments the driver's block pointer
memadr W-1K - writes memory to the current 1KW block then increments the driver's block pointer
LDFB - loads a 31KW system binary image from the current block
<IDE - reads the disk drive status to detect errors, 0 means OK

...and also defines a few operator functions...

DRV - a psuedo-variable linked to a zero-page location that contains the drive number
n CHDRV - changes DRV to n, all disk functions use DRV to know what drive to act upon
DGEN - writes the current system to block 10 octal, updating the boot image
DBOOT - loads a system from block 10, in effect rebooting a HP-IPL/OS disk

The disk driver ipl file (in addition to the disk driver itself) also should define drive-specific functions, in particular ?DRV. How the DRV number is interpreted is up to the driver, the IDE driver uses it to add the double-word 000002 000146 to the blocks specified by SBLA, in effect skipping the entire SFS disk space. The 7900 driver uses DRV numbers 0 to 3 for removeable platters on real drives 0 to 3, and DRV numbers 4 to 7 to indicate the fixed platters of drives 0 to 3. 7900.ipl defines an additional word for changing platters on the current drive, 0 CHPL for removeable, 1 CHPL for fixed. ?DRV for the IDE prints the drive number and equivalent offset, whereas ?DRV for the 7900 prints the current drive and platter.

When XDOS is loaded its internal variables and subroutines are available, including the WKBUF constant, the XCV variable, subroutines FindEntry, GetVol, GetFile and PutFile, and variable arrays VolInfo and DirInfo containing numbers from the current volume and directory entries. DirInfo offset 3 contains the filesize in bytes after an XDOS file operation, handy for even higher level functions like providing filesize for AM2F, and WKBUF makes a handy substitute for typing 66000 or another memory address when using R-1K and W-1K to examine or modify disk blocks. The other XDOS subroutines and the low level disk words can be used by programs to directly manipulating volume or directory entries but aren't designed for console use except when debugging, and are not to be taken lightly. One wrong move can corrupt the disk! PutFile returns a block location you're supposed to write a file to but if your program confuses the stack you could end up overwriting something. Do not debug low-level disk software unless you don't mind losing the entire disk, you've been warned. Having said that, it is relatively safe to experiment on a different drive provided you ALWAYS make sure a reset or something didn't reset DRV back to zero. By convention a simple reset is not supposed to alter DRV but if a system binary is loaded then DRV is reset to whatever it was when that system was saved, just be careful. I develop under simulation so before every risky test I zip up my disk drive file then if something goes wrong it's no biggie, I just find and fix the problem and restore my disk.

LSVOL and XDIR display low-level details that can come in handy when debugging...

? LSVOL
Name Size DB_lo DB_hi FS_lo FS_hi
OLD 000014 000007 000000 000047 000000
WORK 000014 002247 000000 000647 000000
MYSTUFF 000014 002250 000000 001447 000000
? XDIR
Current volume: MYSTUFF NE/DBL/DBH: 000014 002250 000000
Filename LA RA LAP BSIZ CW BLKL BLKH Slot
XDOS 000002 000002 000000 174000 000000 001447 000000 000000
MAZE.IPL 000000 000000 000000 041521 000000 001507 000000 000001
CAPSLOCK.IPL 000000 000000 000000 003526 000000 001547 000000 000002
1DCA.IPL 000000 000000 000000 013727 000000 001607 000000 000003
XDOSCAPS 000002 000002 000000 174000 000000 001647 000000 000004
HPBASIC 000002 000002 000000 174000 000000 001707 000000 000005
XDOSGAMES 000002 000002 000000 174000 000000 001747 000000 000006

[note XDIR no longer displays dir block location, use LSVOL for this information]

The locations of directory blocks and files are displayed but keep in mind that in volume and directory entries (and LSVOL/XDIR output) locations are arranged low word first, SBLA expects the low word on top meaning you have to push the high word first. In this 7900-formatted disk the third volume is located at 2250 low, 0 high so to load it into memory and dump it use something like...

? 0 2250 SBLA 66000 R-1K
? 66000 66060 DUMP

66000:054104 047523 020040 020040 020040 020040 020040 020040 XDOS
66010:000000 000000 000000 000002 000002 000000 174000 000000 ................
66020:046501 055105 027111 050114 020040 020040 020040 020040 MAZE.IPL
66030:000000 000000 000000 000000 000000 000000 041521 000000 ............CQ..
66040:041501 050123 046117 041513 027111 050114 020040 020040 CAPSLOCK.IPL
66050:000000 000000 000000 000000 000000 000000 003526 000000 .............V..

66000 is where HP-IPL/OS' last user memory block usually located, if you specify the wrong location  bad things happen so make sure you're in OCTAL and don't give commands like this quickly. In XDOS the WKBUF constant pushes 66000 so it's safer to 0 2250 SBLA WKBUF R-1K then dump using WKBUF DUP 60 ADD DUMP. Change the 60 to how many words to dump. Now let's make the CW number in MAZE.IPL equal to 1 instead of 0...

? 66037 1 PUT 0 2250 SBLA 66000 W-1K
? XDIR
Current volume: MYSTUFF NE/DBL/DBH: 000014 002250 000000
Filename LA RA LAP BSIZ CW BLKL BLKH Slot
XDOS 000002 000002 000000 174000 000000 001447 000000 000000
MAZE.IPL 000000 000000 000000 041521 000001 001507 000000 000001
CAPSLOCK.IPL 000000 000000 000000 003526 000000 001547 000000 000002
1DCA.IPL 000000 000000 000000 013727 000000 001607 000000 000003
XDOSCAPS 000002 000002 000000 174000 000000 001647 000000 000004
HPBASIC 000002 000002 000000 174000 000000 001707 000000 000005
XDOSGAMES 000002 000002 000000 174000 000000 001747 000000 000006

This is scary stuff, when making this example I accidently typed 66027 in the PUT and corrupted the filename, requiring (with dir block in mem at 66000) 66027 20040 PUT 0 2250 SBLA 66000 W-1K to fix. 20040 of course is double spaces.  BTW currently I don't know what the CW field is for, that's why I chose that one to change as an example but changed back to 0, no telling what CW will come to mean. Caution... always SBLA before W-1K or R-1K, remember that they increment the internal block pointer so consecutive calls read/write consecutive blocks (handy when writing load/save routines).

In a program it is desireable to <IDE after disk operations to push the error status, if at any time it is not 0 then the program should abort with an error message or other indication of failure.

The SBLA R-1K W-1K and <IDE are all that's needed to do most low-level operations except for loading a system file, which requires code that jumps high to avoid being overwritten. LDFB (calls code that) does this and loads a system binary from the current block. In the above example I can "boot" the XDOS binary by (using numbers returned by XDIR) entering 0 1447 SBLA LDFB (-IRQ first if loading something that expects interrupts to be off, HP-IPL/OS doesn't care).


Disk Driver Programming

The disk driver provides device-specific code for the low level disk words to call. The driver word must write subroutine entry addresses to locations 240 through 245 in zero page to connect code to the low level disk words >IDE <IDE W-1K R-1K SBLA and LDFB. Subroutines within the driver use locations 246 through 250 to receive block address low, high and the drive number. If desired the >IDE vector can point to a dummy subroutine  since the commands are really only useful with Bob's IDE interface although the 7900 driver supports a limited subset of the IDE interface command set. Using >IDE for general disk programming is not recommended at this point, whatever it does will likely be specific to a particular drive so if the driver programmer needs specific functions not covered by the low-level words this would probably be a good place to hang them from.

Low Level Device Independent Disk Words

The disk.ipl file provides the following HP-IPL/OS words...

command >IDE     Send low level disk command using Bob's IDE interface language (1)
<IDE Push disk interface status (0=ok) (2)
highblock lowblock SBLA Set current block address (1K word increments)
Address W-1K Write 1K words from Address to current block (3)
Address R-1K Read 1K words from current block to Address (3)
LDFB Load 31K binary from current block and run from 2 (4)
DBOOT Load 31K binary from block 8 decimal (default boot image)
DGEN Save current system to block 8
drive# CHDRV Change drive number (5)

(1) How much to support on non-IDE drives is optional, the 7900 driver emulates the following IDE interface commands...

; Interface commands...
; 101XXX - SB1 - Sets 8 lsb's of 1K word block address
; 102XXX - SB2 - Sets next 8 msb's of 1K word block address
; 103XXX - SB3 - Sets next 8 msb's of 1K word block address
; 104XXX - SB4 - Sets two 2 bits only of 1K work block address
; 110XXX - RDB - Reads XXX blocks from disk. If XXX=0 one 1KW block
; is read into the disk data buffer from the current block address.
; If XXX>0 then XXX blocks (up to a maximum of 31) is read from disk
; and then transfered to the HP.
; 111XXX - WRB - Writes XXX blocks from HP to disk. If XXX=0 the
; contents of the disk data buffer are written to the disk at the
; current block address. If XXX>0 then XXX blocks (up to a maximum
; of 31) is transfered from the host and written to disk.

(2) Currently <IDE is the only way to obtain disk status for all kinds of drives, the driver can return raw status bits or generalize as desired so long as 0 means OK. In the future we may devise a more general <DSTATUS word to be included with drivers that translates the raw <IDE status bits into standard meanings. Ideas? For now if <IDE pushes 0 then the operation was (probably) successful. Having "IDE" in the name of a device-independent word is a little confusing but that's the way it started out and the name stuck. Subject to change.

(3) Drivers should keep their own internal block address and increment it after each read or write so that subsequent operations do not require resetting the block address. Can be separate variables or the same variable, any app depending on this behavior should not count on separate read/write counters. Disk-based sysgens DO perform sequential W-1K operations so driver MUST increment its internal block counter.. to be totally IDE compatible don't increment the zp BLKAL location, just the driver's internal block pointer (with IDE it's really on a 8051).

(4) Binary images are offset by 2 (the first word of a file is for location 2 in memory), and consist of 31 consecutive blocks containing addresses 2-76001 octal.

(5) CHDRV merely changes location 250 which tells the driver what drive is selected, how the driver interprets this information is up to the driver. The psuedo-variable DRV is tied to this location for convienience. Large drives can be "partitioned" into multiple "drives" by multiplying the locations set by SBLA (246/247) by the drive number.

The device-independent words work by calling subroutine vectors...

CMDVE  EQU 240                 command, >IDE code
STAVE EQU 241 status, <IDE code
W1KVE EQU 242 W-1K code
R1KVE EQU 243 R-1K code
SEEKV EQU 244 seek vector, sets seek adr to 246,247
BOOTV EQU 245 boot vector, seek or manually set first

...that connect to the actual driver code, passing variables...

BLKAL  EQU 246                 block address low
BLKAH EQU 247 block address high
DRVNM EQU 250 drive number

For example, look at the code for SBLA...

; Set Block Address
; high low SBLA as in 0 10 SBLA LDFB
"Loading SBLA" $PRINT CRLF
CREATE SBLA /KEEP
JSB ZSPOP,I
STA BLKAL
JSB ZSPOP,I
STA BLKAH
JSB SEEKV,I
END

This code pops the stack to zp location BLKAL (246) and BLKAH (247) then calls the SEEKV vector (244) as a subroutine. High level disk software can seek to a specific 1K block by pushing the high and low block address then using SBLA, independent of the drive and how many words are really in each sector. R-1K, W-1K and LDFB work the same way. Machine-coded disk software can simply poke the zp variables and call... to seek to block 65538 decimal put 2 in BLKAL, 1 in BLKAH and JSB SEEKV,I.

The disk driver must write the driver subroutine addresses to locations 240-245 octal when it initializes, typically as a startup word. If there's anything else needed to make the drive "go" it should be done here. If slot patching via software is desired then all locations containing IO instructions or constants need to be patched. Alternatively the driver can be written to run in one slot only, edit and reload to change the slot number.

A Discussion of the IDE Driver IPL Code

The formatted text is code from ide.ipl, generally the text describes code appearing above it. I chose the IDE driver to discuss for simplicity, the IDE command set is so simple it's almost a shell that could be used to create other drivers. Browse through 7900.ipl for a more complex example.

; !IDE   Initialize IDE interface  1/10/04 5/4/04 9/1/04
;
"Setting default IDE slot to 23" $PRINT CRLF
OCTAL 274 23 PUT

Since this is self-patching code, the desired slot address needs to be written to a zp location reserved for specifying the ide slot. See config.ipl for the locations that are currently defined. Note that MT and GPIB may never become self-patching and those locations can probably be recycled for other things. An almost-requirement for self-patching code is they have an autostart word that contains all the code that needs patching, not the case with the magtape and GPIB support packages. On the other hand disk drivers tend to be contained in a single CREATE word and fairly easy to make self-patching.

;extras...
;note! this driver IPL must be loaded AFTER disk.ipl
;easier to swap drivers this way anyway
"Loading ?DRV" $PRINT CRLF
DEFINE ?DRV
"IDE virtual drive " $PRINT DRV GET PNUM
"offset: " $PRINT
#0 #0 ;push total
DRV GET IFNZ ;if not zero
#1 DRV GET +DO 2 146 DADD +LOOP ;add 2 146 dn times
ENDIF
SWAP PNUM PNUM ;print high low offset
END
;---------

This is not a driver but an IDE-driver-specific word that prints the contents of DRV and the equivalent block address offset. Because DRV is defined in disk.ipl the ide.ipl file has to be loaded after disk.ipl is loaded. If the driver.ipl doesn't refer to anything in disk.ipl (besides the zp equates which are copied) then there's no order requirement. In practice it works better to load disk.ipl before the driver to allow drive-specific words included with the driver to make use of disk.ipl words and the DRV variable.

"Loading !IDE" $PRINT CRLF     ;note autostarting for default
;caution... rename !IDE to IDE if IDE drive not present or it hangs
CREATE !IDE
JMP IDEEN

This begins the real driver. The driver word is named !IDE to make it run whenever hpiplos starts. The driver no longer hangs because the init was taken out but if the drive requires resetting or other initialization then it might/will hang if the drive isn't connected. To work around this put 177777 in the switch register to prevent hpiplos from running autostart words, then rename !DRIVER to DRIVER to keep it from running. This is also how multiple drive types can be supported on one system, make one the default, say IDE then rename the !7900 driver to 7900, execute 7900 to use the 7900 drive, reboot or !IDE to use the IDE drive. Note that the drive-specific ?DRV words collide in this example, we didn't design the system to have multiple disk drivers loaded but should the need arise it's possible, all in the name.

All autostart words run at startup so as long as a driver doesn't hang because of init of unconnected hardware, specific drive support can be enabled by just loading the driver ipl and rebooting or running the !driver word.

The first instruction JMP IDEEN jumps around the internal variables...

* slot assignment... (when assembled anyway)
IDE EQU 23 configured for IDE interface in slot 23
* zero page vectors....
CMDVE EQU 240 command, >IDE code
STAVE EQU 241 status, <IDE code
W1KVE EQU 242 W-1K code
R1KVE EQU 243 R-1K code
SEEKV EQU 244 seek vector, sets seek adr to 246,247
BOOTV EQU 245 boot vector, seek or manually set first
BLKAL EQU 246 block address low
BLKAH EQU 247 block address high
DRVNM EQU 250 drive number
* virtual disk size
* possibly temporary implementation pending firmware changes
* set to 131174 blocks per drive
* Do Not SBLA to this block or higher or you'll be on the next drive.
VDSHI OCT 2 putting up front for easy changing
VDSLO OCT 146
* patch slot number...
ISLOA EQU 274 location containing slot number

The IDE interface is EQU defined to slot 23, because this code is self-patching it doesn't really matter but for non-patching drivers this would be the slot number. Next is the zp equates copied from disk.ipl that define the low-level interface. Next is a pair of variables used to offset the real block address for simulating multiple drives. For some reason this feature is not working... finally is an EQU defining the zp location chosen to hold the self-patching slot number.

IDEEN  LDA IDEPL               get patch list address
LDB ISLOA get slot address
JSB PSUBA,I call patch sub

PSUBA is a standard CREATE built-in EQU that holds the address of HP-IPL/OS' patch subroutine. The A register is loaded with the address of a patch list consisting of a list of addresses to modify ending with 0, B is loaded with the desired slot number, then JSB PSUBA,I to patch the code.

* set vectors to IDE subs...
LDA ACMD get address of command code
STA CMDVE save in command vector
LDA ASTA status
STA STAVE
LDA AW1K write 1K
STA W1KVE
LDA AR1K read 1K
STA R1KVE
LDA ASEEK seek
STA SEEKV
LDA ABOOT boot
STA BOOTV
* not resetting to avoid problems if drive not present
* LDA RESDR get reset command
* JSB CMD send it
JMP END back to hpiplos with vectors set

This code gets the address of each driver subroutine and pokes into the zero page vectors reserved for disk operations. The reset code has been commented out to prevent hanging if an IDE interface is not installed. After setting the code vectors JMP END returns to HP-IPL/OS with the driver "connected".

* patch list...
IDEPL DEF *+1
DEF IPL1
DEF IPL2
DEF IPL3
DEF IPL4
DEF IPL5
DEF IPL6
DEF IPL7
DEF IPL8
DEF IPL9
DEF IPL10
DEF IPL11
DEF IPL12
DEF IPL13
DEF IPL14
DEF IPL15
DEF IPL16
OCT 0

This is a DEF list of all the locations that will be slot-patched.

* Send IDE Command from A...
ACMD DEF *+1
CMD NOP these are subroutines
IPL1 OTA IDE
IPL2 STC IDE,C
IPL3 SFS IDE
JMP *-1
JMP CMD,I
* Get Status into A...
ASTA DEF *+1
STA NOP
IPL4 LIA IDE
JMP STA,I

This is the code connected to the command and status vectors, ACMD holds the address of the CMD subroutine and is put in CMDVE (location 240) so anything doing JSB CMDVE,I will run the CMD subroutine. Labels are placed at each IO instruction to use in the patch list.

* Write 1 word from A...
W1W NOP
IPL5 OTA IDE
IPL6 STC IDE,C
IPL7 SFS IDE
JMP *-1
JMP W1W,I
* Read 1 word into A...
R1W NOP
IPL8 STC IDE,C
IPL9 SFS IDE
JMP *-1
IPL10 LIA IDE
JMP R1W,I

These are IDE-specific subroutines used by W1K and R1K below.

* Write 1K words from address in A...
AW1K DEF *+1
W1K NOP
STA ADDR save address
LDA 1KLEN
STA WCNT init counter
LDA W1BLK get command to write 1 block
JSB CMDVE,I send IDE command
W1KL LDA ADDR,I get one word
JSB W1W write one word
ISZ ADDR bump address
ISZ WCNT bump counter
JMP W1KL loop until counter = 0
JMP W1K,I exit sub
* new constants...
W1BLK OCT 111001 ide command to write one block
R1BLK OCT 110001 ide command to read one block
RESDR OCT 100000 ide command to reset drive
*
ADDR OCT 0 Address
1KLEN OCT -2000 Complement of length
WCNT OCT 0 counter
* Read 1K words into address in A...
AR1K DEF *+1
R1K NOP
STA ADDR save address
LDA 1KLEN
STA WCNT init counter
LDA R1BLK get command to read 1 block
JSB CMDVE,I call IDE command
R1KL JSB R1W read one word
STA ADDR,I save it
ISZ ADDR inc addr
ISZ WCNT inc counter
JMP R1KL loop until done
JMP R1K,I

The above code implements the R-1K and W-1K functions for the IDE interface. These are calling the CMDVE vector to perform their functions but that's IDE-specific, the 7900 driver has read/write code here.

* seek
* this avoids complicated 101xxx >IDE etc commands, instead set
* BLKAL(246) to block low, BLKAH(247) to block high, and call SEEKV
ASEEK DEF *+1
SEEKC NOP
* implementation of virtual drives
* if drive 0 uses raw blkal/blkah
* otherwise d-adds vdshi/vdslo drvnm times
LDA BLKAL
STA TBALO
LDA BLKAH
STA TBAHI
LDA DRVNM
SZA,RSS
JMP SEEK1 no change if drive 0
CMA,INA
STA 1 save -drive in B for counter
DNMLP CLE
CLO
LDA TBALO
ADA VDSLO
STA TBALO 32-bit add
LDA TBAHI
SEZ
INA
ADA VDSHI
STA TBAHI
ISZ 1 done?
JMP DNMLP no, keep looping
SEEK1 LDA TBALO
AND C377
IOR SB1
JSB CMDVE,I
LDA TBALO
AND C1774
ALF,ALF
IOR SB2
JSB CMDVE,I
LDA TBAHI
AND C377
IOR SB3
JSB CMDVE,I
LDA TBAHI
AND C1774
ALF,ALF
IOR SB4
JSB CMDVE,I
JMP SEEKC,I
C377 OCT 000377
C1774 OCT 177400
SB1 OCT 101000
SB2 OCT 102000
SB3 OCT 103000
SB4 OCT 104000
TBALO OCT 0
TBAHI OCT 0

Above is the IDE seek code with support for virtual drives.

* boot
* can be called as a sub, but never returns
* note! seek must already be set!
* good idea to -IRQ etc before calling
ABOOT DEF *+1
BOOT NOP
LDA BOOTC
STA FROM
LDA DEST
STA TO
BOOTL LDA FROM,I
STA TO,I
ISZ TO
LDA FROM
CPA BOOTE
JMP DEST,I
INA
STA FROM
JMP BOOTL

This code copies the machine code below to 77700 and executes it, the machine code loads 31 (decimal) blocks from the current block address then starts it running from location 2. Depending on the drive the code can be adapted from an existing bootrom, but more likely needs to be written from scratch, see 7900ldr.hpa for the code used in the 7900 drivers.

BOOTC  DEF *+1
* IDE BOOTSTRAP LOADER:
* modified 5/2/04 for LDFB
* ORG 77700B
OCT 063732 LDA TCNT
OCT 073727 STA LCNT
OCT 063730 LDA RDBW
OCT 017713 JSB TIDE
OCT 067731 LDB ADDR
OCT 017721 LOOP JSB RIDE
OCT 170001 STA 1,I
OCT 006004 INB
OCT 037727 ISZ LCNT
OCT 027705 JMP LOOP
OCT 127731 JMP ADDR,I
OCT 000000 TIDE NOP
IPL11 OCT 102623 OTA 23B
IPL12 OCT 103723 STC 23B,C
IPL13 OCT 102323 SFS 23B
OCT 027716 JMP *-1
OCT 127713 JMP TIDE,I
OCT 000000 RIDE NOP
IPL14 OCT 103723 STC 23B,C
IPL15 OCT 102323 SFS 23B
OCT 027723 JMP *-1
IPL16 OCT 102523 LIA 23B
OCT 127721 JMP RIDE,I
OCT 000000 LCNT OCT 0
OCT 110037 RDBW OCT 110037
OCT 000002 ADDR OCT 000002
OCT 102000 TCNT OCT 102000
* last addr 77732
BOOTE DEF *-1
DEST OCT 77700
FROM OCT 0
TO OCT 0

This is really "system file loading" rather than booting. A true boot for IDE would load from block 8 decimal, a true boot for 7900 would load the bootext in blocks 0-5 and start it running to load in the rest of the system. System-specific booters etc can be included in the driver ipl file if desired but unless a foreign system is being booted it's faster to -IRQ 0 10 SBLA LDFB (or just DBOOT) rather than go through the whole boot process. Even though this isn't true booting it loads and runs a system binary as if it were booted, so "boot" got used in the names.

Creating the LDFB/BOOTC code is one of the harder aspects about writing a disk driver. The code has to be stand-alone and assembled to run in high memory, then encoded as octal so it can be included in a normal CREATE word that relocates the code to its original assembled location. The LDFB word (that runs the BOOTC code) is supposed to load the system binary from the current block set by SBLA. For drives with separate seek operations like the IDE interface, it was a matter of simply removing the seek commands from existing boot code. For many drives however (like the 7900) the SBLA/seek code doesn't send anything to the drive but rather sets internal variables so that the next operation can perform the seek. For example, the 7900 driver's SBLA/seek code sets an internal var BLADR to the contents of BLKAL which was set by SBLA. The 7900 LDFB/boot code copies the internal BLADR var to BLKAL in zp (in case it changed, just to be strictly compatible with how the IDE interface operates... you'd probably be just as good to use BLKAL/BLKAH directly), then when the hi-mem code in the octals runs it seeks to the block in BLKAL first before loading and running the system binary. The 7900 driver ignores BLKAH since it's a small drive, drives over 128 megabytes need to use BLKAL/BLKAH and use 32-bit math for computations. Then there's the drive number to support, it's in location 250 (argh.. just noticed a bug in the comments of 7900.ipl - I called BLKAL loc 250 but it's 246 - not fixed). For the 7900 I use DRVNM 0-3 to reference the removeable platters of drive numbers 0-3, and DRVNUM 4-7 to refer to the fixed platters.

Nothing in HP-IPL/OS can assemble code directly to high memory (at least not without  serious hacking to subvert the system), practically the LDFB/BOOTC hi-mem code needs to be written and assembled using HPASM. The code can be tested by doing a DGEN to make sure there's a bootable image present in blocks 10-46 (octal), do 0 10 SBLA (or poke BLKAL location 246 octal with 10 octal if your code reads loc 246 for seek) then run the hi-mem code directly with -IRQ 77700 RUN (or wherever your code is assembled to run from, keep it somewhere above 77500 or so to avoid conflicting with other things like the exit code typically placed at 77000). Once the code works and can load/run a system binary then it can be converted to octal and included in the driver along with relocation code and anything needed to tell it where to load from. The octal can come straight from the HPASM listing, or created with the mkcldr.ipl utility which encodes any area of memory to octal along with code that copies it back to its original location.

END
;--- end of IDE core driver code
"Done" $PRINT CONSOLE

That's it. END stops CREATE, CONSOLE restores the prompt.

A quick way to test a new disk driver is do DGEN to write a system binary, add/subtract stuff to detect the change, then DLOAD to boot the system saved by DGEN, should return to the DGEN'd system. If  the driver supports multiple drives you should be able to n CHDRV to DGEN to drive n, n CHDRV DBOOT should load different systems saved to different drives.


Created 5/1/05, Last Modified 8/30/08