#include <sys/cred.h>
#include <sys/file.h>
#include <sys/types.h>
#include <sys/errno.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/policy.h>
#include <sys/ddifm.h>
#include <sys/fm/io/ddi.h>
#include <sys/scsi/adapters/mfi/mfi_evt.h>
#include <sys/scsi/adapters/mfi/mfi_ioctl.h>
#include "lmrc.h"
#include "lmrc_reg.h"
#include "lmrc_raid.h"
#include "lmrc_ioctl.h"
static int lmrc_drv_ioctl_drv_version(lmrc_t *, void *, size_t, int);
static int lmrc_drv_ioctl_pci_info(lmrc_t *, void *, size_t, int);
static int lmrc_drv_ioctl(lmrc_t *, mfi_ioctl_t *, int);
static void lmrc_mfi_ioctl_scsi_io(lmrc_t *, mfi_ioctl_t *, lmrc_mfi_cmd_t *,
uintptr_t *, uintptr_t *);
static void lmrc_mfi_ioctl_dcmd(lmrc_t *, mfi_ioctl_t *, lmrc_mfi_cmd_t *,
uintptr_t *);
static int lmrc_mfi_ioctl(lmrc_t *, mfi_ioctl_t *, int);
static int lmrc_mfi_aen_ioctl(lmrc_t *, mfi_aen_t *);
static int lmrc_fw_ioctl(lmrc_t *, intptr_t, int);
static int lmrc_aen_ioctl(lmrc_t *, intptr_t, int);
static int
lmrc_drv_ioctl_drv_version(lmrc_t *lmrc, void *ubuf, size_t len, int mode)
{
static mfi_drv_ver_t dv = {
.dv_signature = "$ILLUMOS$",
.dv_os_name = "illumos",
.dv_drv_name = "lmrc",
.dv_drv_ver = "0.1",
.dv_drv_rel_date = "Feb 09, 2023"
};
int ret;
ret = ddi_copyout(&dv, ubuf, len, mode);
if (ret != DDI_SUCCESS)
return (EFAULT);
return (0);
}
static int
lmrc_drv_ioctl_pci_info(lmrc_t *lmrc, void *ubuf, size_t len, int mode)
{
int *props = NULL;
ddi_acc_handle_t pcih;
mfi_pci_info_t pi;
uint_t nprop;
int ret;
int i;
ret = ddi_prop_lookup_int_array(DDI_DEV_T_ANY, lmrc->l_dip, 0, "reg",
&props, &nprop);
if (ret != DDI_SUCCESS)
return (EINVAL);
bzero(&pi, sizeof (pi));
pi.pi_bus = (props[0] >> 16) & 0xff;
pi.pi_dev = (props[0] >> 11) & 0x1f;
pi.pi_func = (props[0] >> 8) & 0x7;
ddi_prop_free(props);
if (pci_config_setup(lmrc->l_dip, &pcih) != DDI_SUCCESS)
return (EINVAL);
for (i = 0; i != ARRAY_SIZE(pi.pi_header); i++)
pi.pi_header[i] = pci_config_get8(pcih, i);
if (lmrc_check_acc_handle(lmrc->l_reghandle) != DDI_SUCCESS) {
pci_config_teardown(&pcih);
lmrc_fm_ereport(lmrc, DDI_FM_DEVICE_NO_RESPONSE);
ddi_fm_service_impact(lmrc->l_dip, DDI_SERVICE_LOST);
return (EIO);
}
pci_config_teardown(&pcih);
ret = ddi_copyout(&pi, ubuf, len, mode);
if (ret != DDI_SUCCESS)
return (EFAULT);
return (0);
}
static int
lmrc_drv_ioctl(lmrc_t *lmrc, mfi_ioctl_t *ioc, int mode)
{
mfi_header_t *hdr = &ioc->ioc_frame.mf_hdr;
mfi_dcmd_payload_t *dcmd = &ioc->ioc_frame.mf_dcmd;
size_t xferlen = dcmd->md_sgl.ms64_length;
void *ubuf = (void *)dcmd->md_sgl.ms64_phys_addr;
int ret = EINVAL;
#ifdef _MULTI_DATAMODEL
if (ddi_model_convert_from(mode & FMODELS) == DDI_MODEL_ILP32) {
xferlen = dcmd->md_sgl.ms32_length;
ubuf = (void *)(uintptr_t)dcmd->md_sgl.ms32_phys_addr;
} else {
#endif
xferlen = dcmd->md_sgl.ms64_length;
ubuf = (void *)(uintptr_t)dcmd->md_sgl.ms64_phys_addr;
#ifdef _MULTI_DATAMODEL
}
#endif
switch (dcmd->md_opcode) {
case MFI_DRIVER_IOCTL_DRIVER_VERSION:
ret = lmrc_drv_ioctl_drv_version(lmrc, ubuf, xferlen, mode);
break;
case MFI_DRIVER_IOCTL_PCI_INFORMATION:
ret = lmrc_drv_ioctl_pci_info(lmrc, ubuf, xferlen, mode);
break;
default:
dev_err(lmrc->l_dip, CE_WARN,
"!%s: invalid driver ioctl, cmd = %d",
__func__, dcmd->md_opcode);
ret = EINVAL;
break;
}
if (ret != 0)
hdr->mh_cmd_status = MFI_STAT_INVALID_CMD;
else
hdr->mh_cmd_status = MFI_STAT_OK;
return (ret);
}
static void
lmrc_mfi_ioctl_scsi_io(lmrc_t *lmrc, mfi_ioctl_t *ioc, lmrc_mfi_cmd_t *mfi,
uintptr_t *sgloff, uintptr_t *senseoff)
{
mfi_pthru_payload_t *ioc_pthru = &ioc->ioc_frame.mf_pthru;
mfi_pthru_payload_t *mfi_pthru = &mfi->mfi_frame->mf_pthru;
bcopy(ioc_pthru->mp_cdb, mfi_pthru->mp_cdb, sizeof (mfi_pthru->mp_cdb));
*sgloff = offsetof(mfi_pthru_payload_t, mp_sgl);
*senseoff = offsetof(mfi_pthru_payload_t, mp_sense_buf_phys_addr);
}
static void
lmrc_mfi_ioctl_dcmd(lmrc_t *lmrc, mfi_ioctl_t *ioc, lmrc_mfi_cmd_t *mfi,
uintptr_t *sgloff)
{
mfi_dcmd_payload_t *ioc_dcmd = &ioc->ioc_frame.mf_dcmd;
mfi_dcmd_payload_t *mfi_dcmd = &mfi->mfi_frame->mf_dcmd;
mfi_dcmd->md_opcode = ioc_dcmd->md_opcode;
bcopy(ioc_dcmd->md_mbox_8, mfi_dcmd->md_mbox_8,
sizeof (mfi_dcmd->md_mbox_8));
*sgloff = offsetof(mfi_dcmd_payload_t, md_sgl);
}
static int
lmrc_mfi_ioctl(lmrc_t *lmrc, mfi_ioctl_t *ioc, int mode)
{
uint64_t *mfi_senseaddr = NULL, *ioc_senseaddr = NULL;
lmrc_dma_t sense;
size_t xferlen = 0;
mfi_header_t *mfi_hdr, *ioc_hdr;
mfi_sgl_t *mfi_sgl, *ioc_sgl;
lmrc_mfi_cmd_t *mfi;
uintptr_t sgloff;
void *xferbuf;
int ret;
ioc_hdr = &ioc->ioc_frame.mf_hdr;
if (ioc_hdr->mh_sense_len > MFI_IOC_SENSE_LEN)
return (EINVAL);
mfi = lmrc_get_mfi(lmrc);
mfi_hdr = &mfi->mfi_frame->mf_hdr;
mfi_hdr->mh_cmd = ioc_hdr->mh_cmd;
mfi_hdr->mh_sense_len = ioc_hdr->mh_sense_len;
mfi_hdr->mh_drv_opts = ioc_hdr->mh_drv_opts;
mfi_hdr->mh_flags = ioc_hdr->mh_flags & ~MFI_FRAME_SGL64;
mfi_hdr->mh_timeout = ioc_hdr->mh_timeout;
mfi_hdr->mh_data_xfer_len = ioc_hdr->mh_data_xfer_len;
switch (mfi_hdr->mh_cmd) {
case MFI_CMD_LD_SCSI_IO:
case MFI_CMD_PD_SCSI_IO: {
uintptr_t senseoff;
lmrc_mfi_ioctl_scsi_io(lmrc, ioc, mfi, &sgloff, &senseoff);
mfi_senseaddr = (uint64_t *)&mfi->mfi_frame->mf_raw[senseoff];
ioc_senseaddr = (uint64_t *)&ioc->ioc_frame.mf_raw[senseoff];
break;
}
case MFI_CMD_DCMD:
if (mfi_hdr->mh_sense_len != 0) {
ret = EINVAL;
goto out;
}
lmrc_mfi_ioctl_dcmd(lmrc, ioc, mfi, &sgloff);
break;
default:
dev_err(lmrc->l_dip, CE_WARN,
"!%s: invalid MFI ioctl, cmd = %d",
__func__, mfi_hdr->mh_cmd);
ret = EINVAL;
goto out;
}
ASSERT3U(sgloff, !=, 0);
ioc_sgl = (mfi_sgl_t *)&ioc->ioc_frame.mf_raw[sgloff];
mfi_sgl = (mfi_sgl_t *)&mfi->mfi_frame->mf_raw[sgloff];
#ifdef _MULTI_DATAMODEL
if (ddi_model_convert_from(mode & FMODELS) == DDI_MODEL_ILP32) {
xferlen = ioc_sgl->ms32_length;
xferbuf = (void *)(uintptr_t)ioc_sgl->ms32_phys_addr;
} else {
#endif
xferlen = ioc_sgl->ms64_length;
xferbuf = (void *)(uintptr_t)ioc_sgl->ms64_phys_addr;
#ifdef _MULTI_DATAMODEL
}
#endif
if (xferlen != 0) {
ret = lmrc_dma_alloc(lmrc, lmrc->l_dma_attr,
&mfi->mfi_data_dma, xferlen, 1, DDI_DMA_CONSISTENT);
if (ret != DDI_SUCCESS) {
ret = EINVAL;
goto out;
}
if ((mfi_hdr->mh_flags & MFI_FRAME_DIR_WRITE) != 0) {
ret = ddi_copyin(xferbuf, mfi->mfi_data_dma.ld_buf,
xferlen, mode);
if (ret != DDI_SUCCESS) {
ret = EFAULT;
goto out;
}
}
mfi_hdr->mh_flags |= MFI_FRAME_SGL64;
lmrc_dma_set_addr64(&mfi->mfi_data_dma,
&mfi_sgl->ms64_phys_addr);
mfi_sgl->ms64_length = lmrc_dma_get_size(&mfi->mfi_data_dma);
} else {
mfi_hdr->mh_flags &= ~MFI_FRAME_DIR_BOTH;
}
if (mfi_hdr->mh_sense_len != 0) {
ret = lmrc_dma_alloc(lmrc, lmrc->l_dma_attr, &sense,
mfi_hdr->mh_sense_len, 1, DDI_DMA_CONSISTENT);
if (ret != DDI_SUCCESS) {
ret = EINVAL;
goto out;
}
lmrc_dma_set_addr64(&sense, mfi_senseaddr);
}
mutex_enter(&mfi->mfi_lock);
lmrc_issue_mfi(lmrc, mfi, lmrc_wakeup_mfi);
ret = lmrc_wait_mfi(lmrc, mfi, LMRC_INTERNAL_CMD_WAIT_TIME);
mutex_exit(&mfi->mfi_lock);
if (ret != DDI_SUCCESS) {
ret = EAGAIN;
goto out;
}
if (xferlen != 0 && (mfi_hdr->mh_flags & MFI_FRAME_DIR_READ) != 0) {
ret = ddi_copyout(mfi->mfi_data_dma.ld_buf, xferbuf, xferlen,
mode);
if (ret != DDI_SUCCESS) {
ret = EFAULT;
goto out;
}
}
if (mfi_hdr->mh_sense_len != 0) {
void *sensebuf = (void *)(uintptr_t)*ioc_senseaddr;
(void) ddi_dma_sync(sense.ld_hdl, 0, sense.ld_len,
DDI_DMA_SYNC_FORKERNEL);
ret = ddi_copyout(sense.ld_buf, sensebuf, sense.ld_len, mode);
if (ret != DDI_SUCCESS) {
ret = EFAULT;
goto out;
}
}
out:
ioc_hdr->mh_cmd_status = mfi_hdr->mh_cmd_status;
ioc_hdr->mh_scsi_status = mfi_hdr->mh_scsi_status;
if (xferlen != 0)
lmrc_dma_free(&mfi->mfi_data_dma);
if (mfi_hdr->mh_sense_len != 0)
lmrc_dma_free(&sense);
lmrc_put_mfi(mfi);
if (ret != 0)
dev_err(lmrc->l_dip, CE_WARN,
"%s: failing MFI ioctl, ret = %d",
__func__, ret);
return (ret);
}
static int
lmrc_fw_ioctl(lmrc_t *lmrc, intptr_t arg, int mode)
{
mfi_ioctl_t *ioc;
int ret = EINVAL;
ioc = kmem_zalloc(sizeof (mfi_ioctl_t), KM_SLEEP);
if (ddi_copyin((void *)arg, ioc, sizeof (*ioc), mode) != 0) {
ret = EFAULT;
goto out;
}
if (ioc->ioc_control_code == MFI_DRIVER_IOCTL_COMMON) {
ret = lmrc_drv_ioctl(lmrc, ioc, mode);
} else {
sema_p(&lmrc->l_ioctl_sema);
ret = lmrc_mfi_ioctl(lmrc, ioc, mode);
sema_v(&lmrc->l_ioctl_sema);
}
if (ddi_copyout(ioc, (void *)arg, sizeof (*ioc) - 1, mode) != 0) {
ret = EFAULT;
goto out;
}
out:
kmem_free(ioc, sizeof (mfi_ioctl_t));
return (ret);
}
static int
lmrc_mfi_aen_ioctl(lmrc_t *lmrc, mfi_aen_t *aen)
{
dev_err(lmrc->l_dip, CE_WARN, "!unimplemented ioctl: MFI AEN");
return (EINVAL);
}
static int
lmrc_aen_ioctl(lmrc_t *lmrc, intptr_t arg, int mode)
{
int ret = EINVAL;
mfi_aen_t aen;
if (ddi_copyin((void *)arg, &aen, sizeof (aen), mode) != 0)
return (EFAULT);
ret = lmrc_mfi_aen_ioctl(lmrc, &aen);
if (ret != 0)
goto out;
if (ddi_copyout(&aen, (void *)arg, sizeof (aen), mode) != 0)
return (EFAULT);
out:
return (ret);
}
int
lmrc_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp,
int *rval)
{
lmrc_t *lmrc;
int inst = MINOR2INST(getminor(dev));
int ret;
if (secpolicy_sys_config(credp, B_FALSE) != 0)
return (EPERM);
ret = scsi_hba_ioctl(dev, cmd, arg, mode, credp, rval);
if (ret != ENOTTY)
return (ret);
lmrc = ddi_get_soft_state(lmrc_state, inst);
if (lmrc == NULL)
return (ENXIO);
if (lmrc->l_fw_fault)
return (EIO);
switch ((uint_t)cmd) {
case MFI_IOCTL_FIRMWARE:
ret = lmrc_fw_ioctl(lmrc, arg, mode);
break;
case MFI_IOCTL_AEN:
ret = lmrc_aen_ioctl(lmrc, arg, mode);
break;
default:
ret = ENOTTY;
break;
}
return (ret);
}