               UHDD Data Caching For User Drivers
             ======================================

UHDD can be called by a user device driver to cache its data.   For
an actual "running" example of this, see the UDVD2 driver that uses
UHDD to cache CD/DVD data files and/or their directories.

A user driver must have a "callback" subroutine which does its I-O.
Every I-O request to a user driver will cause a call to UHDD, which
immediately calls-back the user routine for output and for uncached
input.    Data already in cache is simply moved to the user's input
buffer, without a call-back.

A user "callback" subroutine is entered on UHDD's stack.    All but
the SS:SP-registers are available and may be used as desired.   The
"callback" subroutine does all I-O for the request originally given
to UHDD for caching.   After I-O ends, it clears the carry flag for
"no error" or sets carry and sets an error code in the AL-register.
The "callback" subroutine then exits to UHDD with a "retf" command.

Upon return from a caching call to UHDD, the user driver must check
for a device I-O error and also an "XMS error" (carry set, AH=0FFh)
declared by UHDD.   This denotes a problem moving cached data to or
from XMS memory, and a memory DIAGNOSTIC may be necessary!

During their initialization, user drivers must examine memory, from
about 00600h to F8000h, to find UHDD and save its "segment" address
(e.g. in variable UHDDSeg, as shown in the sample logic below), for
use in caching calls.   User drivers must search for a segment with
bytes 10-17 (000Ah-0011h) equal to "UHDD$" plus three zeros, UHDD's
permanent driver name.   NOTE that the "stand-alone" UHDD, with the
name "UHDD-SA$", must NOT be used for caching calls!   In searching
for UHDD, user drivers must IGNORE a driver header with a -1 "link"
(uninitialized), which may be the DOS loader's leftover UHDD image!

If a user driver finds UHDD, but its UHDD_IOF flag byte has no free
cache-unit numbers (bits 2-7 of UHDD_IOF are all ones), user-driver
caching is not available, and UHDD caching calls may NOT be issued!
This is true if all cache-unit numbers have been reserved for other
drivers.    If UHDD is not found, or if its caching is unavailable,
the user-driver variable  UHDDSeg  can stay "zero" and be tested on
an I-O request, as in the sample logic below.

User drive "CacheUnit" numbers must range from 030h to 05Fh.   UHDD
permanently reserves units 000h to 02Fh for disks or diskettes, and
UDVD2 permanently reserves units 028h to 02Fh for CDs/DVDs.   Cache
unit numbers are reserved/released in sets of 8 by setting/clearing
an appropriate bit in UHDD_IOF as follows:

UHDD_IOF bit 2:  Reserves cache-units 030h to 037h.
         bit 3:  Reserves cache-units 038h to 03Fh.
         bit 4:  Reserves cache-units 040h to 047h.
         bit 5:  Reserves cache-units 048h to 04Fh.
         bit 6:  Reserves cache-units 050h to 057h.
         bit 7:  Reserves cache-units 058h to 05Fh.

User drivers (except UDVD2) must set or clear these bits themselves
when a driver is being "initialized" or "unloaded".   Other drivers
may NOT use cache-unit numbers that are already reserved!   Drivers
must avoid altering UHDD_IOF bits 0 or 1, as they are UHDD's "busy"
or "flush cache" flags!

With UHDD of 26-Aug-2013 or later, user drivers can have UHDD cache
data into its "common" cache as before, or a user driver can have a
"private" cache of its own!   UHDD now runs using a parameter table
of 28 bytes, which can be assembled as follows:

  STLmt  dw  0       ;Binary-search table limit offset, initially
                     ;  zero and maintained/updated only by UHDD.
                     ;  Cache-block indexes below this offset in
                     ;  the table are "active", above this offset
                     ;  are "free" blocks available for new data.
  LUTop  dw  0FFFFh  ;Least-recently-used "top" index, initially
                     ;  -1 and maintained/updated only by UHDD.
  LUEnd  dw  0FFFFh  ;Least-recently-used "end" index, initially
                     ;  -1 and maintained/updated only by UHDD.
  Blocks dw  0       ;Number of cache blocks, 1 to 65,535.
  SecPB  db  0       ;512-byte data sectors per cache block.
  SecSC  db  0       ;Sector-shift count.   2 for 2K-byte cache
                     ;  blocks, incremented by 1 for each doubling
                     ;  of the block size.  Maximum value of 7 for
                     ;  the maximum 64K-byte cache blocks.
  MidPt  dw  0       ;Binary-search "midpoint" index.   Must be a
                     ;  power of 2 (512, 1024, etc.), and must be
                     ;  less-than or exactly equal-to the number
                     ;  of cache blocks.
  XBase  dd  0       ;32-bit XMS "base" address, indicating the
                     ;  beginning of all cached data.   XMS memory
                     ;  must be allocated by the user:  (512 bytes
                     ;  per sector) * (sectors per block) * (cache
                     ;  block count), plus 16 bytes per block for
                     ;  the tables/buffer described below.
  DTAddr dd  0       ;32-bit cache data-table address, 12 bytes
                     ;  per cache block, normally set directly
                     ;  past the cached data.   No init needed.
  STAddr dd  0       ;32-bit binary-search table address, 2 bytes
                     ;  per cache block, usually set directly past
                     ;  the cache data-table.   MUST be set during
                     ;  driver-init to "ascending" or "descending"
                     ;  numbers, from zero to the maximum cache
                     ;  block -1.
  STBuff dd  0       ;32-bit binary-search buffer address, 2 bytes
                     ;  per cache block, usually set directly past
                     ;  the binary-search table.   No init needed.

All but the first three parameters are constants.   A UHDD cache is
completely controlled by its binary-search table ("STLmt") plus its
least-recently-used list ("LUTop"/"LUEnd").    A cache is "flushed"
on media-changes or a bad I-O error merely by resetting these three
variables to their initial values of zero, 0FFFFh, and 0FFFFh!   No
reloading of cache-block numbers at "STAddr" is needed!

A user parameter-table pointer must be set at UHDD offset 0030h for
every cached I-O request given to UHDD.   0030h is the table offset
and 0032h is the table segment.    On any exit, also when it loads,
UHDD sets its 4-byte pointer at 0030h to -1.    So, on the next I-O
request, UHDD "knows" if a user driver set its own parameter table.
If 0030h is still -1 (NO user parameters!), UHDD sets its own table
and user data will go into UHDD's "common" cache.   This lets "old"
UDVD2 or user drivers continue working with the "new" UHDD!    Note
that UHDD will "download" the user parameters and then "upload" the
final "STLmt/LUTop/LUEnd" data to the user table, when I-O is done.
If an XMS error occurs, UHDD flushes the user cache itself.   Other
error handling is left to the user driver.

Prior UHDD drivers used 0030h as their DMA address or saved caller-
stack address, which would NEVER be -1 (illegal address!).   A user
driver can test for a "new" UHDD that allows user caches by testing
if 0030h is -1, as it is between I-O requests.   If not -1, an "old
UHDD" is present, and a user cache may NOT be specified!

After doing the above initialization procedures, a user driver then
calls UHDD for data caching with the following logic --

UHDD_IOF equ  byte  ptr 013h    ;UHDD flags & cache-unit "bitmap".
UHDD_CBA equ  dword ptr 014h    ;UHDD user "callback" address, seg:
                                ;  offset (not 32-bit!).   Must NOT
                                ;  be set when UHDD is "busy"!
UHDD_TYP equ  byte  ptr 01Bh    ;UHDD device-type code, always 07Eh
                                ;  for any user devices.   Must NOT
                                ;  be set when UHDD is "busy"!
UHDD_TBL equ  dword ptr 030h    ;UHDD user-parameter table pointer,
                                ;  seg:offset (not 32-bit!).   Must
                                ;  NOT be set when UHDD is "busy"!
UHDD_ENT equ  000A0h            ;Fixed user-caching "entry" offset.

         ..
         ..
         ..
        mov   cx,UHDDSeg        ;UHDD absent or caching unavailable?
        jcxz  NoUHDD            ;If either, go do normal device I-O.
        mov   es,cx             ;Set saved UHDD driver segment.
        mov   eax,BufferAddr    ;Set EAX = 32-bit buffer address.
                                ;("VDS lock" address, NOT seg:offs!).
        mov   cl,Sectors        ;Set CL = number of 512-byte sectors.
        mov   ch,RdWrCmd        ;Set CH = 00h if read, 02h if write.
        mov   di,LBAHighBits    ;Set DI =  upper 16 LBA addr. bits.
        mov   dx,LBAMidBits     ;Set DX = middle 16 LBA addr. bits.
        mov   si,LBALowBits     ;Set SI =  lower 16 LBA addr. bits.
                                ;(Unused hi-order bits must be 0!).
        movzx bp,CacheUnit      ;Set BP = 8-bit cache unit number.
        pushf                   ;Stack current CPU flags.
        cli                     ;Disable CPU interrupts.
        bts   es:UHDD_IOF,0     ;Is UHDD currently busy?
        jc    BsyErr            ;Yes?  Handle as an error!
        push  cs                ;Set "callback" subroutine seg:offs
        push  offset OurCBRtn   ;  in UHDD "callback" address.
        pop   es:UHDD_CBA
        mov   es:UHDD_TYP,07Eh  ;Set "user device" in UHDD byte 01Fh.
        push  UserParams        ;Optional:  Set "private cache" table
        pop   es:UHDD_TBL       ;  pointer in UHDD parameter address.
        push  cs                ;Stack UHDD "Int 13h" exit address.
        push  offset Return
        pushf                   ;Stack "dummy" flags and BP-reg.,
        push  bp                ;  loaded when UHDD does its exit.
        xor   bx,bx             ;Ensure UHDD "base register" is zero.
        push  es                ;Do 32-bit "jump" (not call) to UHDD.
        push  UHDD_ENT
        retf
Return: jc    CachErr           ;If carry is set, go handle error!
        ..                      ;No UHDD errors if we arrive here.
        ..
        ..
BsyErr: popf                    ;If busy, reload interrupt state.
        ..                      ;Handle UHDD-busy error as desired.
        ..
        ..
NoUHDD: ..                      ;No UHDD caching -- do normal I-O.
        ..
        ..

User-driver "private caches" can be "flushed" if necessary, as noted
above.   If a media-change or serious error for user drives requires
flushing UHDD's "common" cache, the following logic can be used --

        ..
        ..
        cmp   UHDDSeg,0         ;UHDD absent or caching unavailable?
        je    NoFlsh            ;If either, no cache "flush" needed.
        mov   es,UHDDSeg        ;Set saved UHDD driver segment.
        or    es:UHDD_IOF,002h  ;Post UHDD "flush cache" flag.
                                ;("Flush" occurs before next I-O).
NoFlsh: ..
        ..


*** SPECIAL NOTE ***

The UIDE driver can still be called for data caching, though this is
NOT recommended!   UIDE's /D: switch permits multiple "device names"
to be assigned, which makes "finding" UIDE in memory more difficult!
Also, note that UHDD posts its XMS "handle" number in offset 008h of
the driver, to permit sharing its XMS I-O buffer.   But UIDE may NOT
do this, as its offset 008h is for a CD/DVD "device interrupt" call!
Finally, note that UIDE does not allow user-driver "private caches".

If UIDE must be called for data caching, its variable offsets are --

UIDE_IOF equ  byte  ptr 013h    ;UIDE flags & cache-unit "bitmap".
UIDE_CBA equ  dword ptr 018h    ;UIDE user "callback" address.
UIDE_TYP equ  byte  ptr 01Fh    ;UIDE device-type code.
UIDE_ENT equ  000B0h            ;Fixed user-caching "entry" offset.

