OpenSolaris_b135/lib/sun_fc/common/FCHBAPort.cc

/*
 * 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 2010 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */



#include <FCHBAPort.h>
#include <Exceptions.h>
#include <Trace.h>
#include <sun_fc.h>
#include <iostream>
#include <iomanip>
#include <sys/types.h>
#include <sys/mkdev.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stropts.h>
#include <dirent.h>
#include <sys/fibre-channel/fc.h>
#include <sys/fibre-channel/fcio.h>
#include <sys/fibre-channel/ulp/fcp_util.h>
#include <sys/fibre-channel/ulp/fcsm.h>
#include <sys/fibre-channel/impl/fc_error.h>
#include <sys/fibre-channel/fc_appif.h>
#include <sys/scsi/generic/commands.h>
#include <sys/scsi/impl/commands.h>
#include <sys/scsi/impl/sense.h>
#include <sys/scsi/generic/inquiry.h>
#include <sys/scsi/generic/status.h>
#include <errno.h>
#include <FCHBANPIVPort.h>


using namespace std;

const int FCHBAPort::MAX_FCIO_MSG_LEN = 256;
const string FCHBAPort::FCSM_DRIVER_PATH = "/devices/pseudo/fcsm@0:fcsm";
const string FCHBAPort::FCP_DRIVER_PATH	= "/devices/pseudo/fcp@0:fcp";

/*
 * Interpret the error code in the fcio_t structure
 *
 * message must be at least MAX_FCIO_MSG_LEN in length.
 */
void
FCHBAPort::transportError(uint32_t fcio_errno, char *message) {
	Trace log("transportError");
	string fcioErrorString;
	if (message == NULL) {
	    log.internalError("NULL routine argument");
	    return;
	}
	switch (fcio_errno) {
	case (uint32_t)FC_FAILURE:
	    fcioErrorString = "general failure";
	    break;
	case (uint32_t)FC_FAILURE_SILENT:
	    fcioErrorString = "general failure but fail silently";
	    break;
	case FC_SUCCESS:
	    fcioErrorString = "successful completion";
	    break;
	case FC_CAP_ERROR:
	    fcioErrorString = "FCA capability error";
	    break;
	case FC_CAP_FOUND:
	    fcioErrorString = "FCA capability unsettable";
	    break;
	case FC_CAP_SETTABLE:
	    fcioErrorString = "FCA capability settable";
	    break;
	case FC_UNBOUND:
	    fcioErrorString = "unbound stuff";
	    break;
	case FC_NOMEM:
	    fcioErrorString = "allocation error";
	    break;
	case FC_BADPACKET:
	    fcioErrorString = "invalid packet specified/supplied";
	    break;
	case FC_OFFLINE:
	    fcioErrorString = "I/O resource unavailable";
	    break;
	case FC_OLDPORT:
	    fcioErrorString = "operation on non-loop port";
	    break;
	case FC_NO_MAP:
	    fcioErrorString = "requested map unavailable";
	    break;
	case FC_TRANSPORT_ERROR:
	    fcioErrorString = "unable to transport I/O";
	    break;
	case FC_ELS_FREJECT:
	    fcioErrorString = "ELS rejected by a Fabric";
	    break;
	case FC_ELS_PREJECT:
	    fcioErrorString = "ELS rejected by an N_port";
	    break;
	case FC_ELS_BAD:
	    fcioErrorString = "ELS rejected by FCA/fctl";
	    break;
	case FC_ELS_MALFORMED:
	    fcioErrorString = "poorly formed ELS request";
	    break;
	case FC_TOOMANY:
		fcioErrorString = "resource request too large";
	    break;
	case FC_UB_BADTOKEN:
	    fcioErrorString = "invalid unsolicited buffer token";
	    break;
	case FC_UB_ERROR:
	    fcioErrorString = "invalid unsol buf request";
	    break;
	case FC_UB_BUSY:
	    fcioErrorString = "buffer already in use";
	    break;
	case FC_BADULP:
	    fcioErrorString = "Unknown ulp";
	    break;
	case FC_BADTYPE:
	    fcioErrorString = "ULP not registered to handle this FC4 type";
	    break;
	case FC_UNCLAIMED:
	    fcioErrorString = "request or data not claimed";
	    break;
	case FC_ULP_SAMEMODULE:
	    fcioErrorString = "module already in use";
	    break;
	case FC_ULP_SAMETYPE:
	    fcioErrorString = "FC4 module already in use";
	    break;
	case FC_ABORTED:
	    fcioErrorString = "request aborted";
	    break;
	case FC_ABORT_FAILED:
	    fcioErrorString = "abort request failed";
	    break;
	case FC_BADEXCHANGE:
	    fcioErrorString = "exchange doesnŐt exist";
	    break;
	case FC_BADWWN:
	    fcioErrorString = "WWN not recognized";
	    break;
	case FC_BADDEV:
	    fcioErrorString = "device unrecognized";
	    break;
	case FC_BADCMD:
	    fcioErrorString = "invalid command issued";
	    break;
	case FC_BADOBJECT:
	    fcioErrorString = "invalid object requested";
	    break;
	case FC_BADPORT:
	    fcioErrorString = "invalid port specified";
	    break;
	case FC_NOTTHISPORT:
	    fcioErrorString = "resource not at this port";
	    break;
	case FC_PREJECT:
	    fcioErrorString = "reject at remote N_Port";
	    break;
	case FC_FREJECT:
	    fcioErrorString = "reject at remote Fabric";
	    break;
	case FC_PBUSY:
	    fcioErrorString = "remote N_Port busy";
	    break;
	case FC_FBUSY:
	    fcioErrorString = "remote Fabric busy";
	    break;
	case FC_ALREADY:
	    fcioErrorString = "already logged in";
	    break;
	case FC_LOGINREQ:
	    fcioErrorString = "login required";
	    break;
	case FC_RESETFAIL:
	    fcioErrorString = "reset failed";
	    break;
	case FC_INVALID_REQUEST:
	    fcioErrorString = "request is invalid";
	    break;
	case FC_OUTOFBOUNDS:
	    fcioErrorString = "port number is out of bounds";
	    break;
	case FC_TRAN_BUSY:
	    fcioErrorString = "command transport busy";
	    break;
	case FC_STATEC_BUSY:
	    fcioErrorString = "port driver currently busy";
	    break;
	case FC_DEVICE_BUSY:
	    fcioErrorString = "transport working on this device";
	    break;
	case FC_DEVICE_NOT_TGT:
	    fcioErrorString = "device is not a SCSI target";
	    break;
	default:
	    snprintf(message, MAX_FCIO_MSG_LEN, "Unknown error code 0x%x",
		fcio_errno);
	    return;
	}
	snprintf(message, MAX_FCIO_MSG_LEN, "%s", fcioErrorString.c_str());
}

static void
reportSense(struct scsi_extended_sense *sense, const char *routine) {
	Trace log("reportSense");
	string msg;
	if (!sense) {
	    log.internalError("NULL sense argument passed.");
	    return;
	}
	if (!routine) {
	    log.internalError("NULL routine argument passed.");
	    return;
	}
	log.genericIOError("SCSI FAILURE");
	switch (sense->es_key) {
	case KEY_NO_SENSE:
	    msg = "No sense";
	    break;
	case KEY_RECOVERABLE_ERROR:
	    msg = "Recoverable error";
	    break;
	case KEY_NOT_READY:
	    msg = "Not ready";
	    break;
	case KEY_MEDIUM_ERROR:
	    msg = "Medium error";
	    break;
	case KEY_HARDWARE_ERROR:
	    msg = "Hardware error";
	    break;
	case KEY_ILLEGAL_REQUEST:
	    msg = "Illegal request";
	    break;
	case KEY_UNIT_ATTENTION:
	    msg = "Unit attention";
	    break;
	case KEY_DATA_PROTECT:
	    msg = "Data protect";
	    break;
	case KEY_BLANK_CHECK:
	    msg = "Blank check";
	    break;
	case KEY_VENDOR_UNIQUE:
	    msg = "Vendor Unique";
	    break;
	case KEY_COPY_ABORTED:
	    msg = "Copy aborted";
	    break;
	case KEY_ABORTED_COMMAND:
	    msg = "Aborted command";
	    break;
	case KEY_EQUAL:
	    msg = "Equal";
	    break;
	case KEY_VOLUME_OVERFLOW:
	    msg = "Volume overflow";
	    break;
	case KEY_MISCOMPARE:
	    msg = "Miscompare";
	    break;
	case KEY_RESERVED:
	    msg = "Reserved";
	    break;
	default:
	    msg = "unknown sense key";
	}
	log.genericIOError("\tSense key: %s", msg.c_str());
	log.genericIOError("\tASC  = 0x%x", sense->es_add_code);
	log.genericIOError("\tASCQ = 0x%x", sense->es_qual_code);
}

/*
 * Issue a SCSI pass thru command.
 * Returns a scsi status value.
 */
void FCHBAPort::sendSCSIPassThru(struct fcp_scsi_cmd *fscsi,
	    HBA_UINT32 *responseSize, HBA_UINT32 *senseSize,
	    HBA_UINT8 *scsiStatus) {
	Trace log("FCHBAPort::sendSCSIPassThru");
	int		    fd;
	HBA_STATUS	    ret;
	int		    count;
	char		    fcioErrorString[MAX_FCIO_MSG_LEN] = "";
	hrtime_t	    start;
	hrtime_t	    end;
	int		    ioctl_errno;
	double		    duration;
	la_wwn_t	    wwn;

	if (fscsi == NULL ||
		responseSize == NULL ||
		senseSize == NULL ||
		scsiStatus == NULL) {
	    throw BadArgumentException();
	}

	memcpy(&wwn, fscsi->scsi_fc_pwwn.raw_wwn, sizeof (la_wwn_t));
	start = gethrtime();
	fscsi->scsi_fc_port_num	= instanceNumber;

	fd = HBA::_open(FCP_DRIVER_PATH, O_RDONLY | O_NDELAY);
	count = 0;
	ioctl_errno = 0;

	if (ioctl(fd, FCP_TGT_SEND_SCSI, fscsi) != 0) {
	    /* save off errno */
	    ioctl_errno = errno;
	    close(fd);
	    /*
	     * collect SCSI status first regrardless of the value.
	     * 0 is a good status so this should be okay
	     */
	    *scsiStatus = fscsi->scsi_bufstatus & STATUS_MASK;
	    transportError(fscsi->scsi_fc_status, fcioErrorString);

	    /* Did we get a check condition? */
	    if ((fscsi->scsi_bufstatus & STATUS_MASK) == STATUS_CHECK) {
		*senseSize = fscsi->scsi_rqlen;
		throw CheckConditionException();
	    } else if (fscsi->scsi_fc_status == FC_DEVICE_NOT_TGT) {
		/*
		 * fcp driver returns FC_DEVICE_NOT_TGT when the node is not
		 * scsi-capable like remote hba nodes.
		 */
		throw NotATargetException();
	    } else if (fscsi->scsi_fc_status == FC_INVALID_LUN) {
		throw InvalidLUNException();
	    } else if (ioctl_errno == EBUSY) {
		throw BusyException();
	    } else if (ioctl_errno == EAGAIN) {
		throw TryAgainException();
	    } else if (ioctl_errno == ENOTSUP) {
		throw NotSupportedException();
	    } else if (ioctl_errno == ENOENT) {
		throw UnavailableException();
	    } else {
		throw IOError(this, wwnConversion(wwn.raw_wwn),
			fscsi->scsi_lun);
	    }
	} else {
		close(fd);
	    /* Just in case, check for a check-condition state */
	    if ((fscsi->scsi_bufstatus & STATUS_MASK) == STATUS_CHECK) {
		*scsiStatus = fscsi->scsi_bufstatus & STATUS_MASK;
		*senseSize = fscsi->scsi_rqlen;
		throw CheckConditionException();
	    }
	}

	/* Record the response data */
	*scsiStatus = fscsi->scsi_bufstatus & STATUS_MASK;
	*responseSize = fscsi->scsi_buflen;
	*senseSize = fscsi->scsi_rqlen;

	/* Do some quick duration calcuations */
	end = gethrtime();
	duration = end - start;
	duration /= HR_SECOND;
	log.debug("Total SCSI IO time for HBA %s "
	    "target %016llx was %.4f seconds", getPath().c_str(),
	    wwnConversion(wwn.raw_wwn), duration);

#ifdef DEBUG
	/* Did we have any failure */
	if (ret != HBA_STATUS_OK) {
	    log.genericIOError(
		"Ioctl failed for device \"%s\" target %016llx."
		"  Errno: \"%s\"(%d), "
		"Transport: \"%s\", SCSI Status: 0x%x"
		"responseSize = %d, senseSize = %d",
		getPath().c_str(), wwnConversion(fscsi->scsi_fc_pwwn.raw_wwn),
		strerror(ioctl_errno), ioctl_errno, fcioErrorString,
		*scsiStatus, *responseSize, *senseSize);
	    /* We may or may not have sense data */
	    reportSense((struct scsi_extended_sense *)fscsi->scsi_rqbufaddr,
		ROUTINE);
	}
#endif

}

/*
 * constructs the fcp_scsi_cmd struct for SCSI_Inquiry, SendReadCapacity, or
 * SendReportLUNs
 */
/*#include <fcio.h>
#include <fcp_util.h>*/
inline void
scsi_cmd_init(struct fcp_scsi_cmd *fscsi, const char *portname, void *reqbuf,
	    size_t req_len, void *responseBuffer, size_t resp_len,
	    void *senseBuffer, size_t sense_len) {
	Trace log("scsi_cmd_init");
	fscsi->scsi_fc_rspcode	= 0;
	fscsi->scsi_flags	= FCP_SCSI_READ;
	fscsi->scsi_timeout	= 10 /* sec */;
	fscsi->scsi_cdbbufaddr	= (char *)reqbuf;
	fscsi->scsi_cdblen	= (uint32_t) req_len;
	fscsi->scsi_bufaddr	= (char *)responseBuffer;
	fscsi->scsi_buflen	= (uint32_t) resp_len;
	fscsi->scsi_bufresid	= 0;
	fscsi->scsi_bufstatus	= 0;
	fscsi->scsi_rqbufaddr	= (char *)senseBuffer;
	fscsi->scsi_rqlen	= (uint32_t) sense_len;
	fscsi->scsi_rqresid	= 0;
}


FCHBAPort::FCHBAPort(string thePath) : HBAPort() {
	Trace log("FCHBAPort::FCHBAPort");
	log.debug("Initializing HBA port %s", thePath.c_str());
	fcio_t		fcio;
	int		size = 200;
	fc_hba_npiv_port_list_t	*pathList;
	bool		retry = false;
	int		bufSize;

	try {
	    path = lookupControllerPath(thePath);
	    sscanf(path.c_str(), "/dev/cfg/c%d", &controllerNumber);
	} catch (...) {
	    log.debug("Unable to lookup controller path and number for %s",
		    thePath.c_str());
	    path = "/devices";
	    path += thePath;
	    path += ":fc";
	    controllerNumber = -1;
	}

	// Fetch the minor number for later use
	struct stat sbuf;
	if (stat(path.c_str(), &sbuf) == -1) {
	    throw IOError("Unable to stat device path: " + path);
	}
	instanceNumber = minor(sbuf.st_rdev);

	// This routine is not index based, so we can discard stateChange
	uint64_t tmp;
	HBA_PORTATTRIBUTES attrs = getPortAttributes(tmp);
	memcpy(&tmp, &attrs.PortWWN, 8);
	portWWN = ntohll(tmp);
	memcpy(&tmp, &attrs.NodeWWN, 8);
	nodeWWN = ntohll(tmp);

	// For reference, here's how to dump WWN's through C++ streams.
	// cout << "\tPort WWN: " << hex << setfill('0') << setw(16) << portWWN
	// << endl;
	// cout << "\tNode WWN: " << hex << setfill('0') << setw(16) << nodeWWN
	// << endl;

	// we should add code here to build NPIVPORT instance
	// Get Port's NPIV port list ( include nwwn and pwwn and path)
	memset((caddr_t)&fcio, 0, sizeof (fcio));
	fcio.fcio_cmd = FCIO_GET_NPIV_PORT_LIST;
	fcio.fcio_xfer = FCIO_XFER_READ;
	do {
		retry = false;
		bufSize = MAXPATHLEN * (size - 1) + (int) sizeof (fc_hba_npiv_port_list_t);
		pathList = (fc_hba_npiv_port_list_t *) new uchar_t[bufSize];
		pathList->numAdapters = size;
		fcio.fcio_olen = bufSize;
		fcio.fcio_obuf = (char *)pathList;
		fp_ioctl(getPath(), FCIO_CMD, &fcio);
		if (pathList->numAdapters > size) {
			log.debug("Buffer too small for number of NPIV Port.Retry.");
			size = pathList->numAdapters;
			retry = true;
			delete (pathList);
		}
	} while (retry);
	log.debug("Get %d npiv ports", pathList->numAdapters);
	// Make instance for each NPIV Port
	for ( int i = 0; i < pathList->numAdapters; i++) {
		try {
			addPort(new FCHBANPIVPort(pathList->hbaPaths[i]));
		} catch (...) {
			log.debug("Ignoring partial failure");
		}
	}
	delete (pathList);
}

uint32_t FCHBAPort::deleteNPIVPort(uint64_t vportwwn) {
	Trace log("FCHBAPort::deleteNPIVPort");
	fcio_t  fcio;
	la_wwn_t        lawwn[1];
	int ret = 0;

	memset(&fcio, 0, sizeof(fcio));
	uint64_t en_wwn = htonll(vportwwn);
	memcpy(&lawwn[0], &en_wwn, sizeof (en_wwn));

	fcio.fcio_cmd = FCIO_DELETE_NPIV_PORT;
	fcio.fcio_xfer = FCIO_XFER_WRITE;
	fcio.fcio_ilen = sizeof (la_wwn_t) * 2;
	fcio.fcio_ibuf = (caddr_t)&lawwn;

	fp_ioctl(getPath(), FCIO_CMD, &fcio);

	return (ret);
}

uint32_t FCHBAPort::createNPIVPort(uint64_t vnodewwn, uint64_t vportwwn, uint32_t vindex) {
	Trace log("FCHBAPort::createNPIVPort");
	fcio_t  fcio;
	la_wwn_t        lawwn[2];
	uint32_t vportindex = 0;
	HBA_NPIVCREATEENTRY	entrybuf;

	memset(&fcio, 0, sizeof(fcio));
	uint64_t en_wwn = htonll(vnodewwn);
	memcpy(&entrybuf.VNodeWWN, &en_wwn, sizeof (en_wwn));
	en_wwn = htonll(vportwwn);
	memcpy(&entrybuf.VPortWWN, &en_wwn, sizeof (en_wwn));
	entrybuf.vindex = vindex;

	fcio.fcio_cmd = FCIO_CREATE_NPIV_PORT;
	fcio.fcio_xfer = FCIO_XFER_READ;
	fcio.fcio_olen = sizeof (uint32_t);
	fcio.fcio_obuf = (caddr_t)&vportindex;
	fcio.fcio_ilen = sizeof (HBA_NPIVCREATEENTRY);
	fcio.fcio_ibuf = (caddr_t)&entrybuf;

	fp_ioctl(getPath(), FCIO_CMD, &fcio);

	return (vportindex);
}

HBA_PORTNPIVATTRIBUTES FCHBAPort::getPortNPIVAttributes(uint64_t &stateChange) {
	Trace log("FCHBAPort::getPortNPIVAttributes");

	HBA_PORTNPIVATTRIBUTES  attributes;
	fc_hba_port_npiv_attributes_t   attrs;
	fcio_t  fcio;

	memset(&fcio, 0, sizeof(fcio));
	memset(&attributes, 0, sizeof(attributes));

	fcio.fcio_cmd = FCIO_GET_ADAPTER_PORT_NPIV_ATTRIBUTES;
	fcio.fcio_olen = sizeof(attrs);
	fcio.fcio_xfer = FCIO_XFER_READ;
	fcio.fcio_obuf = (caddr_t)&attrs;

	fp_ioctl(getPath(), FCIO_CMD, &fcio);

	stateChange = attrs.lastChange;
	attributes.npivflag = attrs.npivflag;
	memcpy(&attributes.NodeWWN, &attrs.NodeWWN, 8);
	memcpy(&attributes.PortWWN, &attrs.PortWWN, 8);
	attributes.MaxNumberOfNPIVPorts = attrs.MaxNumberOfNPIVPorts;
	attributes.NumberOfNPIVPorts = attrs.NumberOfNPIVPorts;

	return (attributes);
}

HBA_PORTATTRIBUTES FCHBAPort::getPortAttributes(uint64_t &stateChange) {
	Trace log("FCHBAPort::getPortAttributes");

	HBA_PORTATTRIBUTES		attributes;
	fcio_t			fcio;
	fc_hba_port_attributes_t    attrs;

	memset(&fcio, 0, sizeof (fcio));
	memset(&attributes, 0, sizeof (attributes));

	fcio.fcio_cmd = FCIO_GET_ADAPTER_PORT_ATTRIBUTES;
	fcio.fcio_olen = sizeof (attrs);
	fcio.fcio_xfer = FCIO_XFER_READ;
	fcio.fcio_obuf = (caddr_t)&attrs;

	fp_ioctl(getPath(), FCIO_CMD, &fcio);

	stateChange = attrs.lastChange;

	attributes.PortFcId = attrs.PortFcId;
	attributes.PortType = attrs.PortType;
	attributes.PortState = attrs.PortState;
	attributes.PortSupportedClassofService = attrs.PortSupportedClassofService;
	attributes.PortSupportedSpeed = attrs.PortSupportedSpeed;
	attributes.PortSpeed = attrs.PortSpeed;
	attributes.PortMaxFrameSize = attrs.PortMaxFrameSize;
	attributes.NumberofDiscoveredPorts = attrs.NumberofDiscoveredPorts;
	memcpy(&attributes.NodeWWN, &attrs.NodeWWN, 8);
	memcpy(&attributes.PortWWN, &attrs.PortWWN, 8);
	memcpy(&attributes.FabricName, &attrs.FabricName, 8);
	memcpy(&attributes.PortSupportedFc4Types, &attrs.PortSupportedFc4Types, 32);
	memcpy(&attributes.PortActiveFc4Types, &attrs.PortActiveFc4Types, 32);
	memcpy(&attributes.PortSymbolicName, &attrs.PortSymbolicName, 256);

	strncpy((char *)attributes.OSDeviceName, getPath().c_str(), 256);
	return (attributes);
}

HBA_PORTATTRIBUTES FCHBAPort::getDiscoveredAttributes(
	    HBA_UINT32 discoveredport, uint64_t &stateChange) {
	Trace log("FCHBAPort::getDiscoverdAttributes(i)");

	HBA_PORTATTRIBUTES		attributes;
	fcio_t			fcio;
	fc_hba_port_attributes_t    attrs;

	memset(&fcio, 0, sizeof (fcio));
	memset(&attributes, 0, sizeof (attributes));

	fcio.fcio_cmd = FCIO_GET_DISCOVERED_PORT_ATTRIBUTES;
	fcio.fcio_olen = sizeof (attrs);
	fcio.fcio_xfer = FCIO_XFER_READ;
	fcio.fcio_obuf = (caddr_t)&attrs;
	fcio.fcio_ilen = sizeof (discoveredport);
	fcio.fcio_ibuf = (caddr_t)&discoveredport;

	fp_ioctl(getPath(), FCIO_CMD, &fcio);

	stateChange = attrs.lastChange;

	attributes.PortFcId = attrs.PortFcId;
	attributes.PortType = attrs.PortType;
	attributes.PortState = attrs.PortState;
	attributes.PortSupportedClassofService = attrs.PortSupportedClassofService;
	attributes.PortSupportedSpeed = attrs.PortSupportedSpeed;
	attributes.PortSpeed = attrs.PortSpeed;
	attributes.PortMaxFrameSize = attrs.PortMaxFrameSize;
	attributes.NumberofDiscoveredPorts = attrs.NumberofDiscoveredPorts;
	memcpy(&attributes.NodeWWN, &attrs.NodeWWN, 8);
	memcpy(&attributes.PortWWN, &attrs.PortWWN, 8);
	memcpy(&attributes.FabricName, &attrs.FabricName, 8);
	memcpy(&attributes.PortSupportedFc4Types, &attrs.PortSupportedFc4Types, 32);
	memcpy(&attributes.PortActiveFc4Types, &attrs.PortActiveFc4Types, 32);
	memcpy(&attributes.PortSymbolicName, &attrs.PortSymbolicName, 256);


	return (attributes);
}

HBA_PORTATTRIBUTES FCHBAPort::getDiscoveredAttributes(
	    uint64_t wwn, uint64_t &stateChange) {
	Trace log("FCHBAPort::getDiscoverdAttributes(p)");

	HBA_PORTATTRIBUTES attributes;
	fcio_t			fcio;
	fc_hba_port_attributes_t    attrs;
	la_wwn_t	lawwn;

	memset(&fcio, 0, sizeof (fcio));
	memset(&attributes, 0, sizeof (attributes));

	uint64_t en_wwn = htonll(wwn);
	memcpy(&lawwn, &en_wwn, sizeof (en_wwn));

	fcio.fcio_cmd = FCIO_GET_PORT_ATTRIBUTES;
	fcio.fcio_olen = sizeof (attrs);
	fcio.fcio_xfer = FCIO_XFER_READ;
	fcio.fcio_obuf = (caddr_t)&attrs;
	fcio.fcio_ilen = sizeof (wwn);
	fcio.fcio_ibuf = (caddr_t)&lawwn;

	fp_ioctl(getPath(), FCIO_CMD, &fcio);

	stateChange = attrs.lastChange;

	attributes.PortFcId = attrs.PortFcId;
	attributes.PortType = attrs.PortType;
	attributes.PortState = attrs.PortState;
	attributes.PortSupportedClassofService = attrs.PortSupportedClassofService;
	attributes.PortSupportedSpeed = attrs.PortSupportedSpeed;
	attributes.PortSpeed = attrs.PortSpeed;
	attributes.PortMaxFrameSize = attrs.PortMaxFrameSize;
	attributes.NumberofDiscoveredPorts = attrs.NumberofDiscoveredPorts;
	memcpy(&attributes.NodeWWN, &attrs.NodeWWN, 8);
	memcpy(&attributes.PortWWN, &attrs.PortWWN, 8);
	memcpy(&attributes.FabricName, &attrs.FabricName, 8);
	memcpy(&attributes.PortSupportedFc4Types, &attrs.PortSupportedFc4Types, 32);
	memcpy(&attributes.PortActiveFc4Types, &attrs.PortActiveFc4Types, 32);
	memcpy(&attributes.PortSymbolicName, &attrs.PortSymbolicName, 256);


	return (attributes);
}


void FCHBAPort::getTargetMappings(PHBA_FCPTARGETMAPPINGV2 userMappings) {
	Trace log("FCHBAPort::getTargetMappings");
	int				i, index;
	uint_t			total_entries = 0;

	struct fcp_ioctl		fioctl;
	fc_hba_target_mappings_t    *mappings;
	int				fd;
	bool			zeroLength = false;


	if (userMappings == NULL) {
	    log.userError("Null mapping argument ");
	    throw BadArgumentException();
	}

	/* It's possible they didn't give any space */
	if (userMappings->NumberOfEntries == 0) {
	    zeroLength = true;
	    userMappings->NumberOfEntries = 1;
		/* We have to give the driver at least one space */
	}

	mappings = (fc_hba_target_mappings_t *)new uchar_t[
		(sizeof (fc_hba_mapping_entry_t)) *
		(userMappings->NumberOfEntries - 1) +
		sizeof (fc_hba_target_mappings_t)];
	if (mappings == NULL) {
	    log.noMemory();
	    throw InternalError();
	}


	fioctl.fp_minor = instanceNumber;
	fioctl.listlen = ((uint32_t) (sizeof (fc_hba_mapping_entry_t))) *
		(userMappings->NumberOfEntries - 1) +
		(uint32_t) sizeof (fc_hba_target_mappings_t);
	fioctl.list = (caddr_t)mappings;

	fd = HBA::_open(FCP_DRIVER_PATH, O_RDONLY | O_NDELAY);

	log.debug("Performing IOCTL to fetch mappings");

	if (ioctl(fd, FCP_GET_TARGET_MAPPINGS, &fioctl) != 0) {
	    delete (mappings);
	    close(fd);
	    if (errno == EBUSY) {
		throw BusyException();
	    } else if (errno == EAGAIN) {
		throw TryAgainException();
	    } else if (errno == ENOTSUP) {
		throw NotSupportedException();
	    } else if (errno == ENOENT) {
		throw UnavailableException();
	    } else {
		throw IOError("Unable to fetch target mappings");
	    }
	}

	close(fd);
	// Quickly iterate through and copy the data over to the client
	for (i = 0; i < userMappings->NumberOfEntries && !zeroLength &&
		    i < mappings->numLuns; i++) {
	    string raw = mappings->entries[i].targetDriver;


	    if (raw.length() <= 0) {
		log.internalError("Bad target mapping without path, truncating.");
		break;
	    }
	    /*
	     * Ideally, we'd like to ask some standard Solaris interface
	     * "What is the prefered minor node for this target?"
	     * but no such interface exists today.  So, for now,
	     * we just hard-code ":n" for tapes, ":c,raw" for disks,
	     * and ":0" for enclosures.
	     * Devices with other generic names will be presented through
	     * first matching /dev path.
	     */
	    if ((raw.find("/st@") != raw.npos) ||
		(raw.find("/tape@") != raw.npos)) {
		raw += ":n";
	    } else if ((raw.find("/ssd@") != raw.npos) ||
	    	(raw.find("/sd@") != raw.npos) ||
	   	(raw.find("/disk@") != raw.npos)) { 
		raw += ":c,raw";
	    } else if ((raw.find("/ses@") != raw.npos) ||
	   	(raw.find("/enclosure@") != raw.npos)) { 
		raw += ":0";
	    } else {
		log.debug(
	    "Unrecognized target driver (%s), using first matching /dev path",
		    raw.c_str());
	    }
	    snprintf(userMappings->entry[i].ScsiId.OSDeviceName,
		sizeof (userMappings->entry[i].ScsiId.OSDeviceName),
		"/devices%s", raw.c_str());
	    userMappings->entry[i].ScsiId.ScsiBusNumber =
		    controllerNumber;
	    userMappings->entry[i].ScsiId.ScsiTargetNumber =
		    mappings->entries[i].targetNumber;
	    userMappings->entry[i].ScsiId.ScsiOSLun =
		    mappings->entries[i].osLUN;
	    userMappings->entry[i].FcpId.FcId =
		    mappings->entries[i].d_id;
	    memcpy(userMappings->entry[i].FcpId.NodeWWN.wwn,
		    mappings->entries[i].NodeWWN.raw_wwn,
		    sizeof (la_wwn_t));
	    memcpy(userMappings->entry[i].FcpId.PortWWN.wwn,
		    mappings->entries[i].PortWWN.raw_wwn,
		    sizeof (la_wwn_t));

	    userMappings->entry[i].FcpId.FcpLun = 
		mappings->entries[i].samLUN;
		
	    memcpy(userMappings->entry[i].LUID.buffer,
		    mappings->entries[i].guid,
		    sizeof (userMappings->entry[i].LUID.buffer));
	}

	log.debug("Total mappings: %d %08x %08x",
	    mappings->numLuns, mappings->entries[i].osLUN, mappings->entries[i].samLUN);

	// If everything is good, convert paths to sym-links
	if (mappings->numLuns > 0 && !zeroLength) {
	    if (userMappings->NumberOfEntries >= mappings->numLuns) {
		// User buffer is larger than needed. (All is good)
		userMappings->NumberOfEntries = mappings->numLuns;
		convertToShortNames(userMappings);
	    } else {
		// User buffer is non zero, but too small.  Don't bother with links
		userMappings->NumberOfEntries = mappings->numLuns;
		delete (mappings);
		throw MoreDataException();
	    }
	} else if (mappings->numLuns > 0) {
	    // Zero length buffer, but we've got mappings
	    userMappings->NumberOfEntries = mappings->numLuns;
	    delete (mappings);
	    throw MoreDataException();
	} else {
	    // No mappings, no worries
	    userMappings->NumberOfEntries = 0;
	    delete (mappings);
	    return;
	}
	delete (mappings);
}

void FCHBAPort::getRNIDMgmtInfo(PHBA_MGMTINFO info) {
	Trace log("FCHBAPort::getRNIDMgmtInfo");
	HBA_STATUS		status = HBA_STATUS_OK;
	fc_rnid_t		rnid;
	fcio_t			fcio;


	if (info == NULL) {
	    log.userError("NULL port management info");
	    throw BadArgumentException();
	}

	// Get the RNID information from the first port
	memset(&rnid, 0, sizeof (fc_rnid_t));
	memset((caddr_t)&fcio, 0, sizeof (fcio));

	fcio.fcio_cmd =	FCIO_GET_NODE_ID;
	fcio.fcio_olen = sizeof (fc_rnid_t);
	fcio.fcio_xfer = FCIO_XFER_READ;
	fcio.fcio_obuf = (caddr_t)&rnid;
	fp_ioctl(getPath(), FCIO_CMD, &fcio);

	// Copy out the struct members of rnid into PHBA_MGMTINFO struct
	memcpy(&info->wwn, &(rnid.global_id), sizeof (info->wwn));
	memcpy(&info->unittype, &(rnid.unit_type), sizeof (info->unittype));
	memcpy(&info->PortId, &(rnid.port_id), sizeof (info->PortId));
	memcpy(&info->NumberOfAttachedNodes, &(rnid.num_attached),
		sizeof (info->NumberOfAttachedNodes));
	memcpy(&info->IPVersion, &(rnid.ip_version), sizeof (info->IPVersion));
	memcpy(&info->UDPPort, &(rnid.udp_port), sizeof (info->UDPPort));
	memcpy(&info->IPAddress, &(rnid.ip_addr), sizeof (info->IPAddress));
	memcpy(&info->TopologyDiscoveryFlags, &(rnid.topo_flags),
		sizeof (info->TopologyDiscoveryFlags));
}

void FCHBAPort::sendCTPassThru(void *requestBuffer, HBA_UINT32 requestSize,
	    void *responseBuffer, HBA_UINT32 *responseSize) {
	Trace log("FCHBAPort::sendCTPassThru");
	fcio_t			fcio;
	struct stat		sbuf;
	minor_t			minor_node;
	hrtime_t		start, end;
	double			duration;

	// Validate the arguments
	if (requestBuffer == NULL) {
	    log.userError("NULL request buffer");
	    throw BadArgumentException();
	}
	if (responseBuffer == NULL) {
	    log.userError("NULL response buffer");
	    throw BadArgumentException();
	}

	minor_node = instanceNumber;

	// construct fcio struct
	memset(&fcio, 0, sizeof (fcio_t));
	fcio.fcio_cmd	= FCSMIO_CT_CMD;
	fcio.fcio_xfer	= FCIO_XFER_RW;

	fcio.fcio_ilen	= requestSize;
	fcio.fcio_ibuf	= (char *)requestBuffer;
	fcio.fcio_olen	= *responseSize;
	fcio.fcio_obuf	= (char *)responseBuffer;

	fcio.fcio_alen	= sizeof (minor_t);
	fcio.fcio_abuf	= (char *)&minor_node;


	start = gethrtime();
	fcsm_ioctl(FCSMIO_CMD, &fcio);

	// Do some calculations on the duration of the ioctl.
	end = gethrtime();
	duration = end - start;
	duration /= HR_SECOND;
	log.debug(
	    "Total CTPASS ioctl call for HBA %s was %.4f seconds",
	    getPath().c_str(), duration);
}

void FCHBAPort::sendRLS(uint64_t destWWN,
	    void		*pRspBuffer,
	    HBA_UINT32		*pRspBufferSize) {
	Trace log("FCHBAPort::sendRLS");

	fcio_t		fcio;
	fc_portid_t		rls_req;


	// Validate the arguments
	if (pRspBuffer == NULL ||
		pRspBufferSize == NULL) {
	    log.userError("NULL hba");
	    throw BadArgumentException();
	}

	// check to see if we are sending RLS to the HBA
	HBA_PORTATTRIBUTES attrs;
	uint64_t tmp;
	if (getPortWWN() == destWWN) {
	    attrs = getPortAttributes(tmp);
	} else {
	    attrs = getDiscoveredAttributes(destWWN, tmp);
	}

	memcpy(&rls_req, &attrs.PortFcId,
	    sizeof (attrs.PortFcId));

	memset((caddr_t)&fcio, 0, sizeof (fcio));
	fcio.fcio_cmd = FCIO_LINK_STATUS;
	fcio.fcio_ibuf = (caddr_t)&rls_req;
	fcio.fcio_ilen = sizeof (rls_req);
	fcio.fcio_xfer = FCIO_XFER_RW;
	fcio.fcio_flags = 0;
	fcio.fcio_cmd_flags = FCIO_CFLAGS_RLS_DEST_NPORT;
	fcio.fcio_obuf = (char *)new uchar_t[*pRspBufferSize];
	fcio.fcio_olen = *pRspBufferSize;

	if (fcio.fcio_obuf == NULL) {
	    log.noMemory();
	    throw InternalError();
	}

	fp_ioctl(getPath(), FCIO_CMD, &fcio);
	memcpy(pRspBuffer, fcio.fcio_obuf, *pRspBufferSize);
	if (fcio.fcio_obuf != NULL) {
	    delete(fcio.fcio_obuf);
	}
}

void FCHBAPort::sendReportLUNs(uint64_t wwn,
	    void *responseBuffer, HBA_UINT32 *responseSize,
	    HBA_UINT8 *scsiStatus,
	    void *senseBuffer, HBA_UINT32 *senseSize) {
	Trace log("FCHBAPort::sendReportLUNs");
	struct	fcp_scsi_cmd	    fscsi;
	union	scsi_cdb	    scsi_rl_req;
	uint64_t		    targetWwn = htonll(wwn);

	// Validate the arguments
	if (responseBuffer == NULL ||
		senseBuffer == NULL ||
		responseSize == NULL ||
		senseSize == NULL) {
	    throw BadArgumentException();
	}

	memset(&fscsi, 0, sizeof (fscsi));
	memset(&scsi_rl_req, 0, sizeof (scsi_rl_req));
	memcpy(fscsi.scsi_fc_pwwn.raw_wwn, &targetWwn, sizeof (la_wwn_t));

	scsi_cmd_init(&fscsi, getPath().c_str(), &scsi_rl_req,
		    sizeof (scsi_rl_req), responseBuffer, *responseSize,
		    senseBuffer, *senseSize);

	fscsi.scsi_lun = 0;
	scsi_rl_req.scc_cmd = SCMD_REPORT_LUNS;
	FORMG5COUNT(&scsi_rl_req, *responseSize);
	sendSCSIPassThru(&fscsi, responseSize, senseSize, scsiStatus);
}

/*
 * arguments:
 *	wwn - remote target WWN where the SCSI Inquiry shall be sent
 *	fcLun - the SCSI LUN to which the SCSI Inquiry shall be sent
 *	cdb1 - the second byte of the CDB for the SCSI Inquiry
 *	cdb2 - the third byte of teh CDB for the SCSI Inquiry
 *	responseBuffer - shall be a pointer to a buffer to receive the SCSI
 *		Inquiry command response
 *	responseSize - a pointer to the size of the buffer to receive
 *		the SCSI Inquiry.
 *	scsiStatus - a pointer to a buffer to receive SCSI status
 *	senseBuffer - pointer to a buffer to receive SCSI sense data
 *	seneseSize - pointer to the size of the buffer to receive SCSI sense
 *		data
 */
void FCHBAPort::sendScsiInquiry(uint64_t wwn, HBA_UINT64 fcLun,
	    HBA_UINT8 cdb1, HBA_UINT8 cdb2, void *responseBuffer,
	    HBA_UINT32 *responseSize, HBA_UINT8 *scsiStatus, void *senseBuffer,
	    HBA_UINT32 *senseSize) {
	Trace log("FCHBAPort::sendScsiInquiry");

	struct	fcp_scsi_cmd	    fscsi;
	union	scsi_cdb	    scsi_inq_req;
	uint64_t		    targetWwn = htonll(wwn);

	// Validate the arguments
	if (responseBuffer == NULL ||
		senseBuffer == NULL ||
		responseSize == NULL ||
		senseSize == NULL) {
	    throw BadArgumentException();
	}

	memset(&fscsi, 0, sizeof (fscsi));
	memset(&scsi_inq_req, 0, sizeof (scsi_inq_req));
	memcpy(fscsi.scsi_fc_pwwn.raw_wwn, &targetWwn, sizeof (la_wwn_t));


	scsi_cmd_init(&fscsi, getPath().c_str(), &scsi_inq_req,
	    sizeof (scsi_inq_req), responseBuffer, *responseSize,
	    senseBuffer, *senseSize);
	fscsi.scsi_lun = fcLun;

	scsi_inq_req.scc_cmd = SCMD_INQUIRY;
	scsi_inq_req.g0_addr1 = cdb2;
	scsi_inq_req.g0_addr2 = cdb1;
	scsi_inq_req.g0_count0 = *responseSize;


	sendSCSIPassThru(&fscsi, responseSize, senseSize, scsiStatus);
}


void FCHBAPort::sendReadCapacity(uint64_t pwwn,
		HBA_UINT64 fcLun, void *responseBuffer,
		HBA_UINT32 *responseSize, HBA_UINT8 *scsiStatus,
		void *senseBuffer, HBA_UINT32 *senseSize) {
	Trace log("FCHBAPort::sendReadCapacity");

	struct fcp_scsi_cmd	    fscsi;
	union scsi_cdb	    scsi_rc_req;
	uint64_t		    targetWwn = htonll(pwwn);

	// Validate the arguments
	if (responseBuffer == NULL ||
		senseBuffer == NULL ||
		responseSize == NULL ||
		senseSize == NULL ||
		scsiStatus == NULL) {
	    throw BadArgumentException();
	}

	memset(&fscsi, 0, sizeof (fscsi));
	memset(&scsi_rc_req, 0, sizeof (scsi_rc_req));

	scsi_cmd_init(&fscsi, getPath().c_str(), &scsi_rc_req,
	    sizeof (scsi_rc_req), responseBuffer, *responseSize,
	    senseBuffer, *senseSize);

	memcpy(fscsi.scsi_fc_pwwn.raw_wwn, &targetWwn, sizeof (la_wwn_t));
	fscsi.scsi_lun = fcLun;

	scsi_rc_req.scc_cmd = SCMD_READ_CAPACITY;
	scsi_rc_req.g1_reladdr = 0;

	scsi_rc_req.g1_addr3 = 0;
	scsi_rc_req.g1_count0	= 0;

	sendSCSIPassThru(&fscsi, responseSize, senseSize, scsiStatus);
}

void FCHBAPort::sendRNID(uint64_t destwwn, HBA_UINT32 destfcid,
			    HBA_UINT32 nodeIdDataFormat, void *pRspBuffer,
			    HBA_UINT32 *RspBufferSize) {
	Trace log("FCHBAPort::sendRNID");
	int 			localportfound, remoteportfound, send;
	fcio_t			fcio;

	// Validate the arguments
	if (pRspBuffer == NULL ||
		RspBufferSize == NULL) {
	    throw BadArgumentException();
	}
	// NodeIdDataFormat must be within the range of 0x00 and 0xff
	if (nodeIdDataFormat > 0xff) {
	    log.userError(
		    "NodeIdDataFormat must be within the range of 0x00 "
		    "and 0xFF");
	    throw BadArgumentException();
	}


	remoteportfound = 0;
	if (destfcid != 0) {
	    try {
		uint64_t tmp;
		HBA_PORTATTRIBUTES attrs = getDiscoveredAttributes(destwwn,
			tmp);
		if (attrs.PortFcId == destfcid) {
		    send = 1;
		    remoteportfound = 1;
		} else {
		    send = 0;
		    remoteportfound = 1;
		}
	    } catch (HBAException &e) {
		/*
		 * Send RNID if destination port not
		 * present in the discovered ports table
		 */
	    }
	    if (remoteportfound == 0) {
		send = 1;
	    }
	} else {
	    send = 1;
	}

	if (!send) {
	    // Can we log something so we can figure out why?
	    throw BadArgumentException();
	}

	memset((caddr_t)&fcio, 0, sizeof (fcio));
	uint64_t netdestwwn = htonll(destwwn);
	fcio.fcio_cmd = FCIO_SEND_NODE_ID;
	fcio.fcio_xfer = FCIO_XFER_READ;
	fcio.fcio_cmd_flags = nodeIdDataFormat;
	fcio.fcio_ilen = sizeof (la_wwn_t);
	fcio.fcio_ibuf = (caddr_t)&netdestwwn;
	fcio.fcio_olen  = *RspBufferSize;
	fcio.fcio_obuf  = (char *)new uchar_t[*RspBufferSize];


	if (fcio.fcio_obuf == NULL) {
	    log.noMemory();
	    throw InternalError();
	}

	fp_ioctl(getPath(), FCIO_CMD, &fcio);

	memcpy(pRspBuffer, fcio.fcio_obuf, *RspBufferSize);

	if (fcio.fcio_obuf != NULL) {
	    delete(fcio.fcio_obuf);
	}
}

void FCHBAPort::setRNID(HBA_MGMTINFO info) {
	Trace log("FCHBAPort::setRNID");
	fc_rnid_t		rnid;
	fcio_t			fcio;

	memset(&rnid, 0, sizeof (fc_rnid_t));
	memset((caddr_t)&fcio, 0, sizeof (fcio));


	fcio.fcio_cmd = FCIO_SET_NODE_ID;
	fcio.fcio_ilen = sizeof (fc_rnid_t);
	fcio.fcio_xfer = FCIO_XFER_WRITE;
	fcio.fcio_ibuf = (caddr_t)&rnid;


	// Copy the HBA_MGMTINFO into fc_rnid_t struct
	memcpy(&(rnid.unit_type), &(info.unittype), sizeof (rnid.unit_type));
	memcpy(&(rnid.port_id), &(info.PortId), sizeof (rnid.port_id));
	memcpy(&(rnid.global_id), &(info.wwn), sizeof (info.wwn));
	memcpy(&(rnid.num_attached), &(info.NumberOfAttachedNodes),
		sizeof (rnid.num_attached));
	memcpy(&(rnid.ip_version), &(info.IPVersion), sizeof (rnid.ip_version));
	memcpy(&(rnid.udp_port), &(info.UDPPort), sizeof (rnid.udp_port));
	memcpy(&(rnid.ip_addr), &info.IPAddress, sizeof (rnid.ip_addr));
	memcpy(&(rnid.topo_flags), &(info.TopologyDiscoveryFlags),
		sizeof (rnid.topo_flags));

	fp_ioctl(getPath(), FCIO_CMD, &fcio, O_NDELAY | O_RDONLY | O_EXCL);
}

void FCHBAPort::fp_ioctl(string path, int cmd, fcio_t *fcio, int openflag) {
	Trace log("FCHBAPort::fp_ioctl with openflag");
	char fcioErrorString[MAX_FCIO_MSG_LEN] = "";
	int fd = HBA::_open(path, openflag);
	try {
	    int times = 0;
	    HBA::_ioctl(fd, cmd, (uchar_t *)fcio);
	    while (fcio->fcio_errno == FC_STATEC_BUSY) {
		sleep(1);
		HBA::_ioctl(fd, cmd, (uchar_t *)fcio);
		if (times++ > 10) {
			break;
		}
	    }
	    close(fd);
	    if (fcio->fcio_errno) {
		throw IOError("IOCTL transport failure");
	    }
	} catch (...) {
	    close(fd);
	    transportError(fcio->fcio_errno, fcioErrorString);
	    log.genericIOError("ioctl (0x%x) failed. Transport: \"%s\"", cmd,
		    fcioErrorString);
	    switch (fcio->fcio_errno) {
	    case FC_BADWWN:
		throw IllegalWWNException();
	    case FC_BADPORT:
		throw IllegalWWNException();
	    case FC_OUTOFBOUNDS:
		throw IllegalIndexException();
	    case FC_PBUSY:
	    case FC_FBUSY:
	    case FC_TRAN_BUSY:
	    case FC_STATEC_BUSY:
	    case FC_DEVICE_BUSY:
		throw BusyException();
	    case FC_SUCCESS:
	    default:
		throw;
	    }
	}
}

void FCHBAPort::fp_ioctl(string path, int cmd, fcio_t *fcio) {
	Trace log("FCHBAPort::fp_ioctl");
	fp_ioctl(path, cmd, fcio, O_NDELAY | O_RDONLY);
}

void FCHBAPort::fcsm_ioctl(int cmd, fcio_t *fcio) {
	// We use the same error handling as fp, so just re-use
	fp_ioctl(FCSM_DRIVER_PATH, cmd, fcio);
}