root/usr.sbin/mpsutil/mps_cmd.c
/*-
 * Copyright (c) 2015 Baptiste Daroussin <bapt@FreeBSD.org>
 *
 * Copyright (c) 2015 Netflix, Inc.
 * Written by: Scott Long <scottl@freebsd.org>
 *
 * Copyright (c) 2008 Yahoo!, Inc.
 * All rights reserved.
 * Written by: John Baldwin <jhb@FreeBSD.org>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the author nor the names of any co-contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/param.h>
#include <sys/errno.h>
#include <sys/ioctl.h>
#include <sys/sysctl.h>
#include <sys/uio.h>
#include <sys/endian.h>

#include <err.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "mpsutil.h"
#include <dev/mps/mps_ioctl.h>
#include <dev/mpr/mpr_ioctl.h>

#ifndef USE_MPT_IOCTLS
#define USE_MPT_IOCTLS
#endif

static const char *mps_ioc_status_codes[] = {
        "Success",                              /* 0x0000 */
        "Invalid function",
        "Busy",
        "Invalid scatter-gather list",
        "Internal error",
        "Reserved",
        "Insufficient resources",
        "Invalid field",
        "Invalid state",                        /* 0x0008 */
        "Operation state not supported",
        NULL,
        NULL,
        NULL,
        NULL,
        NULL,
        NULL,
        NULL,                                   /* 0x0010 */
        NULL,
        NULL,
        NULL,
        NULL,
        NULL,
        NULL,
        NULL,
        NULL,                                   /* 0x0018 */
        NULL,
        NULL,
        NULL,
        NULL,
        NULL,
        NULL,
        NULL,
        "Invalid configuration action",         /* 0x0020 */
        "Invalid configuration type",
        "Invalid configuration page",
        "Invalid configuration data",
        "No configuration defaults",
        "Unable to commit configuration change",
        NULL,
        NULL,
        NULL,                                   /* 0x0028 */
        NULL,
        NULL,
        NULL,
        NULL,
        NULL,
        NULL,
        NULL,
        NULL,                                   /* 0x0030 */
        NULL,
        NULL,
        NULL,
        NULL,
        NULL,
        NULL,
        NULL,
        NULL,                                   /* 0x0038 */
        NULL,
        NULL,
        NULL,
        NULL,
        NULL,
        NULL,
        NULL,
        "Recovered SCSI error",                 /* 0x0040 */
        "Invalid SCSI bus",
        "Invalid SCSI target ID",
        "SCSI device not there",
        "SCSI data overrun",
        "SCSI data underrun",
        "SCSI I/O error",
        "SCSI protocol error",
        "SCSI task terminated",                 /* 0x0048 */
        "SCSI residual mismatch",
        "SCSI task management failed",
        "SCSI I/O controller terminated",
        "SCSI external controller terminated",
        "EEDP guard error",
        "EEDP reference tag error",
        "EEDP application tag error",
        NULL,                                   /* 0x0050 */
        NULL,
        NULL,
        NULL,
        NULL,
        NULL,
        NULL,
        NULL,
        NULL,                                   /* 0x0058 */
        NULL,
        NULL,
        NULL,
        NULL,
        NULL,
        NULL,
        NULL,
        "SCSI target priority I/O",             /* 0x0060 */
        "Invalid SCSI target port",
        "Invalid SCSI target I/O index",
        "SCSI target aborted",
        "No connection retryable",
        "No connection",
        "FC aborted",
        "Invalid FC receive ID",
        "FC did invalid",                       /* 0x0068 */
        "FC node logged out",
        "Transfer count mismatch",
        "STS data not set",
        "FC exchange canceled",
        "Data offset error",
        "Too much write data",
        "IU too short",
        "ACK NAK timeout",                      /* 0x0070 */
        "NAK received",
        NULL,
        NULL,
        NULL,
        NULL,
        NULL,
        NULL,
        NULL,                                   /* 0x0078 */
        NULL,
        NULL,
        NULL,
        NULL,
        NULL,
        NULL,
        NULL,
        "LAN device not found",                 /* 0x0080 */
        "LAN device failure",
        "LAN transmit error",
        "LAN transmit aborted",
        "LAN receive error",
        "LAN receive aborted",
        "LAN partial packet",
        "LAN canceled",
        NULL,                                   /* 0x0088 */
        NULL,
        NULL,
        NULL,
        NULL,
        NULL,
        NULL,
        NULL,
        "SAS SMP request failed",               /* 0x0090 */
        "SAS SMP data overrun",
        NULL,
        NULL,
        NULL,
        NULL,
        NULL,
        NULL,
        "Inband aborted",                       /* 0x0098 */
        "No inband connection",
        NULL,
        NULL,
        NULL,
        NULL,
        NULL,
        NULL,
        "Diagnostic released",                  /* 0x00A0 */
};

struct mprs_pass_thru {
        uint64_t        PtrRequest;
        uint64_t        PtrReply;
        uint64_t        PtrData;
        uint32_t        RequestSize;
        uint32_t        ReplySize;
        uint32_t        DataSize;
        uint32_t        DataDirection;
        uint64_t        PtrDataOut;
        uint32_t        DataOutSize;
        uint32_t        Timeout;
};

struct mprs_btdh_mapping {
        uint16_t        TargetID;
        uint16_t        Bus;
        uint16_t        DevHandle;
        uint16_t        Reserved;
};

static void adjust_iocfacts_endianness(MPI2_IOC_FACTS_REPLY *facts);

const char *
mps_ioc_status(U16 IOCStatus)
{
        static char buffer[16];

        IOCStatus &= MPI2_IOCSTATUS_MASK;
        if (IOCStatus < sizeof(mps_ioc_status_codes) / sizeof(char *) &&
            mps_ioc_status_codes[IOCStatus] != NULL)
                return (mps_ioc_status_codes[IOCStatus]);
        snprintf(buffer, sizeof(buffer), "Status: 0x%04x", IOCStatus);
        return (buffer);
}

#ifdef USE_MPT_IOCTLS
int
mps_map_btdh(int fd, uint16_t *devhandle, uint16_t *bus, uint16_t *target)
{
        int error;
        struct mprs_btdh_mapping map;

        map.Bus = *bus;
        map.TargetID = *target;
        map.DevHandle = *devhandle;

        if ((error = ioctl(fd, MPTIOCTL_BTDH_MAPPING, &map)) != 0) {
                error = errno;
                warn("Failed to map bus/target/device");
                return (error);
        }

        *bus = map.Bus;
        *target = map.TargetID;
        *devhandle = map.DevHandle;

        return (0);
}

int
mps_set_slot_status(int fd, U16 handle, U16 slot, U32 status)
{
        MPI2_SEP_REQUEST req;
        MPI2_SEP_REPLY reply;

        bzero(&req, sizeof(req));
        req.Function = MPI2_FUNCTION_SCSI_ENCLOSURE_PROCESSOR;
        req.Action = MPI2_SEP_REQ_ACTION_WRITE_STATUS;
        req.Flags = MPI2_SEP_REQ_FLAGS_ENCLOSURE_SLOT_ADDRESS;
        req.EnclosureHandle = handle;
        req.Slot = slot;
        req.SlotStatus = status;

        if (mps_pass_command(fd, &req, sizeof(req), &reply, sizeof(reply),
            NULL, 0, NULL, 0, 30) != 0)
                return (errno);

        if (!IOC_STATUS_SUCCESS(le16toh(reply.IOCStatus)))
                return (EIO);
        return (0);
}

int
mps_read_config_page_header(int fd, U8 PageType, U8 PageNumber, U32 PageAddress,
    MPI2_CONFIG_PAGE_HEADER *header, U16 *IOCStatus)
{
        MPI2_CONFIG_REQUEST req;
        MPI2_CONFIG_REPLY reply;

        bzero(&req, sizeof(req));
        req.Function = MPI2_FUNCTION_CONFIG;
        req.Action = MPI2_CONFIG_ACTION_PAGE_HEADER;
        req.Header.PageType = PageType;
        req.Header.PageNumber = PageNumber;
        req.PageAddress = PageAddress;

        if (mps_pass_command(fd, &req, sizeof(req), &reply, sizeof(reply),
            NULL, 0, NULL, 0, 30))
                return (errno);

        if (!IOC_STATUS_SUCCESS(le16toh(reply.IOCStatus))) {
                if (IOCStatus != NULL)
                        *IOCStatus = reply.IOCStatus;
                return (EIO);
        }
        if (header == NULL)
                return (EINVAL);
        *header = reply.Header;
        return (0);
}

int
mps_read_ext_config_page_header(int fd, U8 ExtPageType, U8 PageNumber, U32 PageAddress, MPI2_CONFIG_PAGE_HEADER *header, U16 *ExtPageLength, U16 *IOCStatus)
{
        MPI2_CONFIG_REQUEST req;
        MPI2_CONFIG_REPLY reply;

        bzero(&req, sizeof(req));
        req.Function = MPI2_FUNCTION_CONFIG;
        req.Action = MPI2_CONFIG_ACTION_PAGE_HEADER;
        req.Header.PageType = MPI2_CONFIG_PAGETYPE_EXTENDED;
        req.ExtPageType = ExtPageType;
        req.Header.PageNumber = PageNumber;
        req.PageAddress = htole32(PageAddress);

        if (mps_pass_command(fd, &req, sizeof(req), &reply, sizeof(reply),
            NULL, 0, NULL, 0, 30))
                return (errno);

        if (!IOC_STATUS_SUCCESS(le16toh(reply.IOCStatus))) {
                if (IOCStatus != NULL)
                        *IOCStatus = le16toh(reply.IOCStatus);
                return (EIO);
        }
        if ((header == NULL) || (ExtPageLength == NULL))
                return (EINVAL);
        *header = reply.Header;
        *ExtPageLength = reply.ExtPageLength;
        return (0);
}

void *
mps_read_config_page(int fd, U8 PageType, U8 PageNumber, U32 PageAddress,
    U16 *IOCStatus)
{
        MPI2_CONFIG_REQUEST req;
        MPI2_CONFIG_PAGE_HEADER header;
        MPI2_CONFIG_REPLY reply;
        void *buf;
        int error, len;

        bzero(&header, sizeof(header));
        error = mps_read_config_page_header(fd, PageType, PageNumber,
            PageAddress, &header, IOCStatus);
        if (error) {
                errno = error;
                return (NULL);
        }

        bzero(&req, sizeof(req));
        req.Function = MPI2_FUNCTION_CONFIG;
        req.Action = MPI2_CONFIG_ACTION_PAGE_READ_CURRENT;
        req.PageAddress = htole32(PageAddress);
        req.Header = header;
        if (req.Header.PageLength == 0)
                req.Header.PageLength = 4;

        len = req.Header.PageLength * 4;
        buf = malloc(len);
        if (mps_pass_command(fd, &req, sizeof(req), &reply, sizeof(reply),
            buf, len, NULL, 0, 30)) {
                error = errno;
                free(buf);
                errno = error;
                return (NULL);
        }
        reply.IOCStatus = le16toh(reply.IOCStatus);
        if (!IOC_STATUS_SUCCESS(reply.IOCStatus)) {
                if (IOCStatus != NULL)
                        *IOCStatus = reply.IOCStatus;
                else
                        warnx("Reading config page failed: 0x%x %s",
                            reply.IOCStatus, mps_ioc_status(reply.IOCStatus));
                free(buf);
                errno = EIO;
                return (NULL);
        }
        return (buf);
}

void *
mps_read_extended_config_page(int fd, U8 ExtPageType, U8 PageVersion,
    U8 PageNumber, U32 PageAddress, U16 *IOCStatus)
{
        MPI2_CONFIG_REQUEST req;
        MPI2_CONFIG_PAGE_HEADER header;
        MPI2_CONFIG_REPLY reply;
        U16 pagelen;
        void *buf;
        int error, len;

        if (IOCStatus != NULL)
                *IOCStatus = MPI2_IOCSTATUS_SUCCESS;
        bzero(&header, sizeof(header));
        error = mps_read_ext_config_page_header(fd, ExtPageType, PageNumber,
            PageAddress, &header, &pagelen, IOCStatus);
        if (error) {
                errno = error;
                return (NULL);
        }

        bzero(&req, sizeof(req));
        req.Function = MPI2_FUNCTION_CONFIG;
        req.Action = MPI2_CONFIG_ACTION_PAGE_READ_CURRENT;
        req.PageAddress = htole32(PageAddress);
        req.Header = header;
        if (pagelen == 0)
                pagelen = htole16(4);
        req.ExtPageLength = pagelen;
        req.ExtPageType = ExtPageType;

        len = le16toh(pagelen) * 4;
        buf = malloc(len);
        if (mps_pass_command(fd, &req, sizeof(req), &reply, sizeof(reply),
            buf, len, NULL, 0, 30)) {
                error = errno;
                free(buf);
                errno = error;
                return (NULL);
        }
        reply.IOCStatus = le16toh(reply.IOCStatus);
        if (!IOC_STATUS_SUCCESS(reply.IOCStatus)) {
                if (IOCStatus != NULL)
                        *IOCStatus = reply.IOCStatus;
                else
                        warnx("Reading extended config page failed: %s",
                            mps_ioc_status(reply.IOCStatus));
                free(buf);
                errno = EIO;
                return (NULL);
        }
        return (buf);
}

int
mps_firmware_send(int fd, unsigned char *fw, uint32_t len, bool bios)
{
        MPI2_FW_DOWNLOAD_REQUEST req;
        MPI2_FW_DOWNLOAD_REPLY reply;

        bzero(&req, sizeof(req));
        bzero(&reply, sizeof(reply));
        req.Function = MPI2_FUNCTION_FW_DOWNLOAD;
        req.ImageType = bios ? MPI2_FW_DOWNLOAD_ITYPE_BIOS : MPI2_FW_DOWNLOAD_ITYPE_FW;
        req.TotalImageSize = htole32(len);
        req.MsgFlags = MPI2_FW_DOWNLOAD_MSGFLGS_LAST_SEGMENT;

        if (mps_user_command(fd, &req, sizeof(req), &reply, sizeof(reply),
            fw, len, 0)) {
                return (-1);
        }
        return (0);
}

int
mps_firmware_get(int fd, unsigned char **firmware, bool bios)
{
        MPI2_FW_UPLOAD_REQUEST req;
        MPI2_FW_UPLOAD_REPLY reply;
        int size;

        *firmware = NULL;
        bzero(&req, sizeof(req));
        bzero(&reply, sizeof(reply));
        req.Function = MPI2_FUNCTION_FW_UPLOAD;
        req.ImageType = bios ? MPI2_FW_DOWNLOAD_ITYPE_BIOS : MPI2_FW_DOWNLOAD_ITYPE_FW;

        if (mps_user_command(fd, &req, sizeof(req), &reply, sizeof(reply),
            NULL, 0, 0)) {
                return (-1);
        }
        if (reply.ActualImageSize == 0) {
                return (-1);
        }

        size = le32toh(reply.ActualImageSize);
        *firmware = calloc(size, sizeof(unsigned char));
        if (*firmware == NULL) {
                warn("calloc");
                return (-1);
        }
        if (mps_user_command(fd, &req, sizeof(req), &reply, sizeof(reply),
            *firmware, size, 0)) {
                free(*firmware);
                return (-1);
        }

        return (size);
}

#else

int
mps_read_config_page_header(int fd, U8 PageType, U8 PageNumber, U32 PageAddress,
    MPI2_CONFIG_PAGE_HEADER *header, U16 *IOCStatus)
{
        struct mps_cfg_page_req req;

        if (IOCStatus != NULL)
                *IOCStatus = MPI2_IOCSTATUS_SUCCESS;
        if (header == NULL)
                return (EINVAL);
        bzero(&req, sizeof(req));
        req.header.PageType = PageType;
        req.header.PageNumber = PageNumber;
        req.page_address = PageAddress;
        if (ioctl(fd, MPSIO_READ_CFG_HEADER, &req) < 0)
                return (errno);
        if (!IOC_STATUS_SUCCESS(req.ioc_status)) {
                if (IOCStatus != NULL)
                        *IOCStatus = req.ioc_status;
                return (EIO);
        }
        bcopy(&req.header, header, sizeof(*header));
        return (0);
}

void *
mps_read_config_page(int fd, U8 PageType, U8 PageNumber, U32 PageAddress,
    U16 *IOCStatus)
{
        struct mps_cfg_page_req req;
        void *buf;
        int error;

        error = mps_read_config_page_header(fd, PageType, PageNumber,
            PageAddress, &req.header, IOCStatus);
        if (error) {
                errno = error;
                return (NULL);
        }

        if (req.header.PageLength == 0)
                req.header.PageLength = 4;
        req.len = req.header.PageLength * 4;
        buf = malloc(req.len);
        req.buf = buf;
        bcopy(&req.header, buf, sizeof(req.header));
        if (ioctl(fd, MPSIO_READ_CFG_PAGE, &req) < 0) {
                error = errno;
                free(buf);
                errno = error;
                return (NULL);
        }
        req.ioc_status = le16toh(req.ioc_status);
        if (!IOC_STATUS_SUCCESS(req.ioc_status)) {
                if (IOCStatus != NULL)
                        *IOCStatus = req.ioc_status;
                else
                        warnx("Reading config page failed: 0x%x %s",
                            req.ioc_status, mps_ioc_status(req.ioc_status));
                free(buf);
                errno = EIO;
                return (NULL);
        }
        return (buf);
}

void *
mps_read_extended_config_page(int fd, U8 ExtPageType, U8 PageVersion,
    U8 PageNumber, U32 PageAddress, U16 *IOCStatus)
{
        struct mps_ext_cfg_page_req req;
        void *buf;
        int error;

        if (IOCStatus != NULL)
                *IOCStatus = MPI2_IOCSTATUS_SUCCESS;
        bzero(&req, sizeof(req));
        req.header.PageVersion = PageVersion;
        req.header.PageNumber = PageNumber;
        req.header.ExtPageType = ExtPageType;
        req.page_address = htole32(PageAddress);
        if (ioctl(fd, MPSIO_READ_EXT_CFG_HEADER, &req) < 0)
                return (NULL);
        req.ioc_status = le16toh(req.ioc_status);
        if (!IOC_STATUS_SUCCESS(req.ioc_status)) {
                if (IOCStatus != NULL)
                        *IOCStatus = req.ioc_status;
                else
                        warnx("Reading extended config page header failed: %s",
                            mps_ioc_status(req.ioc_status));
                errno = EIO;
                return (NULL);
        }
        req.len = req.header.ExtPageLength * 4;
        buf = malloc(req.len);
        req.buf = buf;
        bcopy(&req.header, buf, sizeof(req.header));
        if (ioctl(fd, MPSIO_READ_EXT_CFG_PAGE, &req) < 0) {
                error = errno;
                free(buf);
                errno = error;
                return (NULL);
        }
        req.ioc_status = le16toh(req.ioc_status);
        if (!IOC_STATUS_SUCCESS(req.ioc_status)) {
                if (IOCStatus != NULL)
                        *IOCStatus = req.ioc_status;
                else
                        warnx("Reading extended config page failed: %s",
                            mps_ioc_status(req.ioc_status));
                free(buf);
                errno = EIO;
                return (NULL);
        }
        return (buf);
}
#endif

int
mps_open(int unit)
{
        char path[MAXPATHLEN];

        snprintf(path, sizeof(path), "/dev/mp%s%d", is_mps ? "s": "r", unit);
        return (open(path, O_RDWR));
}

int
mps_user_command(int fd, void *req, uint32_t req_len, void *reply,
        uint32_t reply_len, void *buffer, int len, uint32_t flags)
{
        struct mps_usr_command cmd;

        bzero(&cmd, sizeof(struct mps_usr_command));
        cmd.req = req;
        cmd.req_len = req_len;
        cmd.rpl = reply;
        cmd.rpl_len = reply_len;
        cmd.buf = buffer;
        cmd.len = len;
        cmd.flags = flags;

        if (ioctl(fd, is_mps ? MPSIO_MPS_COMMAND : MPRIO_MPR_COMMAND, &cmd) < 0)
                return (errno);
        return (0);
}

int
mps_pass_command(int fd, void *req, uint32_t req_len, void *reply,
        uint32_t reply_len, void *data_in, uint32_t datain_len, void *data_out,
        uint32_t dataout_len, uint32_t timeout)
{
        struct mprs_pass_thru pass;

        bzero(&pass, sizeof(pass));
        pass.PtrRequest = (uint64_t)(uintptr_t)req;
        pass.PtrReply = (uint64_t)(uintptr_t)reply;
        pass.RequestSize = req_len;
        pass.ReplySize = reply_len;
        if (datain_len && dataout_len) {
                pass.PtrData = (uint64_t)(uintptr_t)data_in;
                pass.PtrDataOut = (uint64_t)(uintptr_t)data_out;
                pass.DataSize = datain_len;
                pass.DataOutSize = dataout_len;
                if (is_mps) {
                        pass.DataDirection = MPS_PASS_THRU_DIRECTION_BOTH;
                } else {
                        pass.DataDirection = MPR_PASS_THRU_DIRECTION_BOTH;
                }
        } else if (datain_len) {
                pass.PtrData = (uint64_t)(uintptr_t)data_in;
                pass.DataSize = datain_len;
                if (is_mps) {
                        pass.DataDirection = MPS_PASS_THRU_DIRECTION_READ;
                } else {
                        pass.DataDirection = MPR_PASS_THRU_DIRECTION_READ;
                }
        } else if (dataout_len) {
                pass.PtrData = (uint64_t)(uintptr_t)data_out;
                pass.DataSize = dataout_len;
                if (is_mps) {
                        pass.DataDirection = MPS_PASS_THRU_DIRECTION_WRITE;
                } else {
                        pass.DataDirection = MPR_PASS_THRU_DIRECTION_WRITE;
                }
        } else {
                if (is_mps) {
                        pass.DataDirection = MPS_PASS_THRU_DIRECTION_NONE;
                } else {
                        pass.DataDirection = MPR_PASS_THRU_DIRECTION_NONE;
                }
        }
        pass.Timeout = timeout;

        if (ioctl(fd, MPTIOCTL_PASS_THRU, &pass) < 0)
                return (errno);
        return (0);
}

/* Return the length in bytes of the device's MPI2_IOC_FACTS reply */
static size_t
mps_get_ioc_factslen(int fd)
{
        MPI2_IOC_FACTS_REQUEST req;
        const size_t factslen = 4;
        char factsbuf[4] = {0};
        MPI2_IOC_FACTS_REPLY *facts = (MPI2_IOC_FACTS_REPLY*)factsbuf;
        int error;

        bzero(&req, sizeof(req));
        req.Function = MPI2_FUNCTION_IOC_FACTS;
        error = mps_pass_command(fd, &req, sizeof(MPI2_IOC_FACTS_REQUEST),
            factsbuf, factslen, NULL, 0, NULL, 0, 10);

        if (error)
                return (0);

        /* The card's response is measured in dwords */
        return (facts->MsgLength * 4);
}

MPI2_IOC_FACTS_REPLY *
mps_get_iocfacts(int fd)
{
        MPI2_IOC_FACTS_REPLY *facts;
        MPI2_IOC_FACTS_REQUEST req;
        size_t factslen;
        int error;

        factslen = mps_get_ioc_factslen(fd);
        if (factslen == 0)
                return (NULL);

        facts = malloc(factslen);
        if (facts == NULL) {
                errno = ENOMEM;
                return (NULL);
        }

        bzero(&req, sizeof(req));
        req.Function = MPI2_FUNCTION_IOC_FACTS;

#if 1
        error = mps_pass_command(fd, &req, sizeof(MPI2_IOC_FACTS_REQUEST),
            facts, factslen, NULL, 0, NULL, 0, 10);
#else
        error = mps_user_command(fd, &req, sizeof(MPI2_IOC_FACTS_REQUEST),
            facts, factslen, NULL, 0, 0);
#endif
        if (error) {
                free(facts);
                return (NULL);
        }

        if (!IOC_STATUS_SUCCESS(facts->IOCStatus)) {
                free(facts);
                errno = EINVAL;
                return (NULL);
        }
        adjust_iocfacts_endianness(facts);
        return (facts);
}

static void
adjust_iocfacts_endianness(MPI2_IOC_FACTS_REPLY *facts)
{
        facts->MsgVersion = le16toh(facts->MsgVersion);
        facts->HeaderVersion = le16toh(facts->HeaderVersion);
        facts->Reserved1 = le16toh(facts->Reserved1);
        facts->IOCExceptions = le16toh(facts->IOCExceptions);
        facts->IOCStatus = le16toh(facts->IOCStatus);
        facts->IOCLogInfo = le32toh(facts->IOCLogInfo);
        facts->RequestCredit = le16toh(facts->RequestCredit);
        facts->ProductID = le16toh(facts->ProductID);
        facts->IOCCapabilities = le32toh(facts->IOCCapabilities);
        facts->IOCRequestFrameSize =
            le16toh(facts->IOCRequestFrameSize);
        facts->FWVersion.Word = le32toh(facts->FWVersion.Word);
        facts->MaxInitiators = le16toh(facts->MaxInitiators);
        facts->MaxTargets = le16toh(facts->MaxTargets);
        facts->MaxSasExpanders = le16toh(facts->MaxSasExpanders);
        facts->MaxEnclosures = le16toh(facts->MaxEnclosures);
        facts->ProtocolFlags = le16toh(facts->ProtocolFlags);
        facts->HighPriorityCredit = le16toh(facts->HighPriorityCredit);
        facts->MaxReplyDescriptorPostQueueDepth =
            le16toh(facts->MaxReplyDescriptorPostQueueDepth);
        facts->MaxDevHandle = le16toh(facts->MaxDevHandle);
        facts->MaxPersistentEntries =
            le16toh(facts->MaxPersistentEntries);
        facts->MinDevHandle = le16toh(facts->MinDevHandle);
}