sp.c revision 2fb876ae0cefcbd01f8d8490242aa4501caddbc3
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2006 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* Soft partitioning metadevice driver (md_sp).
*
* This file contains the primary operations of the soft partitioning
* metadevice driver. This includes all routines for normal operation
* metadevice operations vector (md_ops_t). This driver is loosely
* based on the stripe driver (md_stripe).
*
* All metadevice administration is done through the use of ioctl's.
* As such, all administrative routines appear in sp_ioctl.c.
*
* Soft partitions are represented both in-core and in the metadb with a
* unit structure. The soft partition-specific information in the unit
* structure includes the following information:
* - Device information (md_dev64_t & md key) about the device on which
* the soft partition is built.
* - Soft partition status information.
* - The size of the soft partition and number of extents used to
* make up that size.
* mappings and lengths for each extent.
*
* Typical soft partition operation proceeds as follows:
* - The unit structure is fetched from the metadb and placed into
* an in-core array (as with other metadevices). This operation
* is performed via sp_build_incore( ) and takes place during
* "snarfing" (when all metadevices are brought in-core at
* once) and when a new soft partition is created.
* - A soft partition is opened via sp_open( ). At open time the
* the soft partition unit structure is verified with the soft
* partition on-disk structures. Additionally, the soft partition
* status is checked (only soft partitions in the OK state may be
* opened).
* - Soft partition I/O is performed via sp_strategy( ) which relies on
* a support routine, sp_mapbuf( ), to do most of the work.
* sp_mapbuf( ) maps a buffer to a particular extent via a binary
* search of the extent array in the soft partition unit structure.
* Once a translation has been performed, the I/O is passed down
* to the next layer, which may be another metadevice or a physical
* disk. Since a soft partition may contain multiple, non-contiguous
* extents, a single I/O may have to be fragmented.
* - Soft partitions are closed using sp_close.
*
*/
#include <sys/sysmacros.h>
#ifndef lint
char _depends_on[] = "drv/md";
#endif
extern int md_status;
extern mdq_anchor_t md_done_daemon;
extern mdq_anchor_t md_sp_daemon;
extern kcondvar_t md_cv;
extern md_krwlock_t md_unit_array_rw;
static void sp_send_stat_ok(mp_unit_t *);
static void sp_send_stat_err(mp_unit_t *);
/*
* FUNCTION: sp_parent_constructor()
* INPUT: none.
* OUTPUT: ps - parent save structure initialized.
* RETURNS: void * - ptr to initialized parent save structure.
* PURPOSE: initialize parent save structure.
*/
/*ARGSUSED1*/
static int
{
return (0);
}
static void
{
}
/*ARGSUSED1*/
static void
sp_parent_destructor(void *p, void *d)
{
}
/*
* FUNCTION: sp_child_constructor()
* INPUT: none.
* OUTPUT: cs - child save structure initialized.
* RETURNS: void * - ptr to initialized child save structure.
* PURPOSE: initialize child save structure.
*/
/*ARGSUSED1*/
static int
{
return (0);
}
static void
{
}
/*ARGSUSED1*/
static void
sp_child_destructor(void *p, void *d)
{
}
/*
* FUNCTION: sp_run_queue()
* INPUT: none.
* OUTPUT: none.
* RETURNS: void.
* PURPOSE: run the md_daemon to clean up memory pool.
*/
/*ARGSUSED*/
static void
sp_run_queue(void *d)
{
if (!(md_status & MD_GBL_DAEMONS_LIVE))
}
/*
* FUNCTION: sp_build_incore()
* INPUT: p - ptr to unit structure.
* snarfing - flag to tell us we are snarfing.
* OUTPUT: non.
* RETURNS: int - 0 (always).
* PURPOSE: place unit structure into in-core unit array (keyed from
* minor number).
*/
int
sp_build_incore(void *p, int snarfing)
{
return (0);
if (snarfing) {
/*
* if we are snarfing, we get the device information
* from the metadb record (using the metadb key for
* that device).
*/
}
/* place unit in in-core array */
return (0);
}
/*
* FUNCTION: reset_sp()
* removing - flag to tell us if we are removing
* permanently or just reseting in-core
* structures.
* OUTPUT: none.
* RETURNS: void.
* PURPOSE: used to either simply reset in-core structures or to
* permanently remove metadevices from the metadb.
*/
void
{
/* clean up in-core structures */
/*
* Attempt release of minor node
*/
if (!removing)
return;
/* we are removing the soft partition from the metadb */
/*
* Save off device information so we can get to
* it after we do the mddb_deleterec().
*/
/*
* Remove self from the namespace
*/
}
/* Remove the unit structure */
if (vtoc_id)
/*
* remove the underlying device name from the metadb. if other
* soft partitions are built on this device, this will simply
* decrease the reference count for this device. otherwise the
* name record for this device will be removed from the metadb.
*/
}
/*
* FUNCTION: sp_send_stat_msg
* INPUT: un - unit reference
* status - status to be sent to master node
* MD_SP_OK - soft-partition is now OK
* MD_SP_ERR " " errored
* OUTPUT: none.
* RETURNS: void.
* PURPOSE: send a soft-partition status change to the master node. If the
* message succeeds we simply return. If it fails we panic as the
* cluster-wide view of the metadevices is now inconsistent.
* CALLING CONTEXT:
* Blockable. No locks can be held.
*/
static void
{
int rval;
/*
* Panic as we are now in an inconsistent state.
*/
}
}
/*
* FUNCTION: sp_finish_error
* INPUT: ps - parent save structure for error-ed I/O.
* lock_held - set if the unit readerlock is held
* OUTPUT: none.
* RETURNS: void.
* PURPOSE: report a driver error
*/
static void
{
char *str;
/* set error type */
str = "read";
} else {
str = "write";
}
if (lock_held) {
}
md_biodone(pb);
}
/*
* FUNCTION: sp_xmit_ok
* INPUT: dq - daemon queue referencing failing ps structure
* OUTPUT: none.
* RETURNS: void.
* PURPOSE: send a message to the master node in a multi-owner diskset to
* update all attached nodes view of the soft-part to be MD_SP_OK.
* CALLING CONTEXT:
* Blockable. No unit lock held.
*/
static void
{
/* Send a MD_MN_MSG_SP_SETSTAT to the master */
/*
* Successfully transmitted error state to all nodes, now release this
* parent structure.
*/
}
/*
* FUNCTION: sp_xmit_error
* INPUT: dq - daemon queue referencing failing ps structure
* OUTPUT: none.
* RETURNS: void.
* PURPOSE: send a message to the master node in a multi-owner diskset to
* update all attached nodes view of the soft-part to be MD_SP_ERR.
* CALLING CONTEXT:
* Blockable. No unit lock held.
*/
static void
{
/* Send a MD_MN_MSG_SP_SETSTAT to the master */
/*
* Successfully transmitted error state to all nodes, now release this
* parent structure.
*/
}
static void
{
REQ_OLD);
}
static void
{
REQ_OLD);
}
/*
* FUNCTION: sp_error()
* INPUT: ps - parent save structure for error-ed I/O.
* OUTPUT: none.
* RETURNS: void.
* PURPOSE: report a driver error.
* CALLING CONTEXT:
* Interrupt - non-blockable
*/
static void
{
/*
* Drop the mutex associated with this request before (potentially)
* enqueuing the free onto a separate thread. We have to release the
* mutex before destroying the parent structure.
*/
}
} else {
/*
* this should only ever happen if we are panicking,
* since DONTFREE is only set on the parent if panicstr
* is non-NULL.
*/
}
/*
* For a multi-owner set we need to send a message to the master so that
* all nodes get the errored status when we first encounter it. To avoid
* deadlocking when multiple soft-partitions encounter an error on one
* physical unit we drop the unit readerlock before enqueueing the
* request. That way we can service any messages that require a
* writerlock to be held. Additionally, to avoid deadlocking when at
* the bottom of a metadevice stack and a higher level mirror has
* multiple requests outstanding on this soft-part, we clone the ps
* that failed and pass the error back up the stack to release the
* reference that this i/o may have in the higher-level metadevice.
* The other nodes in the cluster just have to modify the soft-part
* status and we do not need to block the i/o completion for this.
*/
if (MD_MNSET_SETNO(setno)) {
sp_finish_error(ps, 0);
return;
} else {
}
/* Flag the error */
}
/*
* FUNCTION: sp_mapbuf()
* INPUT: un - unit structure for soft partition we are doing
* I/O on.
* voff - virtual offset in soft partition to map.
* bcount - # of blocks in the I/O.
* OUTPUT: bp - translated buffer to be passed down to next layer.
* RETURNS: 1 - request must be fragmented, more work to do,
* 0 - request satisified, no more work to do
* -1 - error
* PURPOSE: Map the the virtual offset in the soft partition (passed
* in via voff) to the "physical" offset on whatever the soft
* partition is built on top of. We do this by doing a binary
* search of the extent array in the soft partition unit
* structure. Once the current extent is found, we do the
* translation, determine if the I/O will cross extent
* boundaries (if so, we have to fragment the I/O), then
* fill in the buf structure to be passed down to the next layer.
*/
static int
)
{
extern unsigned md_maxphys;
found = 0;
lo = 0;
/*
* do a binary search to find the extent that contains the
* starting offset. after this loop, mid contains the index
* of the correct extent.
*/
/* is the starting offset contained within the mid-ext? */
found = 1;
else /* voff > un->un_ext[mid].un_voff + un->un_ext[mid].len */
}
if (!found) {
return (-1);
}
/* determine if we need to break the I/O into fragments */
more = 1;
} else {
new_bcount = bcount;
more = 0;
}
/* only break up the I/O if we're not built on another metadevice */
more = 1;
}
/* do bp updates */
}
return (more);
}
/*
* FUNCTION: sp_validate()
* INPUT: un - unit structure to be validated.
* OUTPUT: none.
* RETURNS: 0 - soft partition ok.
* -1 - error.
* PURPOSE: called on open to sanity check the soft partition. In
* order to open a soft partition:
* - it must have at least one extent
* - the extent info in core and on disk must match
* - it may not be in an intermediate state (which would
* imply that a two-phase commit was interrupted)
*
* If the extent checking fails (B_ERROR returned from the read
* strategy call) _and_ we're a multi-owner diskset, we send a
* message to the master so that all nodes inherit the same view
* of the soft partition.
* If we are checking a soft-part that is marked as in error, and
* we can actually read and validate the watermarks we send a
* message to clear the error to the master node.
*/
static int
{
int reset_error = 0;
/* sanity check unit structure components ?? */
"status is %u.",
return (-1);
} else {
"in Errored state.",
reset_error = 1;
}
}
if (un->un_numexts == 0) {
return (-1);
}
/* tally extent lengths to check total size */
/* allocate buffer for watermark */
/* read watermark */
KM_SLEEP);
/*
* make the call non-blocking so that it is not affected
* by a set take.
*/
"read watermark at block %llu for extent %u, "
/*
* If we're a multi-owner diskset we send a message
* indicating that this soft-part has an invalid
* extent to the master node. This ensures a consistent
* view of the soft-part across the cluster.
*/
if (MD_MNSET_SETNO(setno)) {
}
return (-1);
}
/* make sure the checksum is correct first */
"at block %llu for extent %u does not have a "
return (-1);
}
"at block %llu for extent %u does not have a "
"valid watermark magic number, expected 0x%x, "
return (-1);
}
/* make sure sequence number matches the current extent */
"at block %llu for extent %u has invalid "
return (-1);
}
/* make sure watermark length matches unit structure */
"at block %llu for extent %u has inconsistent "
"length, expected %llu, found %llu.",
return (-1);
}
/*
* make sure the type is a valid soft partition and not
* a free extent or the end.
*/
"at block %llu for extent %u is not marked "
return (-1);
}
/* free up buffer */
}
return (-1);
}
/*
* If we're a multi-owner set _and_ reset_error is set, we should clear
* the error condition on all nodes in the set. Use SP_SETSTAT2 with
* MD_SP_OK.
*/
}
return (0);
}
/*
* FUNCTION: sp_done()
* INPUT: child_buf - buffer attached to child save structure.
* this is the buffer on which I/O has just
* completed.
* OUTPUT: none.
* RETURNS: 0 - success.
* 1 - error.
* PURPOSE: called on I/O completion.
*/
static int
{
struct buf *parent_buf;
mdi_unit_t *ui;
/* find the child save structure to which this buffer belongs */
/* now get the parent save structure */
/* pass any errors back up to the parent */
}
/* mapout, if needed */
/*
* if this parent has more children, we just free the
* child and return.
*/
return (1);
}
/* there are no more children */
return (1);
}
} else {
/*
* this should only ever happen if we are panicking,
* since DONTFREE is only set on the parent if panicstr
* is non-NULL.
*/
}
return (0);
}
/*
* FUNCTION: md_sp_strategy()
* INPUT: parent_buf - parent buffer
* flag - flags
* private - private data
* OUTPUT: none.
* RETURNS: void.
* PURPOSE: Soft partitioning I/O strategy. Performs the main work
* needed to do I/O to a soft partition. The basic
* algorithm is as follows:
* - Allocate a child save structure to keep track
* of the I/O we are going to pass down.
* - Map the I/O to the correct extent in the soft
* partition (see sp_mapbuf()).
* - bioclone() the buffer and pass it down the
* stack using md_call_strategy.
* - If the I/O needs to split across extents,
* repeat the above steps until all fragments
* are finished.
*/
static void
{
int more;
mdi_unit_t *ui;
int strat_flag = flag;
/*
* When doing IO to a multi owner meta device, check if set is halted.
* We do this check without the needed lock held, for performance
* reasons.
* If an IO just slips through while the set is locked via an
* MD_MN_SUSPEND_SET, we don't care about it.
* Only check for suspension if we are a top-level i/o request
* (MD_STR_NOTTOP is cleared in 'flag');
*/
(MD_SET_HALTED | MD_SET_MNSET)) {
if ((flag & MD_STR_NOTTOP) == 0) {
mutex_enter(&md_mx);
/* Here we loop until the set is no longer halted */
}
mutex_exit(&md_mx);
}
}
if ((flag & MD_NOBLOCK) == 0) {
if (md_inc_iocount(setno) != 0) {
return;
}
} else {
}
if (!(flag & MD_STR_NOTTOP)) {
return;
}
}
/*
* Save essential information from the original buffhdr
* in the parent.
*/
current_offset = 0;
/*
* if we are at the top and we are panicking,
* we don't free in order to save state.
*/
/*
* Mark this i/o as MD_STR_ABR if we've had ABR enabled on this
* metadevice.
*/
strat_flag |= MD_STR_ABR;
/*
* this loop does the main work of an I/O. we allocate a
* a child save for each buf, do the logical to physical
* mapping, decide if we need to frag the I/O, clone the
* new I/O to pass down the stack. repeat until we've
* taken care of the entire buf that was passed to us.
*/
do {
if (more == -1) {
return;
}
/* calculate new offset, counts, etc... */
if (more) {
}
} while (more);
}
}
}
/*
* FUNCTION: sp_directed_read()
* INPUT: mnum - minor number
* vdr - vol_directed_rd_t from user
* mode - access mode for copying data out.
* OUTPUT: none.
* RETURNS: 0 - success
* Exxxxx - failure error-code
* PURPOSE: Construct the necessary sub-device i/o requests to perform the
* directed read as requested by the user. This is essentially the
* same as md_sp_strategy() with the exception being that the
* underlying 'md_call_strategy' is replaced with an ioctl call.
*/
int
{
int more;
mdi_unit_t *ui;
void *kbuffer;
int ret = 0;
/*
* Construct a parent_buf header which reflects the user-supplied
* request.
*/
return (ENOMEM);
}
if (parent_buf == NULL) {
return (ENOMEM);
}
/*
* Save essential information from the original buffhdr
* in the parent.
*/
current_offset = 0;
vdr->vdr_bytesread = 0;
/*
* this loop does the main work of an I/O. we allocate a
* a child save for each buf, do the logical to physical
* mapping, decide if we need to frag the I/O, clone the
* new I/O to pass down the stack. repeat until we've
* taken care of the entire buf that was passed to us.
*/
do {
if (more == -1) {
goto err_out;
}
/* Work out where we are in the allocated buffer */
/* calculate new offset, counts, etc... */
if (more) {
}
/*
* Free the child structure as we've finished with it.
* Normally this would be done by sp_done() but we're just
* using md_bioclone() to segment the transfer and we never
* issue a strategy request so the iodone will not be called.
*/
if (ret == 0) {
/* copyout the returned data to vdr_data + offset */
goto err_out;
}
} else {
goto err_out;
}
} while (more);
/*
* Update the user-supplied vol_directed_rd_t structure with the
* contents of the last issued child request.
*/
if (ret != 0) {
}
}
return (ret);
}
/*
* FUNCTION: sp_snarf()
* INPUT: cmd - snarf cmd.
* setno - set number.
* OUTPUT: none.
* RETURNS: 1 - soft partitions were snarfed.
* 0 - no soft partitions were snarfed.
* PURPOSE: Snarf soft partition metadb records into their in-core
* structures. This routine is called at "snarf time" when
* md loads and gets all metadevices records into memory.
* The basic algorithm is simply to walk the soft partition
* records in the metadb and call the soft partitioning
* build_incore routine to set up the in-core structures.
*/
static int
{
int gotsomething;
int all_sp_gotten;
if (cmd == MD_SNARF_CLEANUP)
return (0);
all_sp_gotten = 1;
gotsomething = 0;
/* get the record type */
/*
* walk soft partition records in the metadb and call
* sp_build_incore to build in-core structures.
*/
/* if we've already gotten this record, go to the next one */
continue;
switch (rbp->rb_revision) {
case MDDB_REV_RB:
case MDDB_REV_RBFN:
/*
* This means, we have an old and small record.
* And this record hasn't already been converted
* :-o before we create an incore metadevice
* from this we have to convert it to a big
* record.
*/
small_un =
newreqsize = sizeof (mp_unit_t) +
sizeof (struct mp_ext));
KM_SLEEP);
} else {
/* Record has already been converted */
}
break;
case MDDB_REV_RB64:
case MDDB_REV_RB64FN:
/* Large device */
break;
}
/*
* Create minor node for snarfed entry.
*/
/* unit is already in-core */
continue;
}
all_sp_gotten = 0;
gotsomething = 1;
}
}
if (!all_sp_gotten)
return (gotsomething);
/* double-check records */
return (0);
}
/*
* FUNCTION: sp_halt()
* INPUT: cmd - halt cmd.
* setno - set number.
* RETURNS: 0 - success.
* 1 - err.
* PURPOSE: Perform driver halt operations. As with stripe, we
* support MD_HALT_CHECK and MD_HALT_DOIT. The first
* does a check to see if halting can be done safely
* (no open soft partitions), the second cleans up and
* shuts down the driver.
*/
static int
{
int i;
mdi_unit_t *ui;
if (cmd == MD_HALT_CLOSE)
return (0);
if (cmd == MD_HALT_OPEN)
return (0);
if (cmd == MD_HALT_UNLOAD)
return (0);
if (cmd == MD_HALT_CHECK) {
for (i = 0; i < md_nunits; i++) {
continue;
continue;
if (md_unit_isopen(ui))
return (1);
}
return (0);
}
if (cmd != MD_HALT_DOIT)
return (1);
for (i = 0; i < md_nunits; i++) {
continue;
continue;
}
return (0);
}
/*
* FUNCTION: sp_open_dev()
* INPUT: un - unit structure.
* oflags - open flags.
* OUTPUT: none.
* RETURNS: 0 - success.
* non-zero - err.
* PURPOSE: open underlying device via md_layered_open.
*/
static int
{
int err;
/*
* Do the open by device id if underlying is regular
*/
}
if (err)
return (ENXIO);
return (0);
}
/*
* FUNCTION: sp_open()
* INPUT: dev - device to open.
* flag - pass-through flag.
* otyp - pass-through open type.
* cred_p - credentials.
* md_oflags - open flags.
* OUTPUT: none.
* RETURNS: 0 - success.
* non-zero - err.
* PURPOSE: open a soft partition.
*/
/* ARGSUSED */
static int
int flag,
int otyp,
int md_oflags
)
{
int err = 0;
/*
* When doing an open of a multi owner metadevice, check to see if this
* node is a starting node and if a reconfig cycle is underway.
* If so, the system isn't sufficiently set up enough to handle the
* open (which involves I/O during sp_validate), so fail with ENXIO.
*/
(MD_SET_MNSET | MD_SET_MN_START_RC)) {
return (ENXIO);
}
/* grab necessary locks */
/* open underlying device, if necessary */
goto out;
if (MD_MNSET_SETNO(setno)) {
/* For probe, don't incur the overhead of validate */
if (!(md_oflags & MD_OFLG_PROBEDEV)) {
/*
* Don't call sp_validate while
* unit_openclose lock is held. So, actually
* open the device, drop openclose lock,
* call sp_validate, reacquire openclose lock,
* and close the device. If sp_validate
* succeeds, then device will be re-opened.
*/
otyp)) != 0)
goto out;
(void) md_unit_openclose_enter(ui);
/*
* Should be in the same state as before
* the sp_validate.
*/
if (err != 0) {
/* close the device opened above */
goto out;
}
}
/*
* As we're a multi-owner metadevice we need to ensure
* that all nodes have the same idea of the status.
* sp_validate() will mark the device as errored (if
* it cannot read the watermark) or ok (if it was
* previously errored but the watermark is now valid).
* This code-path is only entered on the non-probe open
* so we will maintain the errored state during a probe
* call. This means the sys-admin must metarecover -m
* to reset the soft-partition error.
*/
} else {
/* For probe, don't incur the overhead of validate */
if (!(md_oflags & MD_OFLG_PROBEDEV) &&
/* close the device opened above */
goto out;
} else {
/*
* we succeeded in validating the on disk
* format versus the in core, so reset the
* status if it's in error
*/
}
}
}
}
/* count open */
goto out;
out:
return (err);
}
/*
* FUNCTION: sp_close()
* INPUT: dev - device to close.
* flag - pass-through flag.
* otyp - pass-through type.
* cred_p - credentials.
* md_cflags - close flags.
* OUTPUT: none.
* RETURNS: 0 - success.
* non-zero - err.
* PURPOSE: close a soft paritition.
*/
/* ARGSUSED */
static int
int flag,
int otyp,
int md_cflags
)
{
int err = 0;
/* grab necessary locks */
/* count closed */
goto out;
/* close devices, if necessary */
}
/*
* clear these capabilities if this is the last close in
* the cluster
*/
return (0);
}
/* unlock, return success */
out:
return (err);
}
/* used in sp_dump routine */
/*
* FUNCTION: sp_dump()
* INPUT: dev - device to dump to.
* addr - address to dump.
* blkno - blkno on device.
* nblk - number of blocks to dump.
* OUTPUT: none.
* RETURNS: result from bdev_dump.
* PURPOSE: This routine dumps memory to the disk. It assumes that
* the memory has already been mapped into mainbus space.
* It is called at disk interrupt priority when the system
* is in trouble.
* NOTE: this function is defined using 32-bit arguments,
* but soft partitioning is internally 64-bit. Arguments
* are casted where appropriate.
*/
static int
{
int result;
int more;
int saveresult = 0;
/*
* Don't need to grab the unit lock.
* Cause nothing else is supposed to be happenning.
* Also dump is not supposed to sleep.
*/
return (EINVAL);
return (EINVAL);
do {
if (result)
saveresult = result;
} while (more);
return (saveresult);
}
static int
)
{
int gotsomething;
gotsomething = 0;
continue;
switch (rbp->rb_revision) {
case MDDB_REV_RB:
case MDDB_REV_RBFN:
/*
* Small device
*/
goto out;
break;
case MDDB_REV_RB64:
case MDDB_REV_RB64FN:
goto out;
break;
}
/*
* If this is a top level and a friendly name metadevice,
* update its minor in the namespace.
*/
if ((*parent_id == MD_NO_PARENT) &&
goto out;
}
/*
* Update unit with the imported setno
*
*/
if (*parent_id != MD_NO_PARENT)
gotsomething = 1;
}
out:
return (gotsomething);
}
static md_named_services_t sp_named_services[] = {
{NULL, 0}
};
sp_open, /* open */
sp_close, /* close */
md_sp_strategy, /* strategy */
NULL, /* print */
sp_dump, /* dump */
NULL, /* read */
NULL, /* write */
md_sp_ioctl, /* ioctl, */
sp_snarf, /* snarf */
sp_halt, /* halt */
NULL, /* aread */
NULL, /* awrite */
sp_imp_set, /* import set */
};
static void
{
sizeof (md_spps_t), 0, sp_parent_constructor,
}
static void
{
}
/* define the module linkage */