#include <sys/cdefs.h>
#include <sys/abi_compat.h>
#include <dev/mrsas/mrsas.h>
#include <dev/mrsas/mrsas_ioctl.h>
struct mrsas_passthru_cmd {
struct mrsas_sge64 *kern_sge;
struct mrsas_softc *sc;
struct mrsas_mfi_cmd *cmd;
bus_dma_tag_t ioctl_data_tag;
bus_dmamap_t ioctl_data_dmamap;
u_int32_t error_code;
u_int32_t sge_count;
int complete;
};
int mrsas_alloc_mfi_cmds(struct mrsas_softc *sc);
int mrsas_passthru(struct mrsas_softc *sc, void *arg, u_long ioctlCmd);
void mrsas_free_ioc_cmd(struct mrsas_softc *sc);
void mrsas_free_frame(struct mrsas_softc *sc, struct mrsas_mfi_cmd *cmd);
void *mrsas_alloc_frame(struct mrsas_softc *sc, struct mrsas_mfi_cmd *cmd);
static int mrsas_create_frame_pool(struct mrsas_softc *sc);
static void
mrsas_alloc_cb(void *arg, bus_dma_segment_t *segs,
int nsegs, int error);
extern struct mrsas_mfi_cmd *mrsas_get_mfi_cmd(struct mrsas_softc *sc);
extern void mrsas_release_mfi_cmd(struct mrsas_mfi_cmd *cmd);
extern int
mrsas_issue_blocked_cmd(struct mrsas_softc *sc,
struct mrsas_mfi_cmd *cmd);
static void
mrsas_passthru_load_cb(void *arg, bus_dma_segment_t *segs, int nseg, int error)
{
struct mrsas_passthru_cmd *cb = (struct mrsas_passthru_cmd *)arg;
struct mrsas_softc *sc = cb->sc;
int i = 0;
if (error) {
cb->error_code = error;
if (error == EFBIG) {
device_printf(sc->mrsas_dev, "mrsas_passthru_load_cb: "
"error=%d EFBIG\n", error);
cb->complete = 1;
return;
} else {
device_printf(sc->mrsas_dev, "mrsas_passthru_load_cb: "
"error=%d UNKNOWN\n", error);
}
}
if (nseg > MAX_IOCTL_SGE) {
cb->error_code = EFBIG;
device_printf(sc->mrsas_dev, "mrsas_passthru_load_cb: "
"too many segments: %d\n", nseg);
cb->complete = 1;
return;
}
for (i = 0; i < nseg; i++) {
cb->kern_sge[i].phys_addr = htole64(segs[i].ds_addr);
cb->kern_sge[i].length = htole32(segs[i].ds_len);
}
cb->sge_count = nseg;
bus_dmamap_sync(cb->ioctl_data_tag, cb->ioctl_data_dmamap,
BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
cb->complete = 1;
}
int
mrsas_passthru(struct mrsas_softc *sc, void *arg, u_long ioctlCmd)
{
struct mrsas_iocpacket *user_ioc = (struct mrsas_iocpacket *)arg;
#ifdef COMPAT_FREEBSD32
struct mrsas_iocpacket32 *user_ioc32 = (struct mrsas_iocpacket32 *)arg;
#endif
union mrsas_frame *in_cmd = (union mrsas_frame *)&(user_ioc->frame.raw);
struct mrsas_mfi_cmd *cmd = NULL;
bus_dma_tag_t ioctl_data_tag[MAX_IOCTL_SGE];
bus_dmamap_t ioctl_data_dmamap[MAX_IOCTL_SGE];
void *ioctl_data_mem[MAX_IOCTL_SGE];
bus_addr_t ioctl_data_phys_addr[MAX_IOCTL_SGE];
bus_dma_tag_t ioctl_sense_tag = 0;
bus_dmamap_t ioctl_sense_dmamap = 0;
void *ioctl_sense_mem = NULL;
bus_addr_t ioctl_sense_phys_addr = 0;
int i, ioctl_data_size = 0, ioctl_sense_size, ret = 0;
struct mrsas_sge32 *kern_sge32;
unsigned long *sense_ptr;
uint8_t *iov_base_ptrin = NULL;
size_t iov_len = 0;
if (in_cmd->dcmd.opcode == 0) {
device_printf(sc->mrsas_dev, "In %s() Got a NOP\n", __func__);
user_ioc->frame.hdr.cmd_status = MFI_STAT_OK;
return (0);
}
if (user_ioc->sge_count > MAX_IOCTL_SGE) {
device_printf(sc->mrsas_dev, "In %s() SGL is too long (%d > 8).\n",
__func__, user_ioc->sge_count);
return (ENOENT);
}
cmd = mrsas_get_mfi_cmd(sc);
if (!cmd) {
device_printf(sc->mrsas_dev, "Failed to get a free cmd for IOCTL\n");
return (ENOMEM);
}
memcpy(cmd->frame, user_ioc->frame.raw, 2 * MEGAMFI_FRAME_SIZE);
cmd->frame->hdr.context = cmd->index;
cmd->frame->hdr.pad_0 = 0;
cmd->frame->hdr.flags &= ~(MFI_FRAME_IEEE | MFI_FRAME_SGL64 |
MFI_FRAME_SENSE64);
kern_sge32 = (struct mrsas_sge32 *)
((uintptr_t)cmd->frame + user_ioc->sgl_off);
memset(ioctl_data_tag, 0, (sizeof(bus_dma_tag_t) * MAX_IOCTL_SGE));
memset(ioctl_data_dmamap, 0, (sizeof(bus_dmamap_t) * MAX_IOCTL_SGE));
memset(ioctl_data_mem, 0, (sizeof(void *) * MAX_IOCTL_SGE));
memset(ioctl_data_phys_addr, 0, (sizeof(bus_addr_t) * MAX_IOCTL_SGE));
for (i = 0; i < user_ioc->sge_count; i++) {
if (ioctlCmd == MRSAS_IOC_FIRMWARE_PASS_THROUGH64) {
if (!user_ioc->sgl[i].iov_len)
continue;
ioctl_data_size = user_ioc->sgl[i].iov_len;
#ifdef COMPAT_FREEBSD32
} else {
if (!user_ioc32->sgl[i].iov_len)
continue;
ioctl_data_size = user_ioc32->sgl[i].iov_len;
#endif
}
if (bus_dma_tag_create(sc->mrsas_parent_tag,
1, 0,
BUS_SPACE_MAXADDR_32BIT,
BUS_SPACE_MAXADDR,
NULL, NULL,
ioctl_data_size,
1,
ioctl_data_size,
BUS_DMA_ALLOCNOW,
NULL, NULL,
&ioctl_data_tag[i])) {
device_printf(sc->mrsas_dev, "Cannot allocate ioctl data tag\n");
ret = ENOMEM;
goto out;
}
if (bus_dmamem_alloc(ioctl_data_tag[i], (void **)&ioctl_data_mem[i],
(BUS_DMA_NOWAIT | BUS_DMA_ZERO), &ioctl_data_dmamap[i])) {
device_printf(sc->mrsas_dev, "Cannot allocate ioctl data mem\n");
ret = ENOMEM;
goto out;
}
if (bus_dmamap_load(ioctl_data_tag[i], ioctl_data_dmamap[i],
ioctl_data_mem[i], ioctl_data_size, mrsas_alloc_cb,
&ioctl_data_phys_addr[i], BUS_DMA_NOWAIT)) {
device_printf(sc->mrsas_dev, "Cannot load ioctl data mem\n");
ret = ENOMEM;
goto out;
}
kern_sge32[i].phys_addr = (u_int32_t)ioctl_data_phys_addr[i];
if (ioctlCmd == MRSAS_IOC_FIRMWARE_PASS_THROUGH64) {
kern_sge32[i].length = user_ioc->sgl[i].iov_len;
iov_base_ptrin = user_ioc->sgl[i].iov_base;
iov_len = user_ioc->sgl[i].iov_len;
#ifdef COMPAT_FREEBSD32
} else {
kern_sge32[i].length = user_ioc32->sgl[i].iov_len;
iov_base_ptrin = PTRIN(user_ioc32->sgl[i].iov_base);
iov_len = user_ioc32->sgl[i].iov_len;
#endif
}
ret = copyin(iov_base_ptrin, ioctl_data_mem[i], iov_len);
if (ret) {
device_printf(sc->mrsas_dev, "IOCTL copyin failed!\n");
goto out;
}
}
ioctl_sense_size = user_ioc->sense_len;
if (user_ioc->sense_len) {
if (bus_dma_tag_create(sc->mrsas_parent_tag,
1, 0,
BUS_SPACE_MAXADDR_32BIT,
BUS_SPACE_MAXADDR,
NULL, NULL,
ioctl_sense_size,
1,
ioctl_sense_size,
BUS_DMA_ALLOCNOW,
NULL, NULL,
&ioctl_sense_tag)) {
device_printf(sc->mrsas_dev, "Cannot allocate ioctl sense tag\n");
ret = ENOMEM;
goto out;
}
if (bus_dmamem_alloc(ioctl_sense_tag, (void **)&ioctl_sense_mem,
(BUS_DMA_NOWAIT | BUS_DMA_ZERO), &ioctl_sense_dmamap)) {
device_printf(sc->mrsas_dev, "Cannot allocate ioctl sense mem\n");
ret = ENOMEM;
goto out;
}
if (bus_dmamap_load(ioctl_sense_tag, ioctl_sense_dmamap,
ioctl_sense_mem, ioctl_sense_size, mrsas_alloc_cb,
&ioctl_sense_phys_addr, BUS_DMA_NOWAIT)) {
device_printf(sc->mrsas_dev, "Cannot load ioctl sense mem\n");
ret = ENOMEM;
goto out;
}
sense_ptr =
(unsigned long *)((uintptr_t)cmd->frame + user_ioc->sense_off);
*sense_ptr = ioctl_sense_phys_addr;
}
cmd->sync_cmd = 1;
ret = mrsas_issue_blocked_cmd(sc, cmd);
if (ret == ETIMEDOUT) {
mrsas_dprint(sc, MRSAS_OCR,
"IOCTL command is timed out, initiating OCR\n");
sc->do_timedout_reset = MFI_DCMD_TIMEOUT_OCR;
ret = EAGAIN;
goto out;
}
cmd->sync_cmd = 0;
for (i = 0; i < user_ioc->sge_count; i++) {
if (ioctlCmd == MRSAS_IOC_FIRMWARE_PASS_THROUGH64) {
iov_base_ptrin = user_ioc->sgl[i].iov_base;
iov_len = user_ioc->sgl[i].iov_len;
#ifdef COMPAT_FREEBSD32
} else {
iov_base_ptrin = PTRIN(user_ioc32->sgl[i].iov_base);
iov_len = user_ioc32->sgl[i].iov_len;
#endif
}
ret = copyout(ioctl_data_mem[i], iov_base_ptrin, iov_len);
if (ret) {
device_printf(sc->mrsas_dev, "IOCTL copyout failed!\n");
goto out;
}
}
if (user_ioc->sense_len) {
sense_ptr = (unsigned long *)((uintptr_t)user_ioc->frame.raw +
user_ioc->sense_off);
ret = copyout(ioctl_sense_mem, (unsigned long *)(uintptr_t)*sense_ptr,
user_ioc->sense_len);
if (ret) {
device_printf(sc->mrsas_dev, "IOCTL sense copyout failed!\n");
goto out;
}
}
memcpy(&user_ioc->frame.hdr.cmd_status, &cmd->frame->hdr.cmd_status,
sizeof(u_int8_t));
out:
if (user_ioc->sense_len) {
if (ioctl_sense_phys_addr)
bus_dmamap_unload(ioctl_sense_tag, ioctl_sense_dmamap);
if (ioctl_sense_mem != NULL)
bus_dmamem_free(ioctl_sense_tag, ioctl_sense_mem, ioctl_sense_dmamap);
if (ioctl_sense_tag != NULL)
bus_dma_tag_destroy(ioctl_sense_tag);
}
for (i = 0; i < user_ioc->sge_count; i++) {
if (ioctlCmd == MRSAS_IOC_FIRMWARE_PASS_THROUGH64) {
if (!user_ioc->sgl[i].iov_len)
continue;
#ifdef COMPAT_FREEBSD32
} else {
if (!user_ioc32->sgl[i].iov_len)
continue;
#endif
}
if (ioctl_data_phys_addr[i])
bus_dmamap_unload(ioctl_data_tag[i], ioctl_data_dmamap[i]);
if (ioctl_data_mem[i] != NULL)
bus_dmamem_free(ioctl_data_tag[i], ioctl_data_mem[i],
ioctl_data_dmamap[i]);
if (ioctl_data_tag[i] != NULL)
bus_dma_tag_destroy(ioctl_data_tag[i]);
}
mrsas_release_mfi_cmd(cmd);
return (ret);
}
int
mrsas_user_command(struct mrsas_softc *sc, struct mfi_ioc_passthru *ioc)
{
struct mrsas_mfi_cmd *cmd;
struct mrsas_dcmd_frame *dcmd;
struct mrsas_passthru_cmd *passcmd;
bus_dma_tag_t ioctl_data_tag;
bus_dmamap_t ioctl_data_dmamap;
bus_addr_t ioctl_data_phys_addr;
struct mrsas_sge64 *kern_sge;
int ret, ioctl_data_size;
char *ioctl_temp_data_mem;
ret = 0;
ioctl_temp_data_mem = NULL;
passcmd = NULL;
ioctl_data_phys_addr = 0;
dcmd = NULL;
cmd = NULL;
ioctl_data_tag = NULL;
ioctl_data_dmamap = NULL;
ioctl_data_dmamap = NULL;
cmd = mrsas_get_mfi_cmd(sc);
if (!cmd) {
device_printf(sc->mrsas_dev,
"Failed to get a free cmd for IOCTL\n");
return(ENOMEM);
}
dcmd = (struct mrsas_dcmd_frame *)cmd->frame;
memcpy(dcmd, &ioc->ioc_frame, sizeof(struct mrsas_dcmd_frame));
ioctl_data_size = ioc->buf_size;
cmd->frame->hdr.context = cmd->index;
cmd->frame->hdr.pad_0 = 0;
cmd->frame->hdr.flags = MFI_FRAME_DIR_BOTH;
if (sizeof(bus_addr_t) == 8)
cmd->frame->hdr.flags |= MFI_FRAME_SGL64 | MFI_FRAME_SENSE64;
kern_sge = (struct mrsas_sge64 *)(&dcmd->sgl);
if (ioctl_data_size == 0) {
kern_sge[0].phys_addr = 0;
kern_sge[0].length = 0;
} else {
ioctl_temp_data_mem = malloc(ioc->buf_size, M_MRSAS, M_WAITOK);
ret = copyin(ioc->buf, ioctl_temp_data_mem, ioc->buf_size);
if (ret) {
device_printf(sc->mrsas_dev, "IOCTL copyin failed!\n");
goto out;
}
passcmd = malloc(sizeof(struct mrsas_passthru_cmd), M_MRSAS,
M_WAITOK);
passcmd->complete = 0;
passcmd->sc = sc;
passcmd->cmd = cmd;
passcmd->kern_sge = kern_sge;
if (bus_dma_tag_create(sc->mrsas_parent_tag,
1, 0,
BUS_SPACE_MAXADDR,
BUS_SPACE_MAXADDR,
NULL, NULL,
ioctl_data_size,
MAX_IOCTL_SGE,
ioctl_data_size,
BUS_DMA_ALLOCNOW,
busdma_lock_mutex,
&sc->ioctl_lock,
&ioctl_data_tag)) {
device_printf(sc->mrsas_dev,
"Cannot allocate ioctl data tag %d\n",
ioc->buf_size);
ret = ENOMEM;
goto out;
}
if (bus_dmamap_create(ioctl_data_tag, 0, &ioctl_data_dmamap)) {
device_printf(sc->mrsas_dev, "Cannot create ioctl "
"passthru dmamap\n");
ret = ENOMEM;
goto out;
}
passcmd->ioctl_data_tag = ioctl_data_tag;
passcmd->ioctl_data_dmamap = ioctl_data_dmamap;
if (bus_dmamap_load(ioctl_data_tag, ioctl_data_dmamap,
ioctl_temp_data_mem, ioc->buf_size, mrsas_passthru_load_cb,
passcmd, BUS_DMA_NOWAIT)) {
device_printf(sc->mrsas_dev, "Cannot load ioctl "
"passthru data mem%s %d\n", curproc->p_comm, ioctl_data_size);
ret = ENOMEM;
goto out;
}
while (passcmd->complete == 0) {
pause("mrsas_passthru", hz);
}
cmd->frame->dcmd.sge_count = passcmd->sge_count;
}
cmd->sync_cmd = 1;
mrsas_issue_blocked_cmd(sc, cmd);
cmd->sync_cmd = 0;
if (ioctl_data_size != 0) {
bus_dmamap_sync(ioctl_data_tag, ioctl_data_dmamap,
BUS_DMASYNC_POSTREAD);
ret = copyout(ioctl_temp_data_mem, ioc->buf, ioc->buf_size);
if (ret) {
device_printf(sc->mrsas_dev,
"IOCTL copyout failed!\n");
goto out;
}
}
memcpy(&ioc->ioc_frame.cmd_status, &cmd->frame->hdr.cmd_status,
sizeof(u_int8_t));
out:
if (ioctl_temp_data_mem)
free(ioctl_temp_data_mem, M_MRSAS);
if (passcmd)
free(passcmd, M_MRSAS);
if (ioctl_data_phys_addr) {
bus_dmamap_unload(ioctl_data_tag, ioctl_data_dmamap);
bus_dmamap_destroy(ioctl_data_tag, ioctl_data_dmamap);
}
if (ioctl_data_tag != NULL)
bus_dma_tag_destroy(ioctl_data_tag);
mrsas_release_mfi_cmd(cmd);
return(ret);
}
int
mrsas_alloc_mfi_cmds(struct mrsas_softc *sc)
{
int i, j;
u_int32_t max_cmd;
struct mrsas_mfi_cmd *cmd;
max_cmd = MRSAS_MAX_MFI_CMDS;
sc->mfi_cmd_list = malloc(sizeof(struct mrsas_mfi_cmd *) * max_cmd, M_MRSAS, M_NOWAIT);
if (!sc->mfi_cmd_list) {
device_printf(sc->mrsas_dev, "Cannot alloc memory for mfi_cmd cmd_list.\n");
return (ENOMEM);
}
memset(sc->mfi_cmd_list, 0, sizeof(struct mrsas_mfi_cmd *) * max_cmd);
for (i = 0; i < max_cmd; i++) {
sc->mfi_cmd_list[i] = malloc(sizeof(struct mrsas_mfi_cmd),
M_MRSAS, M_NOWAIT);
if (!sc->mfi_cmd_list[i]) {
for (j = 0; j < i; j++)
free(sc->mfi_cmd_list[j], M_MRSAS);
free(sc->mfi_cmd_list, M_MRSAS);
sc->mfi_cmd_list = NULL;
return (ENOMEM);
}
}
for (i = 0; i < max_cmd; i++) {
cmd = sc->mfi_cmd_list[i];
memset(cmd, 0, sizeof(struct mrsas_mfi_cmd));
cmd->index = i;
cmd->ccb_ptr = NULL;
cmd->sc = sc;
TAILQ_INSERT_TAIL(&(sc->mrsas_mfi_cmd_list_head), cmd, next);
}
if (mrsas_create_frame_pool(sc)) {
device_printf(sc->mrsas_dev, "Cannot allocate DMA frame pool.\n");
for (i = 0; i < MRSAS_MAX_MFI_CMDS; i++) {
cmd = sc->mfi_cmd_list[i];
mrsas_free_frame(sc, cmd);
}
if (sc->mficmd_frame_tag != NULL)
bus_dma_tag_destroy(sc->mficmd_frame_tag);
return (ENOMEM);
}
return (0);
}
static int
mrsas_create_frame_pool(struct mrsas_softc *sc)
{
int i;
struct mrsas_mfi_cmd *cmd;
if (bus_dma_tag_create(sc->mrsas_parent_tag,
1, 0,
BUS_SPACE_MAXADDR_32BIT,
BUS_SPACE_MAXADDR,
NULL, NULL,
MRSAS_MFI_FRAME_SIZE,
1,
MRSAS_MFI_FRAME_SIZE,
BUS_DMA_ALLOCNOW,
NULL, NULL,
&sc->mficmd_frame_tag)) {
device_printf(sc->mrsas_dev, "Cannot create MFI frame tag\n");
return (ENOMEM);
}
for (i = 0; i < MRSAS_MAX_MFI_CMDS; i++) {
cmd = sc->mfi_cmd_list[i];
cmd->frame = mrsas_alloc_frame(sc, cmd);
if (cmd->frame == NULL) {
device_printf(sc->mrsas_dev, "Cannot alloc MFI frame memory\n");
return (ENOMEM);
}
memset(cmd->frame, 0, MRSAS_MFI_FRAME_SIZE);
cmd->frame->io.context = cmd->index;
cmd->frame->io.pad_0 = 0;
}
return (0);
}
void *
mrsas_alloc_frame(struct mrsas_softc *sc, struct mrsas_mfi_cmd *cmd)
{
u_int32_t frame_size = MRSAS_MFI_FRAME_SIZE;
if (bus_dmamem_alloc(sc->mficmd_frame_tag, (void **)&cmd->frame_mem,
BUS_DMA_NOWAIT, &cmd->frame_dmamap)) {
device_printf(sc->mrsas_dev, "Cannot alloc MFI frame memory\n");
return (NULL);
}
if (bus_dmamap_load(sc->mficmd_frame_tag, cmd->frame_dmamap,
cmd->frame_mem, frame_size, mrsas_alloc_cb,
&cmd->frame_phys_addr, BUS_DMA_NOWAIT)) {
device_printf(sc->mrsas_dev, "Cannot load IO request memory\n");
return (NULL);
}
return (cmd->frame_mem);
}
static void
mrsas_alloc_cb(void *arg, bus_dma_segment_t *segs,
int nsegs, int error)
{
bus_addr_t *addr;
addr = arg;
*addr = segs[0].ds_addr;
}
void
mrsas_free_frame(struct mrsas_softc *sc, struct mrsas_mfi_cmd *cmd)
{
if (cmd->frame_phys_addr)
bus_dmamap_unload(sc->mficmd_frame_tag, cmd->frame_dmamap);
if (cmd->frame_mem != NULL)
bus_dmamem_free(sc->mficmd_frame_tag, cmd->frame_mem, cmd->frame_dmamap);
}