/*
 * Copyright (c) 1998-2003 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * Copyright (c) 1999-2003 Apple Computer, Inc.  All Rights Reserved.
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */

#include <sys/types.h>                       // (miscfs/devfs/devfs.h, ...)

#include <miscfs/devfs/devfs.h>              // (devfs_make_node, ...)
#include <sys/buf.h>                         // (struct buf, ...)
#include <sys/conf.h>                        // (bdevsw_add, ...)
#include <sys/fcntl.h>                       // (FWRITE, ...)
#include <sys/ioccom.h>                      // (IOCGROUP, ...)
#include <sys/stat.h>                        // (S_ISBLK, ...)
#include <sys/systm.h>                       // (kernel_flock, ...)
#include <sys/uio.h>                         // (struct uio, ...)
#include <IOKit/assert.h>
#include <IOKit/IOBSD.h>
#include <IOKit/IODeviceTreeSupport.h>
#include <IOKit/IOLib.h>
#include <IOKit/IOKitKeys.h>
#include <IOKit/IOMemoryDescriptor.h>
#include <IOKit/IOMessage.h>
#include <IOKit/storage/IOBlockStorageDriver.h>
#include <IOKit/storage/IOMedia.h>
#include <IOKit/storage/IOMediaBSDClient.h>
#include <IOKit/storage/IOStorageDeviceCharacteristics.h>

#define super IOService
OSDefineMetaClassAndStructors(IOMediaBSDClient, IOService)

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

const SInt32 kMajor              = 14;
const UInt32 kMinorsAddCountBits = 6;
const UInt32 kMinorsAddCountMask = (1 << kMinorsAddCountBits) - 1;
const UInt32 kMinorsAddCount     = (1 << kMinorsAddCountBits);
const UInt32 kMinorsMaxCountBits = 16;
const UInt32 kMinorsMaxCountMask = (1 << kMinorsMaxCountBits) - 1;
const UInt32 kMinorsMaxCount     = (1 << kMinorsMaxCountBits);
const UInt32 kMinorsBucketCount  = kMinorsMaxCount / kMinorsAddCount;
const UInt32 kAnchorsAddCount    = 2;
const UInt32 kAnchorsMaxCount    = kMinorsMaxCount;

#define kMsgNoWhole    "%s: No whole media found for media \"%s\".\n", getName()
#define kMsgNoLocation "%s: No location is found for media \"%s\".\n", getName()

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

extern "C"
{
    int  dkclose(dev_t dev, int flags, int devtype, struct proc *);
    int  dkioctl(dev_t dev, u_long cmd, caddr_t data, int, struct proc *);
    int  dkioctl_bdev(dev_t dev, u_long cmd, caddr_t data, int, struct proc *);
    int  dkopen(dev_t dev, int flags, int devtype, struct proc *);
    int  dkread(dev_t dev, struct uio * uio, int flags);
    int  dksize(dev_t dev);    
    void dkstrategy(struct buf * bp);
    int  dkwrite(dev_t dev, struct uio * uio, int flags);
} // extern "C"

static struct bdevsw bdevswFunctions =
{
    /* d_open     */ dkopen,
    /* d_close    */ dkclose,
    /* d_strategy */ dkstrategy,
    /* d_ioctl    */ dkioctl_bdev,
    /* d_dump     */ eno_dump,
    /* d_psize    */ dksize,
    /* d_type     */ D_DISK
};

struct cdevsw cdevswFunctions =
{
    /* d_open     */ dkopen,
    /* d_close    */ dkclose,
    /* d_read     */ dkread,
    /* d_write    */ dkwrite,
    /* d_ioctl    */ dkioctl,
    /* d_stop     */ eno_stop,
    /* d_reset    */ eno_reset,
    /* d_ttys     */ 0,
    /* d_select   */ eno_select,
    /* d_mmap     */ eno_mmap,
    /* d_strategy */ eno_strat,
    /* d_getc     */ eno_getc,
    /* d_putc     */ eno_putc,
    /* d_type     */ D_DISK
};

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

struct dio { dev_t dev; struct uio * uio; void * drvdata; };

typedef void *                            dkr_t;       /* dkreadwrite request */
typedef enum { DKRTYPE_BUF, DKRTYPE_DIO } dkrtype_t;

static int  dkreadwrite(dkr_t dkr, dkrtype_t dkrtype);
static void dkreadwritecompletion(void *, void *, IOReturn, UInt64);

extern "C" task_t get_aiotask(void);

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

const UInt32 kInvalidAnchorID = (UInt32) (-1);

struct AnchorSlot
{
    UInt32       isAssigned:1; // (slot is occupied)
    UInt32       isObsolete:1; // (slot is to be removed once references gone)

    IOService *  anchor;       // (anchor object)
    void *       key;          // (anchor key)
    IONotifier * notifier;     // (anchor termination notification, post-stop)
};

class AnchorTable
{
protected:
    AnchorSlot * _table;
    UInt32       _tableCount;

    static IOReturn anchorWasNotified( void *      target,
                                       void *      parameter,
                                       UInt32      messageType,
                                       IOService * provider,
                                       void *      messageArgument,
                                       vm_size_t   messageArgumentSize );

public:
    AnchorTable();
    ~AnchorTable();

    UInt32 insert(IOService * anchor, void * key);
    UInt32 locate(IOService * anchor);
    UInt32 locate(IOService * anchor, void * key);
    void   obsolete(UInt32 anchorID);
    void   remove(UInt32 anchorID);
    UInt32 update(IOService * anchor, void * key);

    bool   isObsolete(UInt32 anchorID);
};

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

const UInt32 kInvalidMinorID = (UInt32) (-1);

struct MinorSlot
{
    UInt32             isAssigned:1;  // (slot is occupied)
    UInt32             isObsolete:1;  // (slot is to be removed, close pending)
    UInt32             isOrphaned:1;  // (slot is in  open flux, close pending)

    UInt32             anchorID;      // (minor's associated anchor ID)
    IOMediaBSDClient * client;        // (minor's media bsd client object)
    IOMedia *          media;         // (minor's media object)
    char *             name;          // (minor's name, private allocation)

    UInt64             bdevBlockSize; // (block device's preferred block size)
    void *             bdevNode;      // (block device's devfs node)
    UInt32             bdevOpen:16;   // (block device's open count)
    UInt32             bdevWriter:1;  // (block device's open writer flag)

    void *             cdevNode;      // (character device's devfs node)
    UInt32             cdevOpen:16;   // (character device's open count)
    UInt32             cdevWriter:1;  // (character device's open writer flag)
};

class MinorTable
{
protected:
    class
    {
    public:
        MinorSlot ** buckets;

        inline MinorSlot & operator[](const int i)
        {
            return (buckets[i >> kMinorsAddCountBits])[i & kMinorsAddCountMask];
        }
    } _table;

    UInt32 _tableCount;

public:
    MinorTable();
    ~MinorTable();

    UInt32      insert( IOMedia *          media,
                        UInt32             anchorID,
                        IOMediaBSDClient * client,
                        char *             slicePath );

    UInt32      update( IOMedia *          media,
                        UInt32             anchorID,
                        IOMediaBSDClient * client,
                        char *             slicePath );

    UInt32      locate(IOMedia * media);
    void        obsolete(UInt32 minorID);
    void        remove(UInt32 minorID);

    bool        isObsolete(UInt32 minorID);

    MinorSlot * getMinor(UInt32 minorID);

    UInt32      getOpenCountForAnchorID(UInt32 anchorID);
    bool        hasReferencesToAnchorID(UInt32 anchorID, bool excludeOrphans);
};

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

class IOMediaBSDClientGlobals
{
protected:
    AnchorTable * _anchors;           // (table of anchors)
    MinorTable *  _minors;            // (table of minors)

    UInt32        _bdevswInstalled:1; // (are bdevsw functions installed?)
    UInt32        _cdevswInstalled:1; // (are cdevsw functions installed?)

    IOLock *      _openLock;          // (lock for opens, closes)
    IOLock *      _stateLock;         // (lock for state, tables)

public:
    IOMediaBSDClientGlobals();
    ~IOMediaBSDClientGlobals();

    AnchorTable * getAnchors();
    MinorTable *  getMinors();
    MinorSlot *   getMinor(UInt32 minorID);

    bool          isValid();

    void          lockOpen();
    void          unlockOpen();

    void          lockState();
    void          unlockState();
};

static IOMediaBSDClientGlobals gIOMediaBSDClientGlobals;

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

bool IOMediaBSDClient::init(OSDictionary * properties)
{
    //
    // Initialize this object's minimal state.
    //

    // Ask our superclass' opinion.

    if ( super::init(properties) == false )  return false;

    // Determine whether our minimal global state has been initialized.

    if ( gIOMediaBSDClientGlobals.isValid() == false )  return false;

    // Initialize this object's minimal state.

    _anchors = gIOMediaBSDClientGlobals.getAnchors();
    _minors  = gIOMediaBSDClientGlobals.getMinors();

    return true;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

void IOMediaBSDClient::free()
{
    //
    // Free all of this object's outstanding resources.
    //

    super::free();
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

bool IOMediaBSDClient::start(IOService * provider)
{
    //
    // This method is called once we have been attached to the provider object.
    //

    IOMedia * media = (IOMedia *) provider;

    // Ask our superclass' opinion.

    if ( super::start(provider) == false )  return false;

    // Disable access to tables.

    gIOMediaBSDClientGlobals.lockState();

    // Create bdevsw and cdevsw nodes for the new media object.

    createNodes(media);

    // Enable access to tables.

    gIOMediaBSDClientGlobals.unlockState();

    // Register this object so it can be found via notification requests. It is
    // not being registered to have I/O Kit attempt to have drivers match on it,
    // which is the reason most other services are registered -- that's not the
    // intention of this registerService call.

    registerService();

    return true;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

bool IOMediaBSDClient::terminate(IOOptionBits options)
{
    //
    // This method is called when we are to terminate from the provider object.
    //

    UInt32 minorID;

    // Ask our superclass' opinion.

    if ( super::terminate(options) == false )  return false;

    // Disable access to tables.

    gIOMediaBSDClientGlobals.lockState();

    // Find the minor assigned to this media.

    minorID = gIOMediaBSDClientGlobals.getMinors()->locate(getProvider());

    if ( minorID != kInvalidMinorID )
    {
        MinorSlot * minor;

        minor = gIOMediaBSDClientGlobals.getMinors()->getMinor(minorID);

        // Remove the minor from the minor table.  If an open is still
        // outstanding, we mark the minor as obsolete for removal when
        // the close comes in later.

        if ( minor->bdevOpen || minor->cdevOpen )
        {
            gIOMediaBSDClientGlobals.getMinors()->obsolete(minorID);
        }
        else
        {
            gIOMediaBSDClientGlobals.getMinors()->remove(minorID);
        }
    }

    // Enable access to tables.

    gIOMediaBSDClientGlobals.unlockState();

    return true;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

IOMedia * IOMediaBSDClient::getWholeMedia( IOMedia * media,
                                           UInt32 *  slicePathSize,
                                           char *    slicePath )
{
    //
    // Find the whole media that roots this media tree.  A null return value
    // indicates no whole media was found or a malformed tree was detected.
    //
    // If slicePathSize is non-zero, the size required to fit the slice path
    // (including the zero terminator) is passed back as a result.
    //
    // If slicePathSize and slicePath are both non-zero, the slice path will
    // be written into the slicePath buffer.  The value slicePathSize points
    // to must be the size of the slicePath buffer, which is used for sanity
    // checking in this method.
    //
    // This method assumes that the table (and termination) lock is held.
    //

    UInt32      depth    = 1;
    UInt32      position = sizeof('\0');
    IOService * service  = 0;

    assert(slicePath == 0 || slicePathSize != 0);

    // Search the registry for the parent whole media for this media.

    for ( service = media; service; service = service->getProvider() )
    {
        if ( OSDynamicCast(IOMedia, service) )               // (is it a media?)
        {
            if ( ((IOMedia *)service)->isWhole() )     // (is it a whole media?)
            {
                if ( slicePath )            // (are we building the slice path?)
                {
                    slicePath[*slicePathSize - 1] = 0;  // (zero terminate path)

                    if ( position < *slicePathSize )     // (need to move path?)
                    {
                        memmove( slicePath,    // (move path to start of buffer)
                                 slicePath + (*slicePathSize - position),
                                 position );
                    }
                }
                else if ( slicePathSize ) // (report size req'd for slice path?)
                {
                    *slicePathSize = position;
                }

                return (IOMedia *)service;           // (return the whole media)
            }

            // Determine whether this non-whole media has a location value.  It
            // must, by definition of a non-whole media, but if it does not, we
            // should return an error condition.

            const char * location = service->getLocation();

            if ( location == 0 )            // (no location on non-whole media?)
            {
                if ( service == media ) IOLog(kMsgNoLocation, media->getName());
                return 0;
            }

            // Otherwise, it's a valid non-whole media: we compute the required
            // size for the slice path or build the slice path, if so requested.
            // Note that the slice path is built backwards from the ends of the
            // supplied buffer to the beginning of the buffer.

            position += sizeof('s') + strlen(location);

            if ( slicePath )                          // (build the slice path?)
            {
                char * path = slicePath + *slicePathSize - position;

                if ( position > *slicePathSize )  { assert(0);  return 0; }

                *path = 's';
                strncpy(path + sizeof('s'), location, strlen(location));
            }

            depth += 1;
        }
    }

    // If we've fallen through, then the whole media was never found.

    if ( depth == 1 )  IOLog(kMsgNoWhole, media->getName());

    return 0;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

bool IOMediaBSDClient::createNodes(IOMedia * media)
{
    //
    // Create bdevsw and cdevsw nodes for the given media object.
    //
    // This method assumes that the table (and termination) lock is held.
    //

    IOService *   anchor;
    AnchorTable * anchors   = gIOMediaBSDClientGlobals.getAnchors();
    UInt32        anchorID;
    bool          anchorNew = false;
    MinorTable *  minors    = gIOMediaBSDClientGlobals.getMinors();
    UInt32        minorID;
    char *        slicePath = 0;
    UInt32        slicePathSize;
    IOMedia *     whole;

    //
    // Find the anchor that roots this media tree.  The anchor is defined as the
    // parent of the whole media that roots this media tree.  It is an important
    // object to us because this object stays in place when media is ejected, so
    // we can continue to maintain the "unit number" of the "drive" such that if
    // media is re-inserted, it will show up under the same "unit number".   You
    // can think of the typical anchor as being the drive, if it helps, although
    // it could be one of many other kinds of drivers (eg. a RAID scheme).
    //

    whole = getWholeMedia(media, &slicePathSize);
    if ( whole == 0 )  return false;

    anchor = whole->getProvider();
    if ( anchor == 0 )  return false;

    //
    // Determine whether the anchor already exists in the anchor table (obsolete
    // occurences are skipped in the search, as appropriate,  since those anchor
    // IDs are to be removed soon). If the anchor does not exist, insert it into
    // anchor table.
    //

    anchorID = anchors->locate(anchor, whole);

    if ( anchorID == kInvalidAnchorID )
    {
        //
        // The anchor and key pair does not exist in the table, however we still
        // have more to check.  The anchor might in fact exist in the table, but
        // have a different key.  If such a slot exists, and it isn't referenced
        // in the minor table, we reuse the slot.
        //

        anchorID = anchors->update(anchor, whole);
    }

    if ( anchorID == kInvalidAnchorID )
    {
        anchorID = anchors->insert(anchor, whole);        // (get new anchor ID)
        if ( anchorID == kInvalidAnchorID )  return false;
        anchorNew = true;
    }

    //
    // Allocate space for and build the slice path for the device node names.
    //

    slicePath = (char *) IOMalloc(slicePathSize);
    if ( slicePath == 0 )  goto createNodesErr;

    whole = getWholeMedia(media, &slicePathSize, slicePath);
    assert(whole);

    //
    // Insert the new media into our minor table (we're almost done :-).
    //

    minorID = minors->update(media, anchorID, this, slicePath);

    if ( minorID == kInvalidMinorID )
    {
        minorID = minors->insert(media, anchorID, this, slicePath);
        if ( minorID == kInvalidMinorID )  goto createNodesErr;
    }

    //
    // Create the required properties on the media.
    //

    media->setProperty(kIOBSDNameKey,  minors->getMinor(minorID)->name);
    media->setProperty(kIOBSDUnitKey,  anchorID, 32);           // ("BSD Unit" )
    media->setProperty(kIOBSDMajorKey, kMajor,   32);           // ("BSD Major")
    media->setProperty(kIOBSDMinorKey, minorID,  32);           // ("BSD Minor")

    //
    // Clean up outstanding resources.
    //

    IOFree(slicePath, slicePathSize);

    return true; // (success)

createNodesErr:

    if (anchorNew)  anchors->remove(anchorID);
    if (slicePath)  IOFree(slicePath, slicePathSize);

    return false; // (failure)
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

/* DEPRECATED */ AnchorTable * IOMediaBSDClient::getAnchors()
/* DEPRECATED */ {
/* DEPRECATED */     //
/* DEPRECATED */     // Obtain the table of anchors.
/* DEPRECATED */     //
/* DEPRECATED */ 
/* DEPRECATED */     return _anchors;
/* DEPRECATED */ }

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

/* DEPRECATED */ MinorTable * IOMediaBSDClient::getMinors()
/* DEPRECATED */ {
/* DEPRECATED */     //
/* DEPRECATED */     // Obtain the table of anchors.
/* DEPRECATED */     //
/* DEPRECATED */ 
/* DEPRECATED */     return _minors;
/* DEPRECATED */ }

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

/* DEPRECATED */ MinorSlot * IOMediaBSDClient::getMinor(UInt32 minorID)
/* DEPRECATED */ {
/* DEPRECATED */     //
/* DEPRECATED */     // Obtain information for the specified minor ID.
/* DEPRECATED */     //
/* DEPRECATED */ 
/* DEPRECATED */     return _minors->getMinor(minorID);
/* DEPRECATED */ }

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

IOMedia * IOMediaBSDClient::getProvider() const
{
    //
    // Obtain this object's provider.  We override the superclass's method to
    // return a more specific subclass of IOService -- IOMedia.   This method
    // serves simply as a convenience to subclass developers.
    //

    return (IOMedia *) IOService::getProvider();
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

int IOMediaBSDClient::ioctl( dev_t         dev,
                             u_long        cmd,
                             caddr_t       data,
                             int           flags,
                             struct proc * proc )
{
    //
    // Process a foreign ioctl.
    //

    int error = 0;

    switch ( cmd )
    {
        default:
        {
            //
            // A foreign ioctl was received.  Log an error to the console.
            //

            IOLog( "%s: ioctl(%s\'%c\',%d,%d) is unsupported.\n",
                   gIOMediaBSDClientGlobals.getMinor(minor(dev))->name,
                   ((cmd & IOC_INOUT) == IOC_INOUT) ? ("_IOWR,") :
                     ( ((cmd & IOC_OUT) == IOC_OUT) ? ("_IOR,") :
                       ( ((cmd & IOC_IN) == IOC_IN) ? ("_IOW,") :
                         ( ((cmd & IOC_VOID) == IOC_VOID) ? ("_IO,") : "" ) ) ),
                   (char) IOCGROUP(cmd),
                   (int)  (cmd & 0xff),
                   (int)  IOCPARM_LEN(cmd) );

            error = ENOTTY;

        } break;
    }

    return error;                                       // (return error status)
}

OSMetaClassDefineReservedUsed(IOMediaBSDClient, 0);

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

OSMetaClassDefineReservedUnused(IOMediaBSDClient, 1);

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

OSMetaClassDefineReservedUnused(IOMediaBSDClient, 2);

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

OSMetaClassDefineReservedUnused(IOMediaBSDClient, 3);

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

OSMetaClassDefineReservedUnused(IOMediaBSDClient, 4);

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

OSMetaClassDefineReservedUnused(IOMediaBSDClient, 5);

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

OSMetaClassDefineReservedUnused(IOMediaBSDClient, 6);

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

OSMetaClassDefineReservedUnused(IOMediaBSDClient, 7);

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

OSMetaClassDefineReservedUnused(IOMediaBSDClient, 8);

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

OSMetaClassDefineReservedUnused(IOMediaBSDClient, 9);

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

OSMetaClassDefineReservedUnused(IOMediaBSDClient, 10);

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

OSMetaClassDefineReservedUnused(IOMediaBSDClient, 11);

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

OSMetaClassDefineReservedUnused(IOMediaBSDClient, 12);

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

OSMetaClassDefineReservedUnused(IOMediaBSDClient, 13);

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

OSMetaClassDefineReservedUnused(IOMediaBSDClient, 14);

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

OSMetaClassDefineReservedUnused(IOMediaBSDClient, 15);

// =============================================================================
// BSD Functions

static bool DKIOC_IS_RESERVED(caddr_t data, u_int16_t reserved)
{
    UInt32 index;

    for ( index = 0; index < sizeof(reserved) * 8; index++, reserved >>= 1 )
    {
        if ( (reserved & 1) )
        {
            if ( data[index] )  return true;
        }
    }

    return false;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

int dkopen(dev_t dev, int flags, int devtype, struct proc * p)
{
    //
    // dkopen opens the device (called on each open).
    //

    int             error;
    IOStorageAccess level;
    IOMedia *       media;
    MinorSlot *     minor;

    assert(S_ISBLK(devtype) || S_ISCHR(devtype));

    gIOMediaBSDClientGlobals.lockOpen();    // (disable access to opens, closes)
    gIOMediaBSDClientGlobals.lockState();   // (disable access to state, tables)

    error = 0;
    level = (flags & FWRITE) ? kIOStorageAccessReaderWriter
                             : kIOStorageAccessReader;
    media = 0;
    minor = gIOMediaBSDClientGlobals.getMinor(minor(dev));

    //
    // Process the open.
    //

    if ( minor == 0 )                                       // (is minor valid?)
    {
        error = ENXIO;
    }
    else if ( minor->isOrphaned )                         // (is minor in flux?)
    {
        error = EBUSY;
    }
    else if ( (flags & FWRITE) )                        // (is client a writer?)
    {
        if ( minor->media->isWritable() == false )
            error = EACCES;

        if ( minor->bdevWriter || minor->cdevWriter )
            level = kIOStorageAccessNone;
    }
    else                                                // (is client a reader?)
    {
        if ( minor->bdevOpen || minor->cdevOpen )
            level = kIOStorageAccessNone;
    }

    if ( error == 0 )                                                   // (go?)
    {
        bool wasWriter;

        if ( S_ISBLK(devtype) )                                // (update state)
        {
            minor->bdevOpen++;
            wasWriter = minor->bdevWriter;
            if ( (flags & FWRITE) )  minor->bdevWriter = true;
        }
        else
        {
            minor->cdevOpen++;
            wasWriter = minor->cdevWriter;
            if ( (flags & FWRITE) )  minor->cdevWriter = true;
        }

        gIOMediaBSDClientGlobals.unlockState();     // (enable access to tables)

        if ( level != kIOStorageAccessNone )            // (issue open/upgrade?)
        {
            media = minor->media;
            minor->media->retain();

            if ( minor->media->open(minor->client, 0, level) == false )  // (go)
            {
                gIOMediaBSDClientGlobals.lockState();        // (disable access)

                if ( S_ISBLK(devtype) )                          // (undo state)
                {
                    minor->bdevOpen--;
                    minor->bdevWriter = wasWriter;
                }
                else
                {
                    minor->cdevOpen--;
                    minor->cdevWriter = wasWriter;
                }

                assert(minor->isOrphaned == false);

                if ( !minor->bdevOpen && !minor->cdevOpen && minor->isObsolete )
                    gIOMediaBSDClientGlobals.getMinors()->remove(minor(dev));

                gIOMediaBSDClientGlobals.unlockState();       // (enable access)

                error = EBUSY;
            }
        }
    }
    else
    {
        gIOMediaBSDClientGlobals.unlockState();     // (enable access to tables)
    }

    gIOMediaBSDClientGlobals.unlockOpen();   // (enable access to opens, closes)

    //
    // Wait until I/O Kit has finished to attempt to match storage drivers
    // or terminate storage drivers, should the media object have been re-
    // registered or its storage driver been terminated as a result of the
    // open.
    //

    if ( media )
    {
        media->waitQuiet();
        media->release();
    }

    return error;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

int dkclose(dev_t dev, int /* flags */, int devtype, struct proc *)
{
    //
    // dkclose closes the device (called on last close).
    //

    IOMedia *   media;
    MinorSlot * minor;
    bool        wasWriter;

    assert(S_ISBLK(devtype) || S_ISCHR(devtype));

    gIOMediaBSDClientGlobals.lockOpen();    // (disable access to opens, closes)
    gIOMediaBSDClientGlobals.lockState();   // (disable access to state, tables)

    media     = 0;
    minor     = gIOMediaBSDClientGlobals.getMinor(minor(dev));
    wasWriter = (minor->bdevWriter || minor->cdevWriter); 

    if ( S_ISBLK(devtype) )                                    // (update state)
    {
        minor->bdevBlockSize = minor->media->getPreferredBlockSize();
        minor->bdevOpen      = 0;
        minor->bdevWriter    = false;
    }
    else
    {
        minor->cdevOpen      = 0;
        minor->cdevWriter    = false;
    }

    if ( minor->isOrphaned )                              // (is minor in flux?)
    {
        //
        // We have determined that the specified minor is in "open flux".  This
        // means we are in a state where the media object has been closed, only
        // the device node is still open.  This happens to the minor subsequent
        // to a DKIOCEJECT ioctl -- this close resets the flux state to normal.
        //

        minor->isOrphaned = false;

        // If this minor is marked as obsolete, then we've already received the
        // media's termination notification, but the minor is yet to be removed
        // from the table -- remove it now.

        assert(minor->bdevOpen == 0);
        assert(minor->cdevOpen == 0);

        if ( minor->isObsolete )
            gIOMediaBSDClientGlobals.getMinors()->remove(minor(dev));

        gIOMediaBSDClientGlobals.unlockState();     // (enable access to tables)
    }
    else if ( !minor->bdevOpen && !minor->cdevOpen )
    {
        //
        // We communicate the close down to the media object once all opens are
        // gone, on both the block and character device nodes.
        //

        IOMediaBSDClient * client;

        client = minor->client;
        minor->client->retain();

        media = minor->media;
        minor->media->retain();

        // If this minor is marked as obsolete, then we've already received the
        // media's termination notification, but the minor is yet to be removed
        // from the table -- remove it now.

        if ( minor->isObsolete )
            gIOMediaBSDClientGlobals.getMinors()->remove(minor(dev));

        gIOMediaBSDClientGlobals.unlockState();     // (enable access to tables)

        media->close(client);                                            // (go)

        client->release();
    }
    else if ( !minor->bdevWriter && !minor->cdevWriter && wasWriter )
    {
        //
        // We communicate a downgrade down to the media object once all writers
        // are gone and while readers still exist. 
        //

        media = minor->media;
        minor->media->retain();

        gIOMediaBSDClientGlobals.unlockState();     // (enable access to tables)

        minor->media->open(minor->client, 0, kIOStorageAccessReader);
    }
    else
    {
        gIOMediaBSDClientGlobals.unlockState();     // (enable access to tables)
    }

    gIOMediaBSDClientGlobals.unlockOpen();   // (enable access to opens, closes)

    //
    // Wait until I/O Kit has finished to attempt to match storage drivers,
    // should the media object have been re-registered as a result of this
    // close.
    //

    if ( media )
    {
        media->waitQuiet();
        media->release();
    }

    return 0;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

int dkread(dev_t dev, struct uio * uio, int /* flags */)
{
    //
    // dkread reads data from a device.
    //

    struct dio dio = { dev, uio };

    return dkreadwrite(&dio, DKRTYPE_DIO);
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

int dkwrite(dev_t dev, struct uio * uio, int /* flags */)
{
    //
    // dkwrite writes data to a device.
    //

    struct dio dio = { dev, uio };

    return dkreadwrite(&dio, DKRTYPE_DIO);
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

void dkstrategy(struct buf * bp)
{
    //
    // dkstrategy starts an asynchronous read or write operation.  It returns
    // to the caller as soon as the operation is queued, and completes it via
    // the biodone function.
    //

    dkreadwrite(bp, DKRTYPE_BUF);
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

int dkioctl(dev_t dev, u_long cmd, caddr_t data, int f, struct proc * p)
{
    //
    // dkioctl performs operations other than a read or write.
    //

    int         error = 0;
    MinorSlot * minor = gIOMediaBSDClientGlobals.getMinor(minor(dev));

    if ( minor->isOrphaned )  return EBADF;               // (is minor in flux?)

    //
    // Process the ioctl.
    //

    switch ( cmd )
    {
        case DKIOCGETBLOCKSIZE:                                 // (u_int32_t *)
        {
            //
            // This ioctl returns the preferred block size of the media object.
            //

            *(u_int32_t *)data = minor->media->getPreferredBlockSize();

        } break;

        case DKIOCGETBLOCKCOUNT32:                              // (u_int32_t *)
        {
            //
            // This ioctl returns the size of the media object in blocks.  The
            // implied block size is returned by DKIOCGETBLOCKSIZE.
            //

            if ( minor->media->getPreferredBlockSize() )
                *(u_int32_t *)data = ( minor->media->getSize()               / 
                                       minor->media->getPreferredBlockSize() );
            else
                *(u_int32_t *)data = 0;

        } break;

        case DKIOCGETBLOCKCOUNT:                                // (u_int64_t *)
        {
            //
            // This ioctl returns the size of the media object in blocks.  The
            // implied block size is returned by DKIOCGETBLOCKSIZE.
            //

            if ( minor->media->getPreferredBlockSize() )
                *(u_int64_t *)data = ( minor->media->getSize()               / 
                                       minor->media->getPreferredBlockSize() );
            else
                *(u_int64_t *)data = 0;

        } break;

        case DKIOCGETMAXBLOCKCOUNTREAD:                         // (u_int64_t *)
        {
            //
            // This ioctl returns the maximum block count for reads.
            //

            OSNumber * number = OSDynamicCast(
                         /* class  */ OSNumber,
                         /* object */ minor->media->getProperty(
                                 /* key   */ kIOMaximumBlockCountReadKey,
                                 /* plane */ gIOServicePlane ) );
            if ( number )
                *(u_int64_t *)data = number->unsigned64BitValue();
            else
                *(u_int64_t *)data = 0;

        } break;

        case DKIOCGETMAXBLOCKCOUNTWRITE:                        // (u_int64_t *)
        {
            //
            // This ioctl returns the maximum block count for writes.
            //

            OSNumber * number = OSDynamicCast(
                         /* class  */ OSNumber,
                         /* object */ minor->media->getProperty(
                                 /* key   */ kIOMaximumBlockCountWriteKey,
                                 /* plane */ gIOServicePlane ) );
            if ( number )
                *(u_int64_t *)data = number->unsigned64BitValue();
            else
                *(u_int64_t *)data = 0;

        } break;

        case DKIOCGETMAXBYTECOUNTREAD:                          // (u_int64_t *)
        {
            //
            // This ioctl returns the maximum byte count for reads.
            //

            OSNumber * number = OSDynamicCast(
                         /* class  */ OSNumber,
                         /* object */ minor->media->getProperty(
                                 /* key   */ kIOMaximumByteCountReadKey,
                                 /* plane */ gIOServicePlane ) );
            if ( number )
                *(u_int64_t *)data = number->unsigned64BitValue();
            else
                *(u_int64_t *)data = 0;

        } break;

        case DKIOCGETMAXBYTECOUNTWRITE:                         // (u_int64_t *)
        {
            //
            // This ioctl returns the maximum byte count for writes.
            //

            OSNumber * number = OSDynamicCast(
                         /* class  */ OSNumber,
                         /* object */ minor->media->getProperty(
                                 /* key   */ kIOMaximumByteCountWriteKey,
                                 /* plane */ gIOServicePlane ) );
            if ( number )
                *(u_int64_t *)data = number->unsigned64BitValue();
            else
                *(u_int64_t *)data = 0;

        } break;

        case DKIOCGETMAXSEGMENTCOUNTREAD:                       // (u_int64_t *)
        {
            //
            // This ioctl returns the maximum segment count for reads.
            //

            OSNumber * number = OSDynamicCast(
                         /* class  */ OSNumber,
                         /* object */ minor->media->getProperty(
                                 /* key   */ kIOMaximumSegmentCountReadKey,
                                 /* plane */ gIOServicePlane ) );
            if ( number )
                *(u_int64_t *)data = number->unsigned64BitValue();
            else
                *(u_int64_t *)data = 0;

        } break;

        case DKIOCGETMAXSEGMENTCOUNTWRITE:                      // (u_int64_t *)
        {
            //
            // This ioctl returns the maximum segment count for writes.
            //

            OSNumber * number = OSDynamicCast(
                         /* class  */ OSNumber,
                         /* object */ minor->media->getProperty(
                                 /* key   */ kIOMaximumSegmentCountWriteKey,
                                 /* plane */ gIOServicePlane ) );
            if ( number )
                *(u_int64_t *)data = number->unsigned64BitValue();
            else
                *(u_int64_t *)data = 0;

        } break;

        case DKIOCGETMAXSEGMENTBYTECOUNTREAD:                   // (u_int64_t *)
        {
            //
            // This ioctl returns the maximum segment byte count for reads.
            //

            OSNumber * number = OSDynamicCast(
                         /* class  */ OSNumber,
                         /* object */ minor->media->getProperty(
                                 /* key   */ kIOMaximumSegmentByteCountReadKey,
                                 /* plane */ gIOServicePlane ) );
            if ( number )
                *(u_int64_t *)data = number->unsigned64BitValue();
            else
                *(u_int64_t *)data = 0;

        } break;

        case DKIOCGETMAXSEGMENTBYTECOUNTWRITE:                  // (u_int64_t *)
        {
            //
            // This ioctl returns the maximum segment byte count for writes.
            //

            OSNumber * number = OSDynamicCast(
                         /* class  */ OSNumber,
                         /* object */ minor->media->getProperty(
                                 /* key   */ kIOMaximumSegmentByteCountWriteKey,
                                 /* plane */ gIOServicePlane ) );
            if ( number )
                *(u_int64_t *)data = number->unsigned64BitValue();
            else
                *(u_int64_t *)data = 0;

        } break;

        case DKIOCGETISVIRTUAL:                                 // (u_int32_t *)
        {
            //
            // This ioctl returns the whether the storage is virtual (disk images).
            //

	    *(u_int32_t *)data = false;

            OSDictionary * dict = OSDynamicCast(
                         /* class  */ OSDictionary,
                         /* object */ minor->media->getProperty(
                                 /* key   */ kIOPropertyProtocolCharacteristicsKey,
                                 /* plane */ gIOServicePlane ) );


	    if ( dict )
	    {
		OSString * connect  = OSDynamicCast(OSString,
				       dict->getObject(kIOPropertyPhysicalInterconnectTypeKey));
		OSString * location = OSDynamicCast(OSString,
				       dict->getObject(kIOPropertyPhysicalInterconnectLocationKey));

		if (connect && location
		  && connect->isEqualTo("Virtual Interface")
		  && location->isEqualTo("File"))
		    *(u_int32_t *)data = true;
	    }

        } break;

        case DKIOCISFORMATTED:                                  // (u_int32_t *)
        {
            //
            // This ioctl returns truth if the media object is formatted.
            //

            *(u_int32_t *)data = minor->media->isFormatted();

        } break;

        case DKIOCISWRITABLE:                                   // (u_int32_t *)
        {
            //
            // This ioctl returns truth if the media object is writable.
            //

            *(u_int32_t *)data = minor->media->isWritable();

        } break;

        case DKIOCGETFIRMWAREPATH:                     // (dk_firmware_path_t *)
        {
            //
            // This ioctl returns the open firmware path for this media object.
            //

            int    l = sizeof(((dk_firmware_path_t *)data)->path);
            char * p = ((dk_firmware_path_t *)data)->path;

            if ( minor->media->getPath(p, &l, gIODTPlane) && strchr(p, ':') )
                strcpy(p, strchr(p, ':') + 1);         // (strip the plane name)
            else
                error = EINVAL;

        } break;

        case DKIOCEJECT:                                               // (void)
        {
            //
            // This ioctl asks that the media object be ejected from the device.
            //

            IOMediaBSDClient *     client;
            IOBlockStorageDriver * driver;
            MinorTable *           minors;
            IOReturn               status;

            client = minor->client;
            driver = (IOBlockStorageDriver *) minor->media->getProvider();
            driver = OSDynamicCast(IOBlockStorageDriver, driver);
            minors = gIOMediaBSDClientGlobals.getMinors();

            // Determine whether this media has an IOBlockStorageDriver parent.

            if ( driver == 0 )  { error = ENOTTY;  break; }

            // Disable access to opens, closes, tables.

            gIOMediaBSDClientGlobals.lockOpen();
            gIOMediaBSDClientGlobals.lockState();

            // Determine whether there are other opens on the device nodes that
            // are associated with this anchor -- the one valid open is the one
            // that issued this eject.  If all is well, we then attempt to open
            // the block storage driver to make the ejection request.

            if ( minors->getOpenCountForAnchorID(minor->anchorID) == 1 &&
                 driver->open(client, 0, kIOStorageAccessReaderWriter) )
            {
                // Mark the minor as being in "open flux".  This means we are in
                // a state where the media object has been closed but the device
                // node is still open; we must reject all future accesses to the
                // device node until it is closed.  Note that we do this both on
                // success and failure of the ejection call.

                minor->isOrphaned = true;

                // Enable access to opens, closes, tables.

                gIOMediaBSDClientGlobals.unlockState();
                gIOMediaBSDClientGlobals.unlockOpen();

                // Close the media object before the ejection request is made.

                minor->media->close(client);

                // Retain the media's BSD client object, as it is about
                // to be terminated, and we still need it for the close.

                client->retain();

                // Eject the media from the drive.

                status = driver->ejectMedia();
                error  = driver->errnoFromReturn(status);

                // Close the block storage driver.

                driver->close(client);

                // Release the media's BSD client object.

                client->release();
            }
            else
            {
                error = EBUSY;

                // Enable access to opens, closes, tables.

                gIOMediaBSDClientGlobals.unlockState();
                gIOMediaBSDClientGlobals.unlockOpen();
            }

        } break;

        case DKIOCFORMAT:                            // (dk_format_capacity_t *)
        {
            //
            // This ioctl asks that the media object be formatted.
            //

            IOMediaBSDClient *     client;
            IOBlockStorageDriver * driver;
            MinorTable *           minors;
            dk_format_capacity_t * request;
            IOReturn               status;

            client  = minor->client;
            driver  = (IOBlockStorageDriver *) minor->media->getProvider();
            driver  = OSDynamicCast(IOBlockStorageDriver, driver);
            minors  = gIOMediaBSDClientGlobals.getMinors();
            request = (dk_format_capacity_t *) data;

            if ( DKIOC_IS_RESERVED(data, 0xF000) )  { error = EINVAL;  break; }

            // Determine whether this media has an IOBlockStorageDriver parent.

            if ( driver == 0 )  { error = ENOTTY;  break; }

            // Disable access to opens, closes, tables.

            gIOMediaBSDClientGlobals.lockOpen();
            gIOMediaBSDClientGlobals.lockState();

            // Determine whether there are other opens on the device nodes that
            // are associated with this anchor -- the one valid open is the one
            // that issued the format.  If all is well, we then attempt to open
            // the block storage driver to make the formatting request.

            if ( minors->getOpenCountForAnchorID(minor->anchorID) == 1 &&
                 driver->open(client, 0, kIOStorageAccessReaderWriter) )
            {
                UInt64 capacity = request->blockCount * request->blockSize;

                // Mark the minor as being in "open flux".  This means we are in
                // a state where the media object has been closed but the device
                // node is still open; we must reject all future accesses to the
                // device node until it is closed.  Note that we do this both on
                // success and failure of the formatting call.

                minor->isOrphaned = true;

                // Enable access to opens, closes, tables.

                gIOMediaBSDClientGlobals.unlockState();
                gIOMediaBSDClientGlobals.unlockOpen();

                // Close the media object before the formatting request is made.

                minor->media->close(client);

                // Retain the media's BSD client object, as it is about
                // to be terminated, and we still need it for the close.

                client->retain();

                // Format the media in the drive.

                status = driver->formatMedia(capacity);
                error  = driver->errnoFromReturn(status);

                // Wait until I/O Kit has finished to attempt to match storage
                // drivers, since the media object will have been re-published.
                // This shall ensure the new IOMediaBSDClient reconnects prior
                // to our return from DKIOCFORMAT.  Note that we still recover
                // correctly in case the media object doesn't get re-published,
                // as though an ejection had taken place.

                driver->waitQuiet();

                // Close the block storage driver.

                driver->close(client);

                // Release the media's BSD client object.

                client->release();
            }
            else
            {
                error = EBUSY;

                // Enable access to opens, closes, tables.

                gIOMediaBSDClientGlobals.unlockState();
                gIOMediaBSDClientGlobals.unlockOpen();
            }

        } break;

        case DKIOCGETFORMATCAPACITIES:             // (dk_format_capacities_t *)
        {
            //
            // This ioctl returns the feasible format capacities for this media
            // object.
            //

            UInt64                   blockSize;
            UInt64 *                 capacities;
            UInt32                   capacitiesCount;
            UInt32                   capacitiesMaxCount;
            IOBlockStorageDriver *   driver;
            dk_format_capacities_t * request;

            driver  = (IOBlockStorageDriver *) minor->media->getProvider();
            driver  = OSDynamicCast(IOBlockStorageDriver, driver);
            request = (dk_format_capacities_t *) data;

            if ( DKIOC_IS_RESERVED(data, 0xFF00) )  { error = EINVAL;  break; }

            // Determine whether this media has an IOBlockStorageDriver parent.

            if ( driver == 0 )  { error = ENOTTY;  break; }

            // Obtain the format capacities list from the block storage driver.

            capacitiesCount    = request->capacitiesCount;
            capacitiesMaxCount = driver->getFormatCapacities(NULL, 0);

            if ( capacitiesCount )
            {
                if ( request->capacities == NULL )  { error = EINVAL;  break; }

                capacitiesCount = min(capacitiesCount, capacitiesMaxCount);
                capacities      = IONew(UInt64, capacitiesCount);

                if ( capacities == 0 )  { error = ENOMEM;  break; }

                driver->getFormatCapacities(capacities, capacitiesCount);

                blockSize = minor->media->getPreferredBlockSize();
                if ( blockSize == 0 )  blockSize = DEV_BSIZE;

                // Construct the format capacities list for client consumption.

                for ( UInt32 index = 0; index < capacitiesCount; index++ )
                {
                    dk_format_capacity_t capacity = { 0 };

                    capacity.blockCount = capacities[index] / blockSize;
                    capacity.blockSize  = blockSize;

                    error = copyout( /* kaddr */ &capacity,
                                     /* uaddr */ request->capacities + index,
                                     /* len   */ sizeof(dk_format_capacity_t) );
                    if ( error )  break; 
                }

                IODelete(capacities, UInt64, capacitiesCount);

                if ( capacitiesCount < capacitiesMaxCount )  { error = E2BIG; }
            }

            request->capacitiesCount = capacitiesMaxCount;

        } break;

        case DKIOCSYNCHRONIZECACHE:                                    // (void)
        {
            //
            // This ioctl asks that the media object be flushed onto the device.
            //

            IOReturn status;

            // Flush the media onto the drive.

            status = minor->media->synchronizeCache(minor->client);
            error  = minor->media->errnoFromReturn(status);

        } break;

        case DKIOCGETBSDUNIT:                                   // (u_int32_t *)
        {
            //
            // This ioctl returns the BSD unit of the media object.
            //

            OSNumber * number = OSDynamicCast(
                         /* class  */ OSNumber,
                         /* object */ minor->media->getProperty(
                                 /* key   */ kIOBSDUnitKey ) );
            if ( number )
                *(u_int32_t *)data = number->unsigned32BitValue();
            else
                *(u_int32_t *)data = 0;

        } break;

        default:
        {
            //
            // Call the foreign ioctl handler for all other ioctls.
            //

            error = minor->client->ioctl(dev, cmd, data, f, p);

        } break;
    }

    return error;                                       // (return error status)
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

int dkioctl_bdev(dev_t dev, u_long cmd, caddr_t data, int f, struct proc * p)
{
    //
    // dkioctl_bdev performs operations other than a read or write, specific to
    // the block device.
    //

    int         error = 0;
    MinorSlot * minor = gIOMediaBSDClientGlobals.getMinor(minor(dev));

    if ( minor->isOrphaned )  return EBADF;               // (is minor in flux?)

    //
    // Process the ioctl.
    //

    switch ( cmd )
    {
        case DKIOCGETBLOCKSIZE:                                 // (u_int32_t *)
        {
            //
            // This ioctl returns the preferred (or overrided) block size of the
            // media object.
            //

            *(u_int32_t *)data = minor->bdevBlockSize;

        } break;

        case DKIOCSETBLOCKSIZE:                                 // (u_int32_t *)
        {
            //
            // This ioctl overrides the block size for the media object, for the
            // duration of all block device opens at this minor.
            //

            if ( *(u_int32_t *)data > 0 )
                minor->bdevBlockSize = (UInt64) (*(u_int32_t *)data);
            else
                error = EINVAL;

        } break;

        case DKIOCGETBLOCKCOUNT32:                              // (u_int32_t *)
        {
            //
            // This ioctl returns the size of the media object in blocks.  The
            // implied block size is returned by DKIOCGETBLOCKSIZE.
            //

            if ( minor->bdevBlockSize )
                *(u_int32_t *)data = ( minor->media->getSize() /
                                       minor->bdevBlockSize    );
            else
                *(u_int32_t *)data = 0;

        } break;

        case DKIOCGETBLOCKCOUNT:                                // (u_int64_t *)
        {
            //
            // This ioctl returns the size of the media object in blocks.  The
            // implied block size is returned by DKIOCGETBLOCKSIZE.
            //

            if ( minor->bdevBlockSize )
                *(u_int64_t *)data = ( minor->media->getSize() /
                                       minor->bdevBlockSize    );
            else
                *(u_int64_t *)data = 0;

        } break;

        default:
        {
            //
            // Call the common ioctl handler for all other ioctls.
            //

            error = dkioctl(dev, cmd, data, f, p);

        } break;
    }

    return error;                                       // (return error status)
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

int dksize(dev_t dev)
{
    //
    // dksize returns the block size of the media.
    //
    // This is a departure from BSD 4.4's definition of this function, that is,
    // it will not return the size of the disk partition, as would be expected
    // in a BSD 4.4 implementation.
    //

    MinorSlot * minor = gIOMediaBSDClientGlobals.getMinor(minor(dev));

    if ( minor->isOrphaned )  return 0;                   // (is minor in flux?)

    return (int) minor->bdevBlockSize;                    // (return block size)
}

// =============================================================================
// Support For BSD Functions

inline task_t get_user_task(void)
{
    task_t	utask;

    utask = get_aiotask();
    if (utask == TASK_NULL) 
	return current_task();
    else
    	return utask;
}

inline dev_t DKR_GET_DEV(dkr_t dkr, dkrtype_t dkrtype)
{
    return (dkrtype == DKRTYPE_BUF)
           ? ((struct buf *)dkr)->b_dev
           : ((struct dio *)dkr)->dev;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

inline UInt64 DKR_GET_BYTE_COUNT(dkr_t dkr, dkrtype_t dkrtype)
{
    return (dkrtype == DKRTYPE_BUF)
           ? ((struct buf *)dkr)->b_bcount
           : ((struct dio *)dkr)->uio->uio_resid;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

inline UInt64 DKR_GET_BYTE_START(dkr_t dkr, dkrtype_t dkrtype)
{
    if (dkrtype == DKRTYPE_BUF)
    {
        struct buf * bp = (struct buf *)dkr;
        MinorSlot *  minor;

        minor = gIOMediaBSDClientGlobals.getMinor(minor(bp->b_dev));

        return (UInt64)((unsigned)bp->b_blkno) * minor->bdevBlockSize;
    }
    return ((struct dio *)dkr)->uio->uio_offset;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

inline bool DKR_IS_READ(dkr_t dkr, dkrtype_t dkrtype)
{
    return (dkrtype == DKRTYPE_BUF)
           ? ((((struct buf *)dkr)->b_flags & B_READ) == B_READ)
           : ((((struct dio *)dkr)->uio->uio_rw) == UIO_READ);
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

inline bool DKR_IS_ASYNCHRONOUS(dkr_t dkr, dkrtype_t dkrtype)
{
    return (dkrtype == DKRTYPE_BUF)
           ? true
           : false;
}


// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

inline bool DKR_IS_RAW(dkr_t dkr, dkrtype_t dkrtype)
{
    return (dkrtype == DKRTYPE_BUF)
           ? false
           : true;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

inline void DKR_SET_BYTE_COUNT(dkr_t dkr, dkrtype_t dkrtype, UInt64 bcount)
{
    if (dkrtype == DKRTYPE_BUF)
        ((struct buf *)dkr)->b_resid = ((struct buf *)dkr)->b_bcount - bcount;
    else
        ((struct dio *)dkr)->uio->uio_resid -= bcount;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

inline void DKR_RUN_COMPLETION(dkr_t dkr, dkrtype_t dkrtype, IOReturn status)
{
    if (dkrtype == DKRTYPE_BUF)
    {
        struct buf * bp = (struct buf *)dkr;
        MinorSlot *  minor;

        minor = gIOMediaBSDClientGlobals.getMinor(minor(bp->b_dev));

        bp->b_error  = minor->media->errnoFromReturn(status);        // (error?)
        bp->b_flags |= (status != kIOReturnSuccess) ? B_ERROR : 0;   // (error?)
        biodone(bp);                                       // (complete request)
    }
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

inline IOMemoryDescriptor * DKR_GET_BUFFER(dkr_t dkr, dkrtype_t dkrtype)
{
    if (dkrtype == DKRTYPE_BUF)
    {
        struct buf * bp = (struct buf *)dkr;

        if ( (bp->b_flags & B_VECTORLIST) )
        {
            IOOptionBits mdopts = kIOMemoryTypeUPL | kIOMemoryAsReference;

            mdopts |= (bp->b_flags & B_READ) ? kIODirectionIn : kIODirectionOut;

            return IOMemoryDescriptor::withOptions(          // (multiple-range)
                bp->b_pagelist,
                bp->b_bcount,
                bp->b_uploffset,
                0,
                mdopts );
        }

        return IOMemoryDescriptor::withAddress(                // (single-range)
          (vm_address_t) bp->b_data,
          (vm_size_t)    bp->b_bcount,
          (bp->b_flags & B_READ) ? kIODirectionIn : kIODirectionOut,
          (bp->b_flags & B_PHYS) ? get_user_task() : kernel_task);
    }
    else
    {
        struct uio * uio = ((struct dio *)dkr)->uio;

        return IOMemoryDescriptor::withRanges(               // (multiple-range)
        (IOVirtualRange *) uio->uio_iov,
        (UInt32)           uio->uio_iovcnt,
        (uio->uio_rw     == UIO_READ    ) ? kIODirectionIn  : kIODirectionOut,
        (uio->uio_segflg != UIO_SYSSPACE) ? get_user_task() : kernel_task,
        true );
    }
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

inline void * DKR_GET_DRIVER_DATA(dkr_t dkr, dkrtype_t dkrtype)
{
    return (dkrtype == DKRTYPE_BUF)
           ? ((struct buf *)dkr)->b_drvdata
           : ((struct dio *)dkr)->drvdata;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

inline void DKR_SET_DRIVER_DATA(dkr_t dkr, dkrtype_t dkrtype, void * drvdata)
{
    if (dkrtype == DKRTYPE_BUF)
        ((struct buf *)dkr)->b_drvdata = drvdata;
    else
        ((struct dio *)dkr)->drvdata = drvdata;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

int dkreadwrite(dkr_t dkr, dkrtype_t dkrtype)
{
    //
    // dkreadwrite performs a read or write operation.
    //

    IOMemoryDescriptor * buffer;
    register UInt64      byteCount;
    register UInt64      byteStart;
    UInt64               mediaSize;
    MinorSlot *          minor;
    IOReturn             status;
    boolean_t funnel_state = FALSE;

    if (dkrtype == DKRTYPE_BUF)
        funnel_state = thread_funnel_set(kernel_flock, FALSE);

    DKR_SET_DRIVER_DATA(dkr, dkrtype, 0);

    minor = gIOMediaBSDClientGlobals.getMinor(minor(DKR_GET_DEV(dkr, dkrtype)));

    if ( minor->isOrphaned )                              // (is minor in flux?)
    {
        status = kIOReturnNoMedia;
        goto dkreadwriteErr;
    }

    if ( minor->media->isFormatted() == false )       // (is media unformatted?)
    {
        status = kIOReturnUnformattedMedia;
        goto dkreadwriteErr;
    }

    byteCount = DKR_GET_BYTE_COUNT(dkr, dkrtype);            // (get byte count)
    byteStart = DKR_GET_BYTE_START(dkr, dkrtype);            // (get byte start)
    mediaSize = minor->media->getSize();                     // (get media size)

    //
    // Reads that start at (or perhaps past) the end-of-media are not considered
    // errors, even though no data is transferred, while writes at (or past) the
    // end-of-media do indeed return errors under BSD semantics.
    // 

    if ( byteStart >= mediaSize )     // (is start at or past the end-of-media?)
    {
        status = DKR_IS_READ(dkr,dkrtype) ? kIOReturnSuccess : kIOReturnIOError;
        goto dkreadwriteErr;
    }

    //
    // Reads and writes, via the character device, that do not start or end on a
    // media block boundary are considered errors under BSD semantics.
    //

    if ( DKR_IS_RAW(dkr, dkrtype) )
    {
        UInt64 mediaBlockSize = minor->media->getPreferredBlockSize();

        if ( (byteStart % mediaBlockSize) || (byteCount % mediaBlockSize) )
        {
            status = kIOReturnNotAligned;
            goto dkreadwriteErr;
        }
    }

    //
    // Build a descriptor which describes the buffer involved in the transfer.
    //

    buffer = DKR_GET_BUFFER(dkr, dkrtype);

    if ( buffer == 0 )                                           // (no buffer?)
    {
        status = kIOReturnNoMemory;
        goto dkreadwriteErr;
    }

    //
    // Reads and writes that extend beyond the end-of-media are not considered
    // errors under BSD semantics.  We are to transfer as many bytes as can be
    // read or written from the medium and return no error.  This differs from
    // IOMedia semantics which is to fail the entire request without copying a
    // single byte should it include something past the end-of-media.  We must
    // adapt the IOMedia semantics to look like BSD semantics here.
    // 
    // Clip the transfer buffer should this be a short read or write request.
    //

    if ( byteCount > mediaSize - byteStart )           // (clip at end-of-media)
    {
        IOMemoryDescriptor * originalBuffer = buffer;

        buffer = IOMemoryDescriptor::withSubRange(
                           /* descriptor    */ originalBuffer,
                           /* withOffset    */ 0,
                           /* withLength    */ mediaSize - byteStart,
                           /* withDirection */ originalBuffer->getDirection() );

        originalBuffer->release();   // (either retained above or about to fail)

        if ( buffer == 0 )                                      // (no buffer?)
        {
            status = kIOReturnNoMemory;
            goto dkreadwriteErr;
        }
    }

    //
    // Prepare the transfer.
    //

    if ( buffer->prepare() != kIOReturnSuccess )         // (prepare the buffer)
    {
        buffer->release();
        status = kIOReturnVMError;            // (wiring or permissions failure)
        goto dkreadwriteErr;
    }

    //
    // Execute the transfer.
    //

    DKR_SET_DRIVER_DATA(dkr, dkrtype, buffer);

    if ( DKR_IS_ASYNCHRONOUS(dkr, dkrtype) )       // (an asynchronous request?)
    {
        IOStorageCompletion completion;

        completion.target    = dkr;
        completion.action    = dkreadwritecompletion;
        completion.parameter = (void *) dkrtype;

        if ( DKR_IS_READ(dkr, dkrtype) )                            // (a read?)
        {
            minor->media->read(  /* client     */ minor->client,
                                 /* byteStart  */ byteStart,
                                 /* buffer     */ buffer,
                                 /* completion */ completion );          // (go)
        }
        else                                                       // (a write?)
        {
            minor->media->write( /* client     */ minor->client,
                                 /* byteStart  */ byteStart,
                                 /* buffer     */ buffer,
                                 /* completion */ completion );          // (go)
        }

        status = kIOReturnSuccess;
    }
    else                                             // (a synchronous request?)
    {
        if ( DKR_IS_READ(dkr, dkrtype) )                            // (a read?)
        {
            status = minor->media->read(
                                 /* client          */ minor->client,
                                 /* byteStart       */ byteStart,
                                 /* buffer          */ buffer,
                                 /* actualByteCount */ &byteCount );     // (go)
        }
        else                                                       // (a write?)
        {
            status = minor->media->write(
                                 /* client          */ minor->client,
                                 /* byteStart       */ byteStart,
                                 /* buffer          */ buffer,
                                 /* actualByteCount */ &byteCount );     // (go)
        }

        dkreadwritecompletion(dkr, (void *)dkrtype, status, byteCount);
    }

    if (funnel_state == TRUE)
        funnel_state = thread_funnel_set(kernel_flock, TRUE);

    return minor->media->errnoFromReturn(status);       // (return error status)

dkreadwriteErr:

    if (funnel_state == TRUE)
        funnel_state = thread_funnel_set(kernel_flock, TRUE);

    dkreadwritecompletion(dkr, (void *)dkrtype, status, 0);

    return minor->media->errnoFromReturn(status);       // (return error status)
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

void dkreadwritecompletion( void *   target,
                            void *   parameter,
                            IOReturn status,
                            UInt64   actualByteCount )
{
    //
    // dkreadwritecompletion cleans up after a read or write operation.
    //

    dkr_t       dkr      = (dkr_t) target;
    dkrtype_t   dkrtype  = (dkrtype_t) (int) parameter;
    dev_t       dev      = DKR_GET_DEV(dkr, dkrtype);
    void *      drvdata  = DKR_GET_DRIVER_DATA(dkr, dkrtype);
    MinorSlot * minor    = gIOMediaBSDClientGlobals.getMinor(minor(dev));

    if ( drvdata )                                            // (has a buffer?)
    {
        IOMemoryDescriptor * buffer = (IOMemoryDescriptor *) drvdata;

        buffer->complete();                             // (complete the buffer)
        buffer->release();                 // (release our retain on the buffer)
    }

    if ( status != kIOReturnSuccess )                         // (has an error?)
    {
        IOLog("%s: %s.\n", minor->name, minor->media->stringFromReturn(status));
    }

    if ( DKR_IS_ASYNCHRONOUS(dkr, dkrtype) )       // (an asynchronous request?)
    {
        boolean_t funnel_state;

        funnel_state = thread_funnel_set(kernel_flock, TRUE);

        DKR_SET_BYTE_COUNT(dkr, dkrtype, actualByteCount);   // (set byte count)
        DKR_RUN_COMPLETION(dkr, dkrtype, status);            // (run completion)

        thread_funnel_set(kernel_flock, funnel_state);
    }
    else
    {
        DKR_SET_BYTE_COUNT(dkr, dkrtype, actualByteCount);   // (set byte count)
    }
}

// =============================================================================
// AnchorTable Class

AnchorTable::AnchorTable()
{
    //
    // Initialize this object's minimal state.
    //

    _table      = 0;
    _tableCount = 0;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

AnchorTable::~AnchorTable()
{
    //
    // Free all of this object's outstanding resources.
    //

    for ( UInt32 anchorID = 0; anchorID < _tableCount; anchorID++ )
        if ( _table[anchorID].isAssigned )  remove(anchorID);

    if ( _table )  IODelete(_table, AnchorSlot, _tableCount);
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

UInt32 AnchorTable::insert(IOService * anchor, void * key)
{
    //
    // This method inserts the specified anchor into an unassigned slot in the
    // anchor table and returns its ID (or kInvalidAnchorID on a failure).
    //
    // Note that the anchor is transparently removed from the table should the
    // anchor terminate (or it is at least marked obsolete,  should references
    // to the anchor still exist in the minor table).
    //

    UInt32       anchorID;
    IONotifier * notifier;

    // Search for an unassigned slot in the anchor table.

    for ( anchorID = 0; anchorID < _tableCount; anchorID++ )
        if ( _table[anchorID].isAssigned == false )  break;

    // Was an unassigned slot found?  If not, grow the table.

    if ( anchorID == _tableCount )
    {
        AnchorSlot * newTable;
        UInt32       newTableCount;

        // We must expand the anchor table since no more slots are available.

        if ( _tableCount >= kAnchorsMaxCount )  return kInvalidAnchorID;

        newTableCount = min(kAnchorsAddCount + _tableCount, kAnchorsMaxCount);
        newTable      = IONew(AnchorSlot, newTableCount);

        if ( newTable == 0 )  return kInvalidAnchorID;

        bzero(newTable, newTableCount * sizeof(AnchorSlot));

        // Copy over the old table's entries, then free the old table.

        if ( _table )
        {
            bcopy(_table, newTable, _tableCount * sizeof(AnchorSlot));
            IODelete(_table, AnchorSlot, _tableCount);
        }
    
        // Obtain the next unassigned index (simple since we know the size of
        // the old table),  then update our instance variables to reflect the
        // new tables.

        anchorID    = _tableCount;
        _table      = newTable;
        _tableCount = newTableCount;
    }

    // Create a notification handler for the anchor's termination (post-stop);
    // the handler will remove the anchor transparently from the table if the
    // anchor terminates (or at least marks it obsolete, if references to the
    // anchor still exist in the minor table).

    notifier = anchor->registerInterest(
                          /* type        */ gIOGeneralInterest,
                          /* action      */ anchorWasNotified,
                          /* target      */ this,
                          /* parameter   */ 0 );
  
    if ( notifier == 0 )  return kInvalidAnchorID;

    // Zero the new slot, fill it in, and retain the anchor object.

    bzero(&_table[anchorID], sizeof(AnchorSlot)); // (zero slot)

    _table[anchorID].isAssigned = true;           // (fill in slot)
    _table[anchorID].isObsolete = false;
    _table[anchorID].anchor     = anchor;
    _table[anchorID].key        = key;
    _table[anchorID].notifier   = notifier;

    _table[anchorID].anchor->retain();            // (retain anchor)

    return anchorID;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

void AnchorTable::remove(UInt32 anchorID)
{
    //
    // This method removes the specified anchor from the anchor table.
    //

    assert(anchorID < _tableCount);
    assert(_table[anchorID].isAssigned);

    // Release the resources retained in the anchor slot and zero it.

    _table[anchorID].notifier->remove();
    _table[anchorID].anchor->release();           // (release anchor)

    bzero(&_table[anchorID], sizeof(AnchorSlot)); // (zero slot)
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

void AnchorTable::obsolete(UInt32 anchorID)
{
    //
    // This method obsoletes the specified anchor, that is, the slot is marked
    // as obsolete and will be removed later via the minor table remove method
    // once it detects references to the anchor ID drop to 0.   Once obsoleted,
    // the anchor can be considered to be removed, since it will not appear in
    // locate searches, even though behind the scenes it still occupies a slot.
    //

    assert(anchorID < _tableCount);
    assert(_table[anchorID].isAssigned);

    // Mark the anchor as obsolete so that it can be removed from the table as
    // soon as all its references go away (minor table's responsibility).

    _table[anchorID].isObsolete = true;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

UInt32 AnchorTable::locate(IOService * anchor)
{
    //
    // This method searches for the specified anchor in the anchor table and
    // returns its ID (or kInvalidAnchorID on a failure).  It would find the
    // first occurrence of the anchor in case multiple entries with the same
    // anchor object exist.  It ignores slots marked as obsolete.
    //

    for (UInt32 anchorID = 0; anchorID < _tableCount; anchorID++)
    {
        if ( _table[anchorID].isAssigned != false  &&
             _table[anchorID].isObsolete == false  &&
             _table[anchorID].anchor     == anchor )  return anchorID;
    }

    return kInvalidAnchorID;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

UInt32 AnchorTable::locate(IOService * anchor, void * key)
{
    //
    // This method searches for the specified anchor and key pair in the anchor
    // table and returns its ID (or kInvalidAnchorID on a failure).  It ignores
    // slots marked as obsolete.
    //

    for (UInt32 anchorID = 0; anchorID < _tableCount; anchorID++)
    {
        if ( _table[anchorID].isAssigned != false  &&
             _table[anchorID].isObsolete == false  &&
             _table[anchorID].anchor     == anchor &&
             _table[anchorID].key        == key    )  return anchorID;
    }

    return kInvalidAnchorID;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

UInt32 AnchorTable::update(IOService * anchor, void * key)
{
    //
    // This method searches for the specified anchor in the anchor table and
    // updates its key value if no references to it exist in the minor table
    // or if the references in the minor table are all obsolete.  It returns
    // the updated anchor ID (or kInvalidAnchorID on a failure).  It ignores
    // slots marked as obsolete.
    //

    MinorTable * minors = gIOMediaBSDClientGlobals.getMinors();

    for (UInt32 anchorID = 0; anchorID < _tableCount; anchorID++)
    {
        if ( _table[anchorID].isAssigned != false  &&
             _table[anchorID].isObsolete == false  &&
             _table[anchorID].anchor     == anchor )
        {
            if ( minors->hasReferencesToAnchorID(anchorID, true) == false )
            {
                _table[anchorID].key = key;
                return anchorID;
            }
        }
    }

    return kInvalidAnchorID;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

bool AnchorTable::isObsolete(UInt32 anchorID)
{
    //
    // Determine whether the specified anchor ID is marked as obsolete.
    //

    assert(anchorID < _tableCount);
    assert(_table[anchorID].isAssigned);

    return _table[anchorID].isObsolete ? true : false;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

IOReturn AnchorTable::anchorWasNotified( void *      /* target */,
                                         void *      /* parameter */,
                                         UInt32      messageType,
                                         IOService * anchor,
                                         void *      /* messageArgument */,
                                         vm_size_t   /* messageArgumentSize */ )
{
    //
    // Notification handler for anchors.
    //

    AnchorTable * anchors = gIOMediaBSDClientGlobals.getAnchors();
    UInt32        anchorID;
    MinorTable *  minors  = gIOMediaBSDClientGlobals.getMinors();

    // Determine whether this is a termination notification (post-stop).

    if ( messageType != kIOMessageServiceIsTerminated )
        return kIOReturnSuccess;

    // Disable access to tables.

    gIOMediaBSDClientGlobals.lockState();

    // Determine whether this anchor is in the anchor table (obsolete occurences
    // are skipped in the search, as appropriate, since those anchor IDs will be
    // removed as it is).

    while ( (anchorID = anchors->locate(anchor)) != kInvalidAnchorID )
    {
        // Determine whether this anchor still has references from the minor
        // table.  If it does, we mark the the anchor as obsolete so that it
        // will be removed later, once references to it go to zero (which is
        // handled by MinorTable::remove).

        if ( minors->hasReferencesToAnchorID(anchorID, false) )
            anchors->obsolete(anchorID);
        else
            anchors->remove(anchorID);
    }

    // Enable access to tables.

    gIOMediaBSDClientGlobals.unlockState();   

    return kIOReturnSuccess;
}

// =============================================================================
// MinorTable Class

MinorTable::MinorTable()
{
    //
    // Initialize this object's minimal state.
    //

    _table.buckets = IONew(MinorSlot *, kMinorsBucketCount);
    _tableCount    = 0;

    if ( _table.buckets )
        bzero(_table.buckets, kMinorsBucketCount * sizeof(MinorSlot *));
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

MinorTable::~MinorTable()
{
    //
    // Free all of this object's outstanding resources.
    //

    for ( UInt32 minorID = 0; minorID < _tableCount; minorID++ )
        if ( _table[minorID].isAssigned )  remove(minorID);

    if ( _table.buckets )
    {
        for ( UInt32 bucketID = 0; _table.buckets[bucketID]; bucketID++ )
            IODelete(_table.buckets[bucketID], MinorSlot, kMinorsAddCount);

        IODelete(_table.buckets, MinorSlot *, kMinorsBucketCount);
    }
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

UInt32 MinorTable::insert( IOMedia *          media,
                           UInt32             anchorID,
                           IOMediaBSDClient * client,
                           char *             slicePath )
{
    //
    // This method inserts the specified media/anchorID pair into an unassigned
    // slot in the minor table and returns its ID (or kInvalidMinorID on error).
    //
    // Note that the bdev and cdev nodes are published as a result of this call,
    // with the name "[r]disk<anchorID><slicePath>".  For instance, "disk2s3s1"
    // for an anchorID of 2 and slicePath of "s3s1".
    //

    void *       bdevNode;
    void *       cdevNode;
    UInt32       minorID;
    char *       minorName;
    UInt32       minorNameSize;

    if ( _table.buckets == 0 )  return kInvalidMinorID;

    // Search for an unassigned slot in the minor table.

    for ( minorID = 0; minorID < _tableCount; minorID++ )
        if ( _table[minorID].isAssigned == false )  break;

    // Was an unassigned slot found?  If not, grow the table.

    if ( minorID == _tableCount )
    {
        UInt32 bucketID = _tableCount / kMinorsAddCount;

        // We must expand the minor table since no more slots are available.

        if ( bucketID >= kMinorsBucketCount )  return kInvalidMinorID;

        _table.buckets[bucketID] = IONew(MinorSlot, kMinorsAddCount);

        if ( _table.buckets[bucketID] == 0 )  return kInvalidMinorID;

        bzero(_table.buckets[bucketID], kMinorsAddCount * sizeof(MinorSlot));

        _tableCount += kMinorsAddCount;
    }

    // Create a buffer large enough to hold the full name of the minor.

    minorNameSize = strlen("disk#");
    for (unsigned temp = anchorID; temp >= 10; temp /= 10)  minorNameSize++;
    minorNameSize += strlen(slicePath);
    minorNameSize += 1;
    minorName = IONew(char, minorNameSize);

    // Create a block and character device node in BSD for this media.

    bdevNode = devfs_make_node( /* dev        */ makedev(kMajor, minorID),
                                /* type       */ DEVFS_BLOCK, 
                                /* owner      */ UID_ROOT,
                                /* group      */ GID_OPERATOR, 
                                /* permission */ media->isWritable()?0640:0440, 
                                /* name (fmt) */ "disk%d%s",
                                /* name (arg) */ anchorID,
                                /* name (arg) */ slicePath );

    cdevNode = devfs_make_node( /* dev        */ makedev(kMajor, minorID),
                                /* type       */ DEVFS_CHAR, 
                                /* owner      */ UID_ROOT,
                                /* group      */ GID_OPERATOR, 
                                /* permission */ media->isWritable()?0640:0440,
                                /* name (fmt) */ "rdisk%d%s",
                                /* name (arg) */ anchorID,
                                /* name (arg) */ slicePath );

    if ( minorName == 0 || bdevNode == 0 || cdevNode == 0 )
    {
        if ( cdevNode )   devfs_remove(cdevNode);
        if ( bdevNode )   devfs_remove(bdevNode);
        if ( minorName )  IODelete(minorName, char, minorNameSize);

        return kInvalidMinorID;
    }

    // Construct a name for the node.

    sprintf(minorName, "disk%ld%s", anchorID, slicePath);
    assert(strlen(minorName) + 1 == minorNameSize);

    // Zero the new slot, fill it in, and retain the appropriate objects.

    bzero(&_table[minorID], sizeof(MinorSlot));    // (zero slot)

    _table[minorID].isAssigned    = true;          // (fill in slot)
    _table[minorID].isObsolete    = false;
    _table[minorID].isOrphaned    = false;
    _table[minorID].anchorID      = anchorID;
    _table[minorID].client        = client;
    _table[minorID].media         = media;
    _table[minorID].name          = minorName;
    _table[minorID].bdevBlockSize = media->getPreferredBlockSize();
    _table[minorID].bdevNode      = bdevNode;
    _table[minorID].bdevOpen      = 0;
    _table[minorID].cdevNode      = cdevNode;
    _table[minorID].cdevOpen      = 0;

    _table[minorID].client->retain();              // (retain client)
    _table[minorID].media->retain();               // (retain media)

    return minorID;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

void MinorTable::remove(UInt32 minorID)
{
    //
    // This method removes the specified minor from the minor table.
    //

    UInt32 anchorID;

    assert(minorID < _tableCount);
    assert(_table[minorID].isAssigned);

    assert(_table[minorID].isOrphaned == false);
    assert(_table[minorID].bdevOpen == 0);
    assert(_table[minorID].cdevOpen == 0);

    anchorID = _table[minorID].anchorID;

    // Release the resources retained in the minor slot and zero it.

    devfs_remove(_table[minorID].cdevNode);
    devfs_remove(_table[minorID].bdevNode);
    IODelete(_table[minorID].name, char, strlen(_table[minorID].name) + 1);
    _table[minorID].client->release();             // (release client)
    _table[minorID].media->release();              // (release media)

    bzero(&_table[minorID], sizeof(MinorSlot));    // (zero slot)

    // Determine whether the associated anchor ID is marked as obsolete.  If it
    // is and there are no other references to the anchor ID in the minor table,
    // we remove the anchor ID from the anchor table.

    if ( gIOMediaBSDClientGlobals.getAnchors()->isObsolete(anchorID) )
    {
        if ( hasReferencesToAnchorID(anchorID, false) == false )
            gIOMediaBSDClientGlobals.getAnchors()->remove(anchorID);
    }
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

UInt32 MinorTable::update( IOMedia *          media,
                           UInt32             anchorID,
                           IOMediaBSDClient * client,
                           char *             slicePath )
{
    //
    // This method searches for the specified anchor ID and slice path pair in
    // the minor table and updates it.  An update would be an unusual occasion
    // as new anchors are assigned when two media trees are found to share the
    // same anchor.  It would occur in one specific circumstance: on the minor
    // slot through which a DKIOCFORMAT was issued.  The minor slot would have
    // been marked in "open flux", the format would have been issued, then the
    // media objects terminated, the minor slot marked obsolete, and the media
    // objects republished.  The anchor ID would have one reference, the minor
    // slot with the DKIOCFORMAT still outstanding.  AnchorTable::update would
    // notice the one reference is orphaned and accept the reuse of the anchor
    // ID.  MinorTable::update would notice the orphaned minor slot and update
    // it with the new media object and media bsd client object, and clear its
    // obsolete state and "open flux" state, once the new media object arrives.
    //

    UInt32 minorID;
    char * minorName;
    UInt32 minorNameSize;

    // Create a buffer large enough to hold the full name of the minor.

    minorNameSize = strlen("disk#");
    for (unsigned temp = anchorID; temp >= 10; temp /= 10)  minorNameSize++;
    minorNameSize += strlen(slicePath);
    minorNameSize += 1;
    minorName = IONew(char, minorNameSize);

    if ( minorName == 0 )  return kInvalidMinorID;

    // Construct a name for the node.

    sprintf(minorName, "disk%ld%s", anchorID, slicePath);
    assert(strlen(minorName) + 1 == minorNameSize);

    // Search for an orphaned slot in the minor table with our minor name.

    for ( minorID = 0; minorID < _tableCount; minorID++ )
    {
        if ( _table[minorID].isAssigned              != false    &&
             _table[minorID].isObsolete              != false    &&
             _table[minorID].isOrphaned              != false    &&
             _table[minorID].anchorID                == anchorID &&
             strcmp(_table[minorID].name, minorName) == 0        )  break;
    }

    IODelete(minorName, char, minorNameSize);

    if ( minorID == _tableCount )  return kInvalidMinorID;

    // Update the slot and retain the appropriate objects.

    _table[minorID].client->release();             // (release client)
    _table[minorID].media->release();              // (release media)

    _table[minorID].isObsolete = false;            // (update slot)
    _table[minorID].isOrphaned = false;
    _table[minorID].client     = client;
    _table[minorID].media      = media;

    _table[minorID].client->retain();              // (retain client)
    _table[minorID].media->retain();               // (retain media)

    return minorID;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

UInt32 MinorTable::locate(IOMedia * media)
{
    //
    // This method searches for the specified media in the minor table and
    // returns its ID (or kInvalidMinorID on an error).   It ignores slots
    // marked as obsolete.
    //

    for (UInt32 minorID = 0; minorID < _tableCount; minorID++)
    {
        if ( _table[minorID].isAssigned != false &&
             _table[minorID].isObsolete == false &&
             _table[minorID].media      == media )  return minorID;
    }

    return kInvalidMinorID;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

UInt32 MinorTable::getOpenCountForAnchorID(UInt32 anchorID)
{
    //
    // This method obtains a count of opens on the minors associated with the
    // specified anchor ID.  A block device open is counted separately from a
    // character device open.
    //

    UInt32 opens = 0;

    for ( UInt32 minorID = 0; minorID < _tableCount; minorID++ )
    {
        if ( _table[minorID].isAssigned != false    &&
             _table[minorID].anchorID   == anchorID )
        {
            opens += _table[minorID].bdevOpen;
            opens += _table[minorID].cdevOpen;
        }
    }

    return opens;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

bool MinorTable::hasReferencesToAnchorID(UInt32 anchorID, bool excludeOrphans)
{
    //
    // This method determines whether there are assigned minors in the minor
    // table that refer to the specified anchor ID.  It ignores slots marked
    // as obsolete and orphaned, unless excludeOrphans is false.
    //

    for ( UInt32 minorID = 0; minorID < _tableCount; minorID++ )
    {
        if ( _table[minorID].isAssigned != false    &&
             _table[minorID].anchorID   == anchorID )
        {
            if ( excludeOrphans             == false )  return true;
            if ( _table[minorID].isObsolete == false )  return true;
            if ( _table[minorID].isOrphaned == false )  return true;
        }
    }

    return false;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

MinorSlot * MinorTable::getMinor(UInt32 minorID)
{
    //
    // Obtain the structure describing the specified minor.
    //

    if ( minorID < _tableCount && _table[minorID].isAssigned )
        return &_table[minorID];
    else
        return 0;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

void MinorTable::obsolete(UInt32 minorID)
{
    //
    // This method obsoletes the specified minor, that is, the slot is marked
    // as obsolete and will be removed later via the dkclose function once it
    // detects the last close arrive.  Once obsoleted, the minor can be cons-
    // idered to be removed, since it will not appear in locate searches.
    //

    assert(minorID < _tableCount);
    assert(_table[minorID].isAssigned);

    // Mark the minor as obsolete so that it can be removed from the table as
    // soon as the last close arrives (dkclose function's responsibility).

    _table[minorID].isObsolete = true;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

bool MinorTable::isObsolete(UInt32 minorID)
{
    //
    // Determine whether the specified minor ID is marked as obsolete.
    //

    assert(minorID < _tableCount);
    assert(_table[minorID].isAssigned);

    return _table[minorID].isObsolete ? true : false;
}

// =============================================================================
// IOMediaBSDClientGlobals Class

IOMediaBSDClientGlobals::IOMediaBSDClientGlobals()
{
    //
    // Initialize the minimal global state.
    //

    _anchors         = new AnchorTable();
    _minors          = new MinorTable();

    _bdevswInstalled = (bdevsw_add(kMajor, &bdevswFunctions) == kMajor);
    _cdevswInstalled = (cdevsw_add(kMajor, &cdevswFunctions) == kMajor);

    _openLock        = IOLockAlloc();
    _stateLock       = IOLockAlloc();
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

IOMediaBSDClientGlobals::~IOMediaBSDClientGlobals()
{
    //
    // Free all of the outstanding global resources.
    //

    if ( _openLock )         IOLockFree(_openLock);
    if ( _stateLock )        IOLockFree(_stateLock);

    if ( _cdevswInstalled )  cdevsw_remove(kMajor, &cdevswFunctions); 
    if ( _bdevswInstalled )  bdevsw_remove(kMajor, &bdevswFunctions);

    if ( _minors )           delete _minors;
    if ( _anchors )          delete _anchors;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

AnchorTable * IOMediaBSDClientGlobals::getAnchors()
{
    //
    // Obtain the table of anchors.
    //

    return _anchors;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

MinorTable * IOMediaBSDClientGlobals::getMinors()
{
    //
    // Obtain the table of minors.
    //

    return _minors;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

MinorSlot * IOMediaBSDClientGlobals::getMinor(UInt32 minorID)
{
    //
    // Obtain information for the specified minor ID.
    //

    return _minors->getMinor(minorID);
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

bool IOMediaBSDClientGlobals::isValid()
{
    //
    // Determine whether the minimal global state has been initialized.
    //

    return ( _anchors         ) &&
           ( _minors          ) &&
           ( _bdevswInstalled ) &&
           ( _cdevswInstalled ) &&
           ( _openLock        ) &&
           ( _stateLock       );
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

void IOMediaBSDClientGlobals::lockOpen()
{
    //
    // Disable access to the opens and closes.
    //

    IOLockLock(_openLock);
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

void IOMediaBSDClientGlobals::unlockOpen()
{
    //
    // Enable access to the opens and closes.
    //

    IOLockUnlock(_openLock);
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

void IOMediaBSDClientGlobals::lockState()
{
    //
    // Disable access to the global state.
    //

    IOLockLock(_stateLock);
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

void IOMediaBSDClientGlobals::unlockState()
{
    //
    // Enable access to the global state.
    //

    IOLockUnlock(_stateLock);
}
