[note... this was written in 2005 and documents the process of writing a disk driver and the original creation of XDOS. Some of the listed code had bugs so if using for more than reading refer to the actual IPL files for the code.] HP-IPL/OS Disk Programming -------------------------- Referenced files... disk.ipl - low level device-independent words 7900.ipl - driver for 7900 drives ide.ipl - driver for IDE drives using Bob's IDE interface tdos.ipl - example mini-dos using file numbers (not filenames) config.ipl - contains the CONFIG word, customize to support new slot-patching drivers mkldr.ipl - ascii-encodes an area of memory with a decoder (not patchable) mkcldr.ipl - CREATE-encodes memory, binary encoded as OCT statements (slot-patchable) 7900ldr.hpa - HPASM code for the 7900 drive that loads a system binary xdos.ipl - SFS-compatible "example" DOS xutils.ipl - XDOS formating/import/export/recover utilities The idea is to generalize all disk operations to simple commands that disk software can use without regard to what type of drive is connected. All transfers and seeks are for 1K word blocks, regardless of the actual sector size. There is no provision in this interface for transfers of any other size, with the exception of the system binary load code which is self contained and can do it any way it wants (that doesn't depend on HP-IPL/OS because it's being overwritten). The low-level disk words are contained in the disk.ipl file and are basically shells that interface with the real disk code in the disk driver. The disk driver ipl defines an autostart word that must poke addesses into zero page locations to provide functionality for the words in disk.ipl, which can be used by disk applications to (theoretically) allow drive-independent operation. Device-specific functions such as reboot can be included in the driver ipl file. HP-IPL/OS disk software can use simple commands to seek to a particular block (SBLA - Set Block Address), read 1K words into memory (address R-1K), write 1K words from memory (address W-1K), get status (IDE Send low level disk command using Bob's IDE interface language (1) 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 code STAVE EQU 241 status, IDE code STAVE EQU 241 status, 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 The IDE seek code with support for virtual drives. Possibly with a bug unless it turns out to be firmware. * 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 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). 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 and N CHDRV DBOOT to load different versions. Summary of the low level disk words... H L SBLA set block address to high/low block address R-1K read 1KW into address from current block address W-1K write 1KW from address to current block LDFB load/run system binary from current block Z ;put #entries in last dir on Z to make programming easier WKBUF DUP 1777 ADD +DO INDEX #0 PUT +LOOP ;clear work buffer DEC ;decrement #vols to get the number of last entry DUP #0 SWAP +DO ;loop thru all volume index entries WKBUF INDEX 20 MUL ADD ;push entry start address #0 7 +DO DUP INDEX ADD 20040 PUT +LOOP ;make name = spaces 13 ADD ;point to dir size entry OVER INDEX SUB IFZ ;if this is the last volume then Z>S ;get size of last dir from Z, removing from Z ELSE 100 ;otherwise full size ENDIF OVER SWAP PUT ;write dir size, leaving mem ptr on stack INC ;point to dir block ptr low INDEX IFZ ;if this is the first entry DUP 7 PUT ;dir is in block 7 INC INC ;leave dir block high zero 47 PUT ;file space begins in block 47 ELSE S>Z ;temporarily stash mem ptr on Z ;dir block = volume number * 4001 + 46 ;file space begins in next block INDEX 4001 EMUL #0 46 DADD DUP ;dup the low word of the result Z>S DUP S>Z ;get mem ptr, leave on Z SWAP PUT ;write dir block low OVER ;push the high word of the result Z>S INC DUP S>Z ;get mem ptr, inc, return to Z SWAP PUT ;write dir block high #0 1 DADD ;increment result to get start of filespace Z>S INC DUP S>Z ;get mem ptr, inc, return to Z SWAP PUT ;write file space low Z>S INC PUT ;write file space high ENDIF +LOOP DROP ;last vol number "Writing new volume index... " $PRINT #0 6 SBLA ;seek to block 6 WKBUF W-1K ;write work buffer Z ;put last valid address onto Z stack 20 40 $CREATE $CAT #0 17 $SLICE $SWAP $DROP ;space-pad string $ADR ;push address of string SWAP ;swap with mem ptr DO ;until match is found or no more entries to look at DUP ;pointer for test Z>S DUP S>Z SWAP SUB IF<0 ;if past the end DROP ;mem ptr DROP ;address of string $DROP ;name string #0 ;return 0 for failure #1 ;terminate loop ELSE ;see if first two chars match.. (str adr, mem ptr on stack) OVER GET ;push 1st word of string OVER GET ;push 1st word of mem entry SUB IFNZ ;if they don't match 20 ADD ;next entry #0 ;keep looking ELSE ;check the remaining 7 words.. #1 S>Y ;flag on Y, any mismatch clears #1 7 +DO ;loop 7 times OVER INDEX ADD GET ;push word from string OVER INDEX ADD GET ;push word from mem SUB IFNZ Y>S DROP #0 S>Y ENDIF ;clear flag if not equal +LOOP Y>S ;get flag IFZ ;if no match.. 20 ADD #0 ;next entry ELSE ;found it! SWAP DROP ;string address $DROP ;string #1 ;stop looping ;leaving entry address on stack ENDIF ENDIF ENDIF UNTIL Z>S DROP ;clean up Z stack END ;--------------------------------------- ..egads again. This one took a few tries, found bugs in MKVOL (not space-pading right) and the search code (DUP when I need OVER) but seems to be working ok now... ? ?CV MyVol ? 66000 "MyVol" FindEntry PNUM 066000 ? 66000 "Blah" FindEntry PNUM 000000 A few more volume-oriented words... ;--------------------------------------- "Loading DELVOL" $PRINT CRLF ; "name" DELVOL - removes volume entry DEFINE DELVOL "Delete volume " $PRINT $DUP $PRINT "? (Y/N) " $PRINT CHRIN "Y" $HEAD $DROP SUB CRLF IFNZ $DROP ELSE #0 6 SBLA WKBUF R-1K ;read volume index File functionality is to allow importing regular ipl files to disk so that I can demonstrate how to do that little bit of magic. With such things in place from the console or an IPL file I can do... ZAM 2 MSUSER >MS "defname" $DEFADR PDEF "CONSOLE" MS$OUT MSCRLF <>CON "filename" AM2F ...to put definition BLAH into a file. ZAM is a simple function that clears alt memory, AM2F will be XDOS' Altmem-to-file function which is similar to the TDOS word with the same name except it takes a filename string instead of a file number. Of course if I make the system so it can store arbitrary text then I'll probably want some kind of "show" command to list the text to the screen. Update.. now AM2F takes byte size so now it's more like... ZAM 2 MSUSER >MS "defname" $DEFADR PDEF "CONSOLE" MS$OUT MSCRLF " " MS$OUT <>CON UPTR GET DEC DEC 2 MUL "filename" AM2F Even a simple dos can pile up the code! I must resist getting too fancy but some level of fanciness is needed to make a system that's actually useable. This time around I'm trying to stay as close to SFS as I can (minus the file access stuff) in the hopes of being able to reuse as much of this as possible for SFS. For usage words that aren't part of the SFS spec (the loads/saves/etc) ideally I want to be able to reuse them with or without SFS loaded. If I have to modify them to work with SFS that's ok but syntax-wise I want to get used to a certain way of "using" and stick with it as much as possible. I'm just that way. So what I'd like is.... "filename" XSAVE - save current system to a file "filename" AM2F - save alternate memory to a file "filename" F2AM - load a file into alternate memory "filename" XLOAD - load a system or ipl file "filename" XDEL - delete a file "filename" "newname" XREN - rename a file "filename" XSHOW - show a file on the screen XDIR - show what files are available and stats about them [AM2F now takes bytes on the stack, didn't think I'd need filesize but without it other things are file-size-aware are harder to write] For the utilities ipl (not always loaded) I'd like to have... "volname" XRECOVER - scans the volume and adds generic directory entries for any files it finds, file0, file1, etc, along with "best guess" settings for load/run address. Useful for converting an existing TDOS disk to XDOS/SFS. [ended up being just XRECOVER (no string), CHVOL there first] VRECOVER - scans the volume index table and adds generic volume names for volume entries that appear to point to a directory. Useful when a bug blows the first part of the disk and you'd like to get it back without rebuilding. XINIT then VRECOVER then XRECOVER the first directory, crossing fingers. Here begins the real "usage" part of XDOS, the previous code more or less fills in for what SFS should eventually provide. However the concept of the current volume (XCV) and associated words ?CV and CHVOL should be kept or recoded for SFS since this is a usage thing, I expect to "stay" where I "go". Internally SFS doesn't really have a "current" concept, each buffer is tied to a particular location which is always specified. But something has to specify it. Current-aware usage words can make use of XCV to tell SFS what volume to operate on. Mainly though the current volume is so XLOAD XSAVE XDIR etc know where to operate without having to tell them. This gives the user of XDOS the illusion of moving around the disk rather than the volume name becoming just another part of the filename, not to mention being able to write code that works in another volume, having to hard-code the volume name, or specify it every time, is just plain inconvenient, hence the XCV variable. At first this division of a file-access-system like SFS and usage functions like XDOS might seem strange but it's the same as in MSDOS and about any operating system, the maker supplies the raw convulted dos with its hard to use calls, then supplies a default command interpreter which can be replaced by the user or not used at all. The user sees what the command interpreter provides, which translates nice simple commands into whatever archaic syntax is demanded by the actual guts of the dos. XDOS isn't a command interpreter but provides the kind of things usually provided by one. OS providers are usually too close to the system to see how people really use disk files, they're too busy streaming buffers and allocating bits, and making sure every itty bitty aspect of the system can be accessed. And often write terrible docs :) All the OS provider can do is provide a default environment based on how they think it should be used, and make it replaceable in case someone else needs something different. Thus we have divisions like low level disk words, drive-specific code for those words, TDOS to get by with, XDOS to replace it with something better, and working on SFS to provide file access methods which cannot be provided by usage words alone. One size does not fit all, but the variations can still be more or less compatible with one another if basic standards are followed. This is practical as well, I can load more games into a build that has less dos. XDOS doesn't really qualify as a file-system, more like a file launcher. It provides no way to process files unless you care to load them into alt and process it there, no file buffers, no streaming, but that's perfectly fine, even preferable when I'm not running file-processing programs. On the other hand if you need a program that has to open an input file and an output file at the same time (say for a real assembler), or need byte access to files that have real file lengths, then you need a real file system like SFS. The key word is need. Ok enough OS rambling, get to work... The following code was last modified 4/26/05 after initial testing. The initial words changed slightly, DELVOL and XI79 now ask for a confirmation (updated in text above), ?CV was removed because XDIR prints the current volume, but essentially the volume stuff seems to be working ok. Writing the file stuff turned into a marathon programming session, with a few puzzling bugs along the way. Stupid stuff like OVER INDEX then forgetting to ADD but not forgetting PUT, wildly overwriting memory.. unlike some operating systems HP-IPL/OS makes no attempt to protect against this and if it did that would prevent all kinds of cool stuff from working. One programmer's bug is another's feature.. so embrace it and remember to dump and reload after bugs happen. For the most part I kept with the simple XSAVE/XLOAD/XDIR/XDEL/XREN specs I outlined, they work but should be considered works in progress. Presently not much honors the bytesize in the dir entry, it knows only 62KB systems and 64KB-4 files. The example (ZAM...) to save a definition to a disk file works, probably needs to be a word as simple as it is... something like "DefName" XDSAVE and it appends ".IPL" for the filename. But I really need to start thinking about actually using bytesize... ? XDIR Current volume: MYSTUFF NE/DBL/DBH: 000014 002250 000000 Filename LA RA LAP BSIZ CW BLKH BLKL Slot XDOSWORK 000002 000002 000000 174000 000000 001447 000000 000000 XDOS 000002 000002 000000 174000 000000 001507 000000 000001 XDOSGAMES 000002 000002 000000 174000 000000 001547 000000 000002 TEST.IPL 000000 000000 000000 177773 000000 001607 000000 000003 TEST is just a few dozen words long but F2AM has to load all 32 blocks. Inefficient to say the least :) It would be better if the save process (whatever that might be) detect and record the actual size, then AM2F and F2AM just have to save/load the part of the file that matters. If MSUSER is being used to build the file in alt then UPTR could be used to determine filesize, a more generic way would be scan alt for when all-zeros start but that's more code and less accurate. Better to modify AM2F and F2AM so that they take bytesize on the stack and set the appropriate DirInfo array location before making the file. Maybe. Here's the rest of the code... (updated 4/28/05, version 1.1) ;--------------------------------------- "Variables VolInfo and DirInfo" $PRINT CRLF VARIABLE VolInfo 5 VARIABLE DirInfo 6 ; VolInfo contains dirsize, dir ptr low/high, file space low/high ; DirInfo contains LA RA LAP bytelength controlword entry# ;--------------------------------------- "Loading GetVol" $PRINT CRLF ;reads XCV variable and sets VolInfo array ;if error occurs first word of VolInfo is 0 DEFINE GetVol #0 6 SBLA WKBUF R-1K ;read volume index ACOPY ;copy 2000 octal words to alt +LOOP END ;--------------------------------------- "Loading AM2F" $PRINT CRLF ;"filename" bytes AM2F - save alt mem to file DEFINE AM2F "Saving alt " $PRINT ;determine type by context, if loc 2 contains 124003 ;then assume alt contains a system binary, otherwise ;set load/run addresses to zero, file length on stack 2 DirInfo 1 A>CCOPY ;get location 2 from alt mem DirInfo GET 124003 SUB IFZ 2 ELSE #0 ENDIF DirInfo OVER PUT ;load address = 2 if bin, or 0 DirInfo INC SWAP PUT ;run address = 2 if bin, or 0 DirInfo INC INC #0 PUT ;last access = 0 DirInfo 3 ADD OVER PUT ;length = specified DirInfo 4 ADD #0 PUT ;control = 0 PutFile ;attempt to make new dir entry IFZ "error" $PRINT DROP ELSE ;bytelen, high/low block start of file on stack "to " $PRINT OVER PNUM DUP PNUM SBLA ;set block address to start of file ;bytelen on stack INC ;in case odd 4000 DIV ;turn into #blocks 2 ;mem ptr OVER #0 SWAP +DO ;loop for up to 32 dec 1KW blocks DUP ;push alt mem loc source WKBUF ;push destination 2000 ;size of first 31 blocks INDEX 37 SUB IFZ DROP 1777 ENDIF ;size of last block A>CCOPY ;copy 1K to work buffer WKBUF W-1K ;write to disk 2000 ADD ;increment mem ptr to next block "*" $PRINT +LOOP DROP ;mem ptr DROP ;block length ACOPY ;copy 1K to alt mem 2000 ADD ;inc ptr to next block "*" $PRINT +LOOP DROP ;alt mem ptr DROP ;#blocks =end UNTIL MS_RESTORE DROP ELSE DirInfo GET 177777 SUB IFNZ ;if not because of error ;dump alt mem as hex... #0 ;alt mem ptr DO ;until quit DUP WKBUF 200 A>CCOPY ;copy 128 alt words to work buf #0 17 +DO ;do 16 dec lines at a time DUP $STR $HEAD DROP $PRINT ":" $PRINT #0 7 +DO ;do 8 words per line DUP INDEX ADD GET PNUM +LOOP 10 ADD ;inc ptr CRLF +LOOP "----- B F Q or any for next ----- " $PRINT CHRIN CASE = 102 DROP #0 #0 ;move to begining = 106 2000 ADD #0 ;move forward = 121 #1 ;quit DEFAULT #0 42 +DO 4040 PWRD 10 PCHR +LOOP ;erase prompt #0 ;keep going ENDCASE UNTIL DROP ;alt mem pointer ENDIF ENDIF END ;--------------------------------------- "Loading XDIR" $PRINT CRLF ;XDIR - lists files in current directory block DEFINE XDIR GetVol ;read volume index and set VolInfo to current dir "Current volume" $PRINT VolInfo GET IFZ ;if something went wrong " invalid" $PRINT ELSE XCV GET 20 MUL WKBUF ADD ;point to current volume name ": " $PRINT #0 7 +DO DUP INDEX ADD GET PWRD +LOOP DROP ;print CV name " NE/DBL/DBH: " $PRINT #0 2 +DO VolInfo INDEX ADD GET PNUM +LOOP ;print numbers VolInfo INC INC GET ;push dirblock high VolInfo INC GET ;push dirblock low SBLA WKBUF R-1K ;read dirblock Y ;push printed-legend flag #0 VolInfo GET DEC +DO ;loop thru all dir entries DUP GET IFNZ ;if not zero DUP GET 20040 SUB IFNZ ;if not spaces Y>S DUP S>Y IFZ ;if not printed yet CRLF ;print legend... "Filename " $PRINT "LA RA LAP BSIZ CW BLKH BLKL Slot" $PRINT Y>S DROP #1 S>Y ;flip flag to print only once ENDIF CRLF #0 7 +DO DUP INDEX ADD GET PWRD +LOOP ;print filename " " $PRINT 13 17 +DO DUP INDEX ADD GET PNUM +LOOP ;print numbers #0 INDEX 40 MUL ;push 32 bit file offset VolInfo 4 ADD GET ;push filespace high VolInfo 3 ADD GET ;push filespace low DADD ;make 32 bit starting block of file PNUM PNUM ;print BLKH BLKL INDEX PNUM ;print slot# ENDIF ENDIF 20 ADD ;increment ptr +LOOP DROP ;ptr Y>S IFZ CRLF "No files" $PRINT ENDIF ENDIF ENDIF END ;--------------------------------------- "Loading XREN" $PRINT CRLF ;"filename" "newname" XREN - renames file DEFINE XREN $SWAP $DUP ;swap strings, save filename ;newname oldname oldname on X GetFile ;look up dir/vol stats IFZ ;if error $PRINT " not found" $PRINT $DROP ELSE ;VolInfo/DirInfo valid (as disk is), loc hi/lo on stack DROP DROP ;don't need to know DirInfo 5 ADD GET ;push entry# 20 MUL WKBUF ADD ;turn into address ;sanity check... DUP WKBUF SUB IF<0 #1 ELSE #0 ENDIF DUP WKBUF 1777 ADD SWAP SUB IF<0 DROP #1 ENDIF IFNZ "Bad error! check file " $PRINT $PRINT $DROP DROP ELSE ;newname oldname on X, mem ptr on stack "Renaming " $PRINT $PRINT " to " $PRINT $DUP $PRINT "... " $PRINT 20 40 $CREATE $CAT #0 17 $SLICE $SWAP $DROP ;space-pad string $ADR ;push address of new name string #0 7 +DO ;loop for 8 words OVER INDEX ADD ;push address in dir entry OVER INDEX ADD GET ;push string word PUT ;copy string to mem +LOOP DROP DROP $DROP ;string adr, mem ptr, new name string VolInfo INC INC GET ;push dirblock high VolInfo INC GET ;push dirblock low SBLA WKBUF W-1K ;update dir block F, F>MS etc but that will have to wait for a filesystem like SFS. Redirection can be faked using MSUSER, to "redirect" MS out to a file (so anything that writes to MS out writes to the file) then do something like... MS_SAVE ;save existing MS vectors ZAM ;clear alt mem 2 MSUSER ;redirect MS (in and out) to alt memory run something that writes to MS out (but doesn't read MS) " " MS$OUT ;space-pad in case odd UPTR GET ;push value of alt mem ptr (byte 0/1 would be uptr=2) DEC DEC 2 MUL ;convert into byte size (always even) MS_RESTORE ;restore MS vectors "filename" AM2F ;write to a file UPTR doesn't increment until the 2nd byte is received, so for an even-length file the space-pad isn't accounted for in the length but is still written to the file if within the same block. Generalizing into general-purpose open and close commands is difficult, possible but there isn't any housekeeping for keeping track of things, like preventing open when close for the current file hasn't been performed yet. Until there is a real file-access system in place is better to hard-code the function that's needed and not worry if it doesn't fit the traditional model of open something, do stuff to it then close it. Instead... "filename" F2AM ;"open" the file ;DirInfo+3 contains byte size - note case-sensitive MS_SAVE 2 MSUSER ;redirect MS to alt ;MSBIN MSBOUT MSWIN MSWOUT MS$IN MS$OUT ;UPTR GET to access mem pointer (offset by 2 words) ;but no way to tell odd/even ;n MSUSER again to force to a certain address, starting even ;when done "processing" the file... DirInfo 3 ADD GET ;push filesize "filename" XDEL ;have to confirm "filename" AM2F ;write to same file MS_RESTORE ;restore MS vectors Or many variations depending on what you want to do. Copy file... "filename" F2AM DirInfo 3 ADD GET "newfile" AM2F Note this won't preserve attributes of binary files, except for system binaries but at the moment that's the only binary type. I'm still not totally satisfied with how AM2F works, sometimes specifying the filesize is a good thing but at other times that complicates the syntax, forcing the user/programmer to determine it from DirInfo, UPTR or other means... but that might just be the price of file size - someone/something has to specify it. For text there might be another solution... count chars until zero, that's the byte size. Unfortunately that prevents storing arbitrary data that contains zeroes... specifying the filesize on the stack as I have it now is probably the simplest method. Some of these "problems" will go away when SFS is in place, as the programmer will be able to open/read/write/close files without having to use g.p. alt mem as a buffer and SFS will keep track of the numbers. However even with SFS the issue doesn't go away, at some point when importing or creating new files something has to specify size and other attributes. A challenge for the OS designer is to make programmers happy while at the same time not inconveniencing users by making them enter unnecessary data. In the case of importing files from the PC I don't know any other way than specify filesize directly, any attempt to read past the end of paper tape locks up the system. No file system can help there, just the way PTR works. The only solution I can think of doesn't involve a file system but clever use of the watchdog timer, with major side effects if the user is using it for something else. So I vote leave it alone until we know what other options are available. Looking forward... XDOS came out ok for my needs but I don't do much file programming and when I do I don't mind doing it close to the metal. I can see how the limited file-accessing abilities could be a problem for those writing more traditional file-processing programs. Say a merge which requires opening two input files and an output file - the original SFS spec runs out of steam on that one but the new spec should handle it if the DMS memory exists to buffer that many files. If I were actually confronted with such a problem (so far I haven't been) I might choose to solve in a more direct way, GetFile both of the input files saving the block locations, PutFile my output file saving the destination block location, then go to town using only a 1K work buffer and not use alt memory at all. That can be done now. Problem is, putting such power in the hands of users is asking for corrupted disks. The mixed-case names of GetFile PutFile DirInfo etc are that way for a reason although unless I hide them they'll get used anyway by those who dare. The nice thing about testing with SIMH is I can zip up disks and instantly restore them if something goes haywire. Backing up disks is a much more significant problem when using real hardware. Block-level disk programming is OK for system-level functions but unacceptable for user-level programming, this is where the file-access system comes in. How to get from XDOS to SFS... One approach is to specify SFS with all the volume stuff (perhaps borrowing code from XDOS as needed) then eliminating much of XDOS' volume functionality keeping only the X-words that perform common tasks, and those would likely need to be rewritten to conform to SFS methods. Another approach is leave XDOS basically like it is (standalone) then add only SFS file-handling words which can be loaded or unloaded as needed. This makes more sense to me.. instead of making everything have to conform to SFS, make SFS conform to an existing system. SFS can be smaller by making use of existing XDOS subroutines, and wouldn't need to have much volume-maintenance stuff. The big requirement is standalone functions that write vol/dir entries should not be run while files are open since SFS might update the directory with what it thinks is current, negating manual changes or outright confusing SFS. Practically this isn't a problem, on PC's we have to avoid running disk utilities on open files so it's no different under HP-IPL/OS. I don't think much has to change with SFS' file-handling functions, but it wouldn't have to provide utilities for volume editing etc and can concentrate on file access. More to do with how it's programmed more than anything, instead of doing everything from scratch just call XDOS subs or rely on XDOS words to do that stuff. XDOS always updates the disk so no concerns with SFS getting current data, it's always current. The only concern is making sure XDOS doesn't do anything when there's a chance SFS will undo it, which can be handled by instructing user to not do that. If user-written file-processing words are written so that they don't leave files open after returning to HP-IPL/OS then there is no opportunity to XSAVE or anything that updates a directory to cause problems, problem solved. I think. 4/30/05 - finished up more XDOS utilities... ;--------------------------- "Loading F2MS" $PRINT CRLF ;"file" F2MS - copies file to MS output DEFINE F2MS F2AM ;copy file to alt mem CRLF "Writing " $PRINT MS_SAVE 351 DUP GET ;save MS out vect to restore 2 MSUSER ;redirect MS to alt PUT ;restore normal MS out #0 ;push a zero DirInfo 3 ADD GET ;push byte size DUP IFZ "nothing." $PRINT DROP ELSE DUP PNUM "bytes to MS out... " $PRINT DEC ;make byte size - 1 +DO ;loop thru all bytes in file MSBIN MSBOUT ;copy alt to MS out +LOOP "Done" $PRINT ENDIF MS_RESTORE END ;--------------------------- "Loading ABS2F" $PRINT CRLF ;"file" ABS2F - copies ABS binary to file ;if location 2 is 0/unused then prompts for ;run address and exit address ;note... uses location 100 DEFINE ABS2F "Clearing alt.." $PRINT ZAM CRLF ABSLOAD ;load abs file into alt mem 2 100 1 A>CCOPY ;get loc 2 from alt mem 100 GET IFZ ;if loc 2 = 0 CRLF "Run Address : " $PRINT $IN $VAL DUP IFZ DROP ELSE ;if run addr <> 0 100 124003 PUT 100 2 1 C>ACOPY ;add jmp *+1,i 100 SWAP PUT 100 3 1 C>ACOPY ;add run address ENDIF "Add 77 exit for BASIC? (Y/N) " $PRINT CHRIN 131 SUB IFZ ;if Y "es" $PRINT CRLF 100 77000 PUT 100 76 1 C>ACOPY ;put 77000 in alt loc 76 100 124076 PUT 100 77 1 C>ACOPY ;put jmp 76,i in alt loc 77 ENDIF ENDIF 174000 AM2F ;write alt to file, length = 62KB END ;--------------------------- "Loading VRECOVER" $PRINT CRLF ; scans volume index table and adds VOL0 VOL1 etc entries for ; spaced entries if they point to a non-zero directory block. ; Useful after XINIT to recover existing volumes. ; Must have at least 2 blocks allocated ; A better way to program this would probably prompt for ; the names, for now use RENVOL to rename. DEFINE VRECOVER WKBUF @BLK GET SUB IFZ "block error" $PRINT ELSE #0 6 SBLA WKBUF R-1K ;read volume index table FILE CNF and DEL to use as well. XDOS commands that write to the disk should not be used when files are open under SFS but there's no need to do that, please close all files before XSAVING or other stuff like that. Perhaps CLOSE could treat -1 to mean close all. Next project is library files, this would permit storing multiple ipl files in one disk file. Here's the original code I wrote for implementing this for the MS input stream... ;support for multiple-file streams 11/6/04 ;"string" MSSCAN - searches for "string" in MS input stream, ;up until ascii 128 character. If found, pushes a 1 and leaves ;MS positioned to next character after target, otherwise pushes 0. "Loading MSSCAN" $PRINT CRLF OCTAL DEFINE MSSCAN #0 ;success/fail flag #0 ;mode - 0=looking for 1st occ, 1=matching subsequent $DUP ;match string, when empty match is made $HEAD ;start by looking for first char DO ;until done MSBIN ;get one byte from MS input DUP 200 SUB IFZ ;if ascii 128 then DROP ;the ms in char DROP ;the char being matched DROP ;the mode ;leave 0 on stack #1 ;we're done ELSE ;not an ascii 128, try to match... OVER SUB IFNZ ;(drop char) if no match then OVER IFZ ;if mode 0 #0 ;just keep going ELSE ;if partial match gone bad DROP DROP ;match char and mode #0 ;back to mode 0 $DROP $DUP $HEAD ;start it again #0 ;keep going ENDIF ELSE ;finally a match! DROP DROP ;the match char and mode $LEN ;of emptying match string IFZ ;if successful match DROP #1 ;change to success #1 ;and exit ELSE #1 ;mode 1, match successive chars $HEAD ;next char to match #0 ;keep going ENDIF ENDIF ENDIF UNTIL ;done $DROP $DROP ;strings END ; ;RUN&DEL - execute word in string on X stack then delete it "Loading RUN&DEL" $PRINT CRLF DEFINE RUN&DEL $DEFADR DUP IFZ "Word not found" $PRINT DROP ELSE DUP EXECUTE ;run it 4 SUB #0 PUT ;delete it CRLF ;to make it look neat ENDIF END ; ;The main show.. "file" LIBLOAD searches for the specified ;file then loads it, the file itself determines how it behaves. "Loading LIBLOAD" $PRINT CRLF DEFINE LIBLOAD ";;;FILE:" $SWAP $CAT ":" $CAT MSSCAN IFZ "Not found" $PRINT ELSE LOAD ENDIF END ; ;now test it... "TESTRUN" LIBLOAD ;should print It worked! ;at this point you should be able to "TESTLOAD" LIBLOAD ;to define the word TEST CONSOLE ; ;;;FILE:TESTRUN: "TESTWORD" ;immediate string-push name DEFINE TESTWORD "It worked!" $PRINT END RUN&DEL ;run and delete "TESTWORD" CONSOLE ; ;;;FILE:TESTLOAD: ;conventional file DEFINE TEST "Blablabla" $PRINT END CONSOLE [ascii 128 to terminate] This needs to be adapted to disk files. Operation would be easier if the library file name didn't have to always be specified, but we'll probably want more than one library. Need words that load the specified definition, run the specified definition, and a way to save a definition in memory to the library file if there's room. ------------------------------------ First draft 4/24/05 last modified 5/1/05 Terry Newton email:wtnewton@infionline.net