#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",
"Invalid function",
"Busy",
"Invalid scatter-gather list",
"Internal error",
"Reserved",
"Insufficient resources",
"Invalid field",
"Invalid state",
"Operation state not supported",
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
"Invalid configuration action",
"Invalid configuration type",
"Invalid configuration page",
"Invalid configuration data",
"No configuration defaults",
"Unable to commit configuration change",
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
"Recovered SCSI error",
"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",
"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,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
"SCSI target priority I/O",
"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",
"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",
"NAK received",
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
"LAN device not found",
"LAN device failure",
"LAN transmit error",
"LAN transmit aborted",
"LAN receive error",
"LAN receive aborted",
"LAN partial packet",
"LAN canceled",
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
"SAS SMP request failed",
"SAS SMP data overrun",
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
"Inband aborted",
"No inband connection",
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
"Diagnostic released",
};
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);
}
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);
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);
}