/*
 * 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
 * or http://www.opensolaris.org/os/licensing.
 * 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 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <io/xdf_shell.h>

/*
 * We're emulating (and possibly layering on top of) cmdk devices, so xdf
 * disk unit mappings must match up with cmdk disk unit mappings'.
 */
#if !defined(XDF_PSHIFT)
#error "can't find definition for xdf unit mappings - XDF_PSHIFT"
#endif /* XDF_PSHIFT */

#if !defined(CMDK_UNITSHF)
#error "can't find definition for cmdk unit mappings - CMDK_UNITSHF"
#endif /* CMDK_UNITSHF */

#if ((XDF_PSHIFT - CMDK_UNITSHF) != 0)
#error "cmdk and xdf unit mappings don't match."
#endif /* ((XDF_PSHIFT - CMDK_UNITSHF) != 0) */

extern const struct dev_ops	cmdk_ops;
extern void			*cmdk_state;

/*
 * Globals required by xdf_shell.c
 */
const char		*xdfs_c_name = "cmdk";
const char		*xdfs_c_linkinfo = "PV Common Direct Access Disk";
void			**xdfs_c_hvm_ss = &cmdk_state;
const size_t		xdfs_c_hvm_ss_size = sizeof (struct cmdk);
const struct dev_ops	*xdfs_c_hvm_dev_ops = &cmdk_ops;

const xdfs_h2p_map_t xdfs_c_h2p_map[] = {
	/*
	 * The paths mapping here are very specific to xen and qemu.  When a
	 * domU is booted under xen in HVM mode, qemu is normally used to
	 * emulate up to four ide disks.  These disks always have the four
	 * path listed below.  To configure an emulated ide device, the
	 * xen domain configuration file normally has an entry that looks
	 * like this:
	 *	disk = [ 'file:/foo.img,hda,w' ]
	 *
	 * The part we're interested in is the 'hda', which we'll call the
	 * xen disk device name here.  The xen management tools (which parse
	 * the xen domain configuration file and launch qemu) makes the
	 * following assumptions about this value:
	 *	hda == emulated ide disk 0 (ide bus 0, master)
	 *	hdb == emulated ide disk 1 (ide bus 0, slave)
	 *	hdc == emulated ide disk 2 (ide bus 1, master)
	 *	hdd == emulated ide disk 3 (ide bus 1, slave)
	 *
	 * (Uncoincidentally, these xen disk device names actually map to
	 * the /dev filesystem names of ide disk devices in Linux.  So in
	 * Linux /dev/hda is the first ide disk.)  So for the first part of
	 * our mapping we've just hardcoded the cmdk paths that we know
	 * qemu will use.
	 *
	 * To understand the second half of the mapping (ie, the xdf device
	 * that each emulated cmdk device should be mapped two) we need to
	 * know the solaris device node address that will be assigned to
	 * each xdf device.  (The device node address is the decimal
	 * number that comes after the "xdf@" in the device path.)
	 *
	 * So the question becomes, how do we know what the xenstore device
	 * id for emulated disk will be?  Well, it turns out that since the
	 * xen management tools expect the disk device names to be Linux
	 * device names, those same management tools assign each disk a
	 * device id that matches the dev_t of the corresponding device
	 * under Linux.  (Big shocker.)  This xen device name-to-id mapping
	 * is currently all hard coded here:
	 *	xen.hg/tools/python/xen/util/blkif.py`blkdev_name_to_number()
	 *
	 * So looking at the code above we can see the following xen disk
	 * device name to xenstore device id mappings:
	 *	'hda' == 0t768  == ((3  * 256) + (0 * 64))
	 *	'hdb' == 0t832  == ((3  * 256) + (1 * 64))
	 *	'hdc' == 0t5632 == ((22 * 256) + (0 * 64))
	 *	'hdd' == 0t5696 == ((22 * 256) + (1 * 64))
	 */
	{ "/pci@0,0/pci-ide@1,1/ide@0/cmdk@0,0", "/xpvd/xdf@768" },
	{ "/pci@0,0/pci-ide@1,1/ide@0/cmdk@1,0", "/xpvd/xdf@832" },
	{ "/pci@0,0/pci-ide@1,1/ide@1/cmdk@0,0", "/xpvd/xdf@5632" },
	{ "/pci@0,0/pci-ide@1,1/ide@1/cmdk@1,0", "/xpvd/xdf@5696" },
	{ NULL, 0 }
};

/*
 * Private functions
 */
/*
 * xdfs_get_modser() is basically a local copy of
 * cmdk_get_modser() modified to work without the dadk layer.
 * (which the non-pv version of the cmdk driver uses.)
 */
static int
xdfs_get_modser(xdfs_state_t *xsp, int ioccmd, char *buf, int len)
{
	struct scsi_device	*scsi_device;
	opaque_t		ctlobjp;
	dadk_ioc_string_t	strarg;
	char			*s;
	char			ch;
	boolean_t		ret;
	int			i;
	int			tb;

	strarg.is_buf = buf;
	strarg.is_size = len;
	scsi_device = ddi_get_driver_private(xsp->xdfss_dip);
	ctlobjp = scsi_device->sd_address.a_hba_tran;
	if (CTL_IOCTL(ctlobjp,
	    ioccmd, (uintptr_t)&strarg, FNATIVE | FKIOCTL) != 0)
		return (0);

	/*
	 * valid model/serial string must contain a non-zero non-space
	 * trim trailing spaces/NULL
	 */
	ret = B_FALSE;
	s = buf;
	for (i = 0; i < strarg.is_size; i++) {
		ch = *s++;
		if (ch != ' ' && ch != '\0')
			tb = i + 1;
		if (ch != ' ' && ch != '\0' && ch != '0')
			ret = B_TRUE;
	}

	if (ret == B_FALSE)
		return (0);

	return (tb);
}

/*
 * xdfs_devid_modser() is basically a copy of cmdk_devid_modser()
 * that has been modified to use local pv cmdk driver functions.
 *
 * Build a devid from the model and serial number
 * Return DDI_SUCCESS or DDI_FAILURE.
 */
static int
xdfs_devid_modser(xdfs_state_t *xsp)
{
	int	rc = DDI_FAILURE;
	char	*hwid;
	int	modlen;
	int	serlen;

	/*
	 * device ID is a concatenation of model number, '=', serial number.
	 */
	hwid = kmem_alloc(CMDK_HWIDLEN, KM_SLEEP);
	modlen = xdfs_get_modser(xsp, DIOCTL_GETMODEL, hwid, CMDK_HWIDLEN);
	if (modlen == 0)
		goto err;

	hwid[modlen++] = '=';
	serlen = xdfs_get_modser(xsp, DIOCTL_GETSERIAL,
	    hwid + modlen, CMDK_HWIDLEN - modlen);
	if (serlen == 0)
		goto err;

	hwid[modlen + serlen] = 0;

	/* Initialize the device ID, trailing NULL not included */
	rc = ddi_devid_init(xsp->xdfss_dip, DEVID_ATA_SERIAL, modlen + serlen,
	    hwid, (ddi_devid_t *)&xsp->xdfss_tgt_devid);
	if (rc != DDI_SUCCESS)
		goto err;

	kmem_free(hwid, CMDK_HWIDLEN);
	return (DDI_SUCCESS);

err:
	kmem_free(hwid, CMDK_HWIDLEN);
	return (DDI_FAILURE);
}

/*
 * xdfs_devid_read() is basically a local copy of
 * cmdk_devid_read() modified to work without the dadk layer.
 * (which the non-pv version of the cmdk driver uses.)
 *
 * Read a devid from on the first block of the last track of
 * the last cylinder.  Make sure what we read is a valid devid.
 * Return DDI_SUCCESS or DDI_FAILURE.
 */
static int
xdfs_devid_read(xdfs_state_t *xsp)
{
	diskaddr_t	blk;
	struct dk_devid *dkdevidp;
	uint_t		*ip, chksum;
	int		i;

	if (cmlb_get_devid_block(xsp->xdfss_cmlbhandle, &blk, 0) != 0)
		return (DDI_FAILURE);

	dkdevidp = kmem_zalloc(NBPSCTR, KM_SLEEP);
	if (xdfs_lb_rdwr(xsp->xdfss_dip,
	    TG_READ, dkdevidp, blk, NBPSCTR, NULL) != 0)
		goto err;

	/* Validate the revision */
	if ((dkdevidp->dkd_rev_hi != DK_DEVID_REV_MSB) ||
	    (dkdevidp->dkd_rev_lo != DK_DEVID_REV_LSB))
		goto err;

	/* Calculate the checksum */
	chksum = 0;
	ip = (uint_t *)dkdevidp;
	for (i = 0; i < ((NBPSCTR - sizeof (int))/sizeof (int)); i++)
		chksum ^= ip[i];
	if (DKD_GETCHKSUM(dkdevidp) != chksum)
		goto err;

	/* Validate the device id */
	if (ddi_devid_valid((ddi_devid_t)dkdevidp->dkd_devid) != DDI_SUCCESS)
		goto err;

	/* keep a copy of the device id */
	i = ddi_devid_sizeof((ddi_devid_t)dkdevidp->dkd_devid);
	xsp->xdfss_tgt_devid = kmem_alloc(i, KM_SLEEP);
	bcopy(dkdevidp->dkd_devid, xsp->xdfss_tgt_devid, i);
	kmem_free(dkdevidp, NBPSCTR);
	return (DDI_SUCCESS);

err:
	kmem_free(dkdevidp, NBPSCTR);
	return (DDI_FAILURE);
}

/*
 * xdfs_devid_fabricate() is basically a local copy of
 * cmdk_devid_fabricate() modified to work without the dadk layer.
 * (which the non-pv version of the cmdk driver uses.)
 *
 * Create a devid and write it on the first block of the last track of
 * the last cylinder.
 * Return DDI_SUCCESS or DDI_FAILURE.
 */
static int
xdfs_devid_fabricate(xdfs_state_t *xsp)
{
	ddi_devid_t	devid = NULL; /* devid made by ddi_devid_init  */
	struct dk_devid	*dkdevidp = NULL; /* devid struct stored on disk */
	diskaddr_t	blk;
	uint_t		*ip, chksum;
	int		i;

	if (cmlb_get_devid_block(xsp->xdfss_cmlbhandle, &blk, 0) != 0)
		return (DDI_FAILURE);

	if (ddi_devid_init(xsp->xdfss_dip, DEVID_FAB, 0, NULL, &devid) !=
	    DDI_SUCCESS)
		return (DDI_FAILURE);

	/* allocate a buffer */
	dkdevidp = (struct dk_devid *)kmem_zalloc(NBPSCTR, KM_SLEEP);

	/* Fill in the revision */
	dkdevidp->dkd_rev_hi = DK_DEVID_REV_MSB;
	dkdevidp->dkd_rev_lo = DK_DEVID_REV_LSB;

	/* Copy in the device id */
	i = ddi_devid_sizeof(devid);
	if (i > DK_DEVID_SIZE)
		goto err;
	bcopy(devid, dkdevidp->dkd_devid, i);

	/* Calculate the chksum */
	chksum = 0;
	ip = (uint_t *)dkdevidp;
	for (i = 0; i < ((NBPSCTR - sizeof (int))/sizeof (int)); i++)
		chksum ^= ip[i];

	/* Fill in the checksum */
	DKD_FORMCHKSUM(chksum, dkdevidp);

	if (xdfs_lb_rdwr(xsp->xdfss_dip,
	    TG_WRITE, dkdevidp, blk, NBPSCTR, NULL) != 0)
		goto err;

	kmem_free(dkdevidp, NBPSCTR);

	xsp->xdfss_tgt_devid = devid;
	return (DDI_SUCCESS);

err:
	if (dkdevidp != NULL)
		kmem_free(dkdevidp, NBPSCTR);
	if (devid != NULL)
		ddi_devid_free(devid);
	return (DDI_FAILURE);
}

/*
 * xdfs_rwcmd_copyin() is a duplicate of rwcmd_copyin().
 */
static int
xdfs_rwcmd_copyin(struct dadkio_rwcmd *rwcmdp, caddr_t inaddr, int flag)
{
	switch (ddi_model_convert_from(flag)) {
		case DDI_MODEL_ILP32: {
			struct dadkio_rwcmd32 cmd32;

			if (ddi_copyin(inaddr, &cmd32,
			    sizeof (struct dadkio_rwcmd32), flag)) {
				return (EFAULT);
			}

			rwcmdp->cmd = cmd32.cmd;
			rwcmdp->flags = cmd32.flags;
			rwcmdp->blkaddr = (blkaddr_t)cmd32.blkaddr;
			rwcmdp->buflen = cmd32.buflen;
			rwcmdp->bufaddr = (caddr_t)(intptr_t)cmd32.bufaddr;
			/*
			 * Note: we do not convert the 'status' field,
			 * as it should not contain valid data at this
			 * point.
			 */
			bzero(&rwcmdp->status, sizeof (rwcmdp->status));
			break;
		}
		case DDI_MODEL_NONE: {
			if (ddi_copyin(inaddr, rwcmdp,
			    sizeof (struct dadkio_rwcmd), flag)) {
				return (EFAULT);
			}
		}
	}
	return (0);
}

/*
 * xdfs_rwcmd_copyout() is a duplicate of rwcmd_copyout().
 */
static int
xdfs_rwcmd_copyout(struct dadkio_rwcmd *rwcmdp, caddr_t outaddr, int flag)
{
	switch (ddi_model_convert_from(flag)) {
		case DDI_MODEL_ILP32: {
			struct dadkio_rwcmd32 cmd32;

			cmd32.cmd = rwcmdp->cmd;
			cmd32.flags = rwcmdp->flags;
			cmd32.blkaddr = rwcmdp->blkaddr;
			cmd32.buflen = rwcmdp->buflen;
			ASSERT64(((uintptr_t)rwcmdp->bufaddr >> 32) == 0);
			cmd32.bufaddr = (caddr32_t)(uintptr_t)rwcmdp->bufaddr;

			cmd32.status.status = rwcmdp->status.status;
			cmd32.status.resid = rwcmdp->status.resid;
			cmd32.status.failed_blk_is_valid =
			    rwcmdp->status.failed_blk_is_valid;
			cmd32.status.failed_blk = rwcmdp->status.failed_blk;
			cmd32.status.fru_code_is_valid =
			    rwcmdp->status.fru_code_is_valid;
			cmd32.status.fru_code = rwcmdp->status.fru_code;

			bcopy(rwcmdp->status.add_error_info,
			    cmd32.status.add_error_info, DADKIO_ERROR_INFO_LEN);

			if (ddi_copyout(&cmd32, outaddr,
			    sizeof (struct dadkio_rwcmd32), flag))
				return (EFAULT);
			break;
		}
		case DDI_MODEL_NONE: {
			if (ddi_copyout(rwcmdp, outaddr,
			    sizeof (struct dadkio_rwcmd), flag))
			return (EFAULT);
		}
	}
	return (0);
}

static int
xdfs_dioctl_rwcmd(dev_t dev, intptr_t arg, int flag)
{
	struct dadkio_rwcmd	*rwcmdp;
	struct iovec		aiov;
	struct uio		auio;
	struct buf		*bp;
	int			rw, status;

	rwcmdp = kmem_alloc(sizeof (struct dadkio_rwcmd), KM_SLEEP);
	status = xdfs_rwcmd_copyin(rwcmdp, (caddr_t)arg, flag);

	if (status != 0)
		goto out;

	switch (rwcmdp->cmd) {
		case DADKIO_RWCMD_READ:
		case DADKIO_RWCMD_WRITE:
			break;
		default:
			status = EINVAL;
			goto out;
	}

	bzero((caddr_t)&aiov, sizeof (struct iovec));
	aiov.iov_base = rwcmdp->bufaddr;
	aiov.iov_len = rwcmdp->buflen;

	bzero((caddr_t)&auio, sizeof (struct uio));
	auio.uio_iov = &aiov;
	auio.uio_iovcnt = 1;
	auio.uio_loffset = (offset_t)rwcmdp->blkaddr * (offset_t)XB_BSIZE;
	auio.uio_resid = rwcmdp->buflen;
	auio.uio_segflg = (flag & FKIOCTL) ? UIO_SYSSPACE : UIO_USERSPACE;

	/*
	 * Tell the xdf driver that this I/O request is using an absolute
	 * offset.
	 */
	bp = getrbuf(KM_SLEEP);
	bp->b_private = (void *)XB_SLICE_NONE;

	rw = ((rwcmdp->cmd == DADKIO_RWCMD_WRITE) ? B_WRITE : B_READ);
	status = physio(xdfs_strategy, bp, dev, rw, xdfs_minphys, &auio);

	biofini(bp);
	kmem_free(bp, sizeof (buf_t));

	if (status == 0)
		status = xdfs_rwcmd_copyout(rwcmdp, (caddr_t)arg, flag);

out:
	kmem_free(rwcmdp, sizeof (struct dadkio_rwcmd));
	return (status);
}


/*
 * xdf_shell callback functions
 */
/*ARGSUSED*/
int
xdfs_c_ioctl(xdfs_state_t *xsp, dev_t dev, int part,
    int cmd, intptr_t arg, int flag, cred_t *credp, int *rvalp, boolean_t *done)
{
	*done = B_TRUE;
	switch (cmd) {
	default:
		*done = B_FALSE;
		return (0);
	case DKIOCLOCK:
	case DKIOCUNLOCK:
	case FDEJECT:
	case DKIOCEJECT:
	case CDROMEJECT: {
		/* we don't support ejectable devices */
		return (ENOTTY);
	}
	case DKIOCGETWCE:
	case DKIOCSETWCE: {
		/* we don't support write cache get/set */
		return (EIO);
	}
	case DKIOCADDBAD: {
		/*
		 * This is for ata/ide bad block handling.  It is supposed
		 * to cause the driver to re-read the bad block list and
		 * alternate map after it has been updated.  Our driver
		 * will refuse to attach to any disk which has a bad blocks
		 * list defined, so there really isn't much to do here.
		 */
		return (0);
	}
	case DKIOCGETDEF: {
		/*
		 * I can't actually find any code that utilizes this ioctl,
		 * hence we're leaving it explicitly unimplemented.
		 */
		ASSERT("ioctl cmd unsupported by xdf shell: DKIOCGETDEF");
		return (EIO);
	}
	case DIOCTL_RWCMD: {
		/*
		 * This just seems to just be an alternate interface for
		 * reading and writing the disk.  Great, another way to
		 * do the same thing...
		 */
		return (xdfs_dioctl_rwcmd(dev, arg, flag));
	}
	case DKIOCINFO: {
		int		instance = ddi_get_instance(xsp->xdfss_dip);
		dev_info_t	*dip = xsp->xdfss_dip;
		struct dk_cinfo	info;
		int		rv;

		/* Pass on the ioctl request, save the response */
		if ((rv = ldi_ioctl(xsp->xdfss_tgt_lh[part],
		    cmd, (intptr_t)&info, FKIOCTL, credp, rvalp)) != 0)
			return (rv);

		/* Update controller info */
		info.dki_cnum = ddi_get_instance(ddi_get_parent(dip));
		(void) strlcpy(info.dki_cname,
		    ddi_get_name(ddi_get_parent(dip)), sizeof (info.dki_cname));

		/* Update unit info. */
		if (info.dki_ctype == DKC_VBD)
			info.dki_ctype = DKC_DIRECT;
		info.dki_unit = instance;
		(void) strlcpy(info.dki_dname,
		    ddi_driver_name(dip), sizeof (info.dki_dname));
		info.dki_addr = 1;

		if (ddi_copyout(&info, (void *)arg, sizeof (info), flag))
			return (EFAULT);
		return (0);
	}
	} /* switch (cmd) */
	/*NOTREACHED*/
}

/*
 * xdfs_c_devid_setup() is a slightly modified copy of cmdk_devid_setup().
 *
 * Create and register the devid.
 * There are 4 different ways we can get a device id:
 *    1. Already have one - nothing to do
 *    2. Build one from the drive's model and serial numbers
 *    3. Read one from the disk (first sector of last track)
 *    4. Fabricate one and write it on the disk.
 * If any of these succeeds, register the deviceid
 */
void
xdfs_c_devid_setup(xdfs_state_t *xsp)
{
	int	rc;

	/* Try options until one succeeds, or all have failed */

	/* 1. All done if already registered */

	if (xsp->xdfss_tgt_devid != NULL)
		return;

	/* 2. Build a devid from the model and serial number */
	rc = xdfs_devid_modser(xsp);
	if (rc != DDI_SUCCESS) {
		/* 3. Read devid from the disk, if present */
		rc = xdfs_devid_read(xsp);

		/* 4. otherwise make one up and write it on the disk */
		if (rc != DDI_SUCCESS)
			rc = xdfs_devid_fabricate(xsp);
	}

	/* If we managed to get a devid any of the above ways, register it */
	if (rc == DDI_SUCCESS)
		(void) ddi_devid_register(xsp->xdfss_dip, xsp->xdfss_tgt_devid);
}

int
xdfs_c_getpgeom(dev_info_t *dip, cmlb_geom_t *pgeom)
{
	struct scsi_device	*scsi_device;
	struct tgdk_geom	tgdk_geom;
	opaque_t		ctlobjp;
	int			err;

	scsi_device = ddi_get_driver_private(dip);
	ctlobjp = scsi_device->sd_address.a_hba_tran;
	if ((err = CTL_IOCTL(ctlobjp,
	    DIOCTL_GETPHYGEOM, (uintptr_t)&tgdk_geom, FKIOCTL)) != 0)
		return (err);

	/* This driver won't work if this isn't true */
	ASSERT(tgdk_geom.g_secsiz == XB_BSIZE);

	pgeom->g_ncyl = tgdk_geom.g_cyl;
	pgeom->g_acyl = tgdk_geom.g_acyl;
	pgeom->g_nhead = tgdk_geom.g_head;
	pgeom->g_nsect = tgdk_geom.g_sec;
	pgeom->g_secsize = tgdk_geom.g_secsiz;
	pgeom->g_capacity = tgdk_geom.g_cap;
	pgeom->g_intrlv = 1;
	pgeom->g_rpm = 3600;
	return (0);
}

boolean_t
xdfs_c_bb_check(xdfs_state_t *xsp)
{
	struct alts_parttbl	*ap;
	diskaddr_t		nblocks, blk;
	uint32_t		altused, altbase, altlast;
	uint16_t		vtoctag;
	int			alts;

	/* find slice with V_ALTSCTR tag */
	for (alts = 0; alts < NDKMAP; alts++) {

		if (cmlb_partinfo(xsp->xdfss_cmlbhandle, alts,
		    &nblocks, &blk, NULL, &vtoctag, 0) != 0) {
			/* no partition table exists */
			return (B_FALSE);
		}

		if ((vtoctag == V_ALTSCTR) && (nblocks > 1))
			break;
	}
	if (alts >= NDKMAP)
		return (B_FALSE); /* no V_ALTSCTR slice defined */

	/* read in ALTS label block */
	ap = (struct alts_parttbl *)kmem_zalloc(NBPSCTR, KM_SLEEP);
	if (xdfs_lb_rdwr(xsp->xdfss_dip, TG_READ, ap, blk, NBPSCTR, NULL) != 0)
		goto err;

	altused = ap->alts_ent_used;	/* number of BB entries */
	altbase = ap->alts_ent_base;	/* blk offset from begin slice */
	altlast = ap->alts_ent_end;	/* blk offset to last block */

	if ((altused == 0) || (altbase < 1) ||
	    (altbase > altlast) || (altlast >= nblocks))
		goto err;

	/* we found bad block mappins */
	kmem_free(ap, NBPSCTR);
	return (B_TRUE);

err:
	kmem_free(ap, NBPSCTR);
	return (B_FALSE);
}

char *
xdfs_c_cmlb_node_type(xdfs_state_t *xsp)
{
	return (xsp->xdfss_tgt_is_cd ? DDI_NT_CD : DDI_NT_BLOCK);
}

/*ARGSUSED*/
int
xdfs_c_cmlb_alter_behavior(xdfs_state_t *xsp)
{
	return (xsp->xdfss_tgt_is_cd ?
	    0 : CMLB_CREATE_ALTSLICE_VTOC_16_DTYPE_DIRECT);
}

/*ARGSUSED*/
void
xdfs_c_attach(xdfs_state_t *xsp)
{
}