root/sys/dev/smartpqi/smartpqi_ioctl.c
/*-
 * Copyright 2016-2025 Microchip Technology, Inc. and/or its subsidiaries.
 *
 * 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.
 *
 * 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.
 */


/*
 * Management interface for smartpqi driver
 */

#include "smartpqi_includes.h"

/*
 * Wrapper function to copy to user from kernel
 */
int
os_copy_to_user(struct pqisrc_softstate *softs, void *dest_buf,
                void *src_buf, int size, int mode)
{
        return(copyout(src_buf, dest_buf, size));
}

/*
 * Wrapper function to copy from user to kernel
 */
int
os_copy_from_user(struct pqisrc_softstate *softs, void *dest_buf,
                void *src_buf, int size, int mode)
{
        return(copyin(src_buf, dest_buf, size));
}

/*
 * Device open function for ioctl entry
 */
static int
smartpqi_open(struct cdev *cdev, int flags, int devtype,
                struct thread *td)
{
        return BSD_SUCCESS;
}

/*
 * Device close function for ioctl entry
 */
static int
smartpqi_close(struct cdev *cdev, int flags, int devtype,
                struct thread *td)
{
        return BSD_SUCCESS;
}

/*
 * ioctl for getting driver info
 */
static void
smartpqi_get_driver_info_ioctl(caddr_t udata, struct cdev const *cdev)
{
        struct pqisrc_softstate *softs = cdev->si_drv1;
        pdriver_info driver_info = (pdriver_info)udata;

        DBG_FUNC("IN udata = %p cdev = %p\n", udata, cdev);

        driver_info->major_version = PQISRC_DRIVER_MAJOR;
#if __FreeBSD__ <= 14
        driver_info->minor_version = (unsigned char) ((PQISRC_DRIVER_MINOR >> 4) & 0xFF);
#else
        driver_info->minor_version = PQISRC_DRIVER_MINOR;
#endif
        driver_info->release_version = PQISRC_DRIVER_RELEASE;
        driver_info->build_revision = PQISRC_DRIVER_REVISION;
        driver_info->max_targets = PQI_MAX_DEVICES - 1;
        driver_info->max_io = softs->max_io_for_scsi_ml;
        driver_info->max_transfer_length = softs->pqi_cap.max_transfer_size;

        DBG_FUNC("OUT\n");
}

/*
 * ioctl for getting controller info
 */
static void
smartpqi_get_pci_info_ioctl(caddr_t udata, struct cdev const *cdev)
{
        struct pqisrc_softstate *softs = cdev->si_drv1;
        device_t dev = softs->os_specific.pqi_dev;
        pqi_pci_info_t *pci_info = (pqi_pci_info_t *)udata;
        uint32_t sub_vendor = 0;
        uint32_t sub_device = 0;
        uint32_t vendor = 0;
        uint32_t device = 0;

        DBG_FUNC("IN udata = %p cdev = %p\n", udata, cdev);

        pci_info->bus = pci_get_bus(dev);
        pci_info->dev_fn = pci_get_function(dev);
        pci_info->domain = pci_get_domain(dev);
        sub_vendor = pci_read_config(dev, PCIR_SUBVEND_0, 2);
        sub_device = pci_read_config(dev, PCIR_SUBDEV_0, 2);
        pci_info->board_id = ((sub_device << 16) & 0xffff0000) | sub_vendor;
        vendor = pci_get_vendor(dev);
        device =  pci_get_device(dev);
        pci_info->chip_id = ((device << 16) & 0xffff0000) | vendor;

        DBG_FUNC("OUT\n");
}

static inline int
pqi_status_to_bsd_ioctl_status(int pqi_status)
{
        if (PQI_STATUS_SUCCESS == pqi_status)
                return BSD_SUCCESS;
        else
                return EIO;
}

/*
 * ioctl entry point for user
 */
static int
smartpqi_ioctl(struct cdev *cdev, u_long cmd, caddr_t udata,
                int flags, struct thread *td)
{
        int bsd_status, pqi_status;
        struct pqisrc_softstate *softs = cdev->si_drv1;

        DBG_FUNC("IN cmd = 0x%lx udata = %p cdev = %p\n", cmd, udata, cdev);

        if (!udata) {
                DBG_ERR("udata is null !!\n");
                return EINVAL;
        }

        if (pqisrc_ctrl_offline(softs)){
                return ENOTTY;
        }

        switch (cmd) {
                case CCISS_GETDRIVVER:
                        smartpqi_get_driver_info_ioctl(udata, cdev);
                        bsd_status = BSD_SUCCESS;
                        break;
                case CCISS_GETPCIINFO:
                        smartpqi_get_pci_info_ioctl(udata, cdev);
                        bsd_status = BSD_SUCCESS;
                        break;
                case SMARTPQI_PASS_THRU:
                case CCISS_PASSTHRU:
                        pqi_status = pqisrc_passthru_ioctl(softs, udata, 0);
                        bsd_status = pqi_status_to_bsd_ioctl_status(pqi_status);
                        break;
                case SMARTPQI_BIG_PASS_THRU:
                        pqi_status = pqisrc_big_passthru_ioctl(softs, udata, 0);
                        bsd_status = pqi_status_to_bsd_ioctl_status(pqi_status);
                        break;
                case SMARTPQI_BIG_PASSTHRU_SUPPORTED:
                        bsd_status = BSD_SUCCESS;
                        break;
                case CCISS_REGNEWD:
                        pqi_status = pqisrc_scan_devices(softs);
                        bsd_status = pqi_status_to_bsd_ioctl_status(pqi_status);
                        break;
                default:
                        DBG_WARN( "!IOCTL cmd 0x%lx not supported\n", cmd);
                        bsd_status = ENOTTY;
                        break;
        }

        DBG_FUNC("OUT error = %d\n", bsd_status);

        return bsd_status;
}

static struct cdevsw smartpqi_cdevsw =
{
        .d_version = D_VERSION,
        .d_open    = smartpqi_open,
        .d_close   = smartpqi_close,
        .d_ioctl   = smartpqi_ioctl,
        .d_name    = "smartpqi",
};

/*
 * Function to create device node for ioctl
 */
int
create_char_dev(struct pqisrc_softstate *softs, int card_index)
{
        int error = BSD_SUCCESS;

        DBG_FUNC("IN idx = %d\n", card_index);

        softs->os_specific.cdev = make_dev(&smartpqi_cdevsw, card_index,
                                UID_ROOT, GID_OPERATOR, 0640,
                                "smartpqi%u", card_index);
        if(softs->os_specific.cdev) {
                softs->os_specific.cdev->si_drv1 = softs;
        } else {
                error = ENXIO;
        }

        DBG_FUNC("OUT error = %d\n", error);

        return error;
}

/*
 * Function to destroy device node for ioctl
 */
void
destroy_char_dev(struct pqisrc_softstate *softs)
{
        DBG_FUNC("IN\n");
        if (softs->os_specific.cdev) {
                destroy_dev(softs->os_specific.cdev);
                softs->os_specific.cdev = NULL;
        }
        DBG_FUNC("OUT\n");
}

/*
 * Function used to send passthru commands to adapter
 * to support management tools. For eg. ssacli, sscon.
 */
int
pqisrc_passthru_ioctl(struct pqisrc_softstate *softs, void *arg, int mode)
{
        int ret;
        char *drv_buf = NULL;
        uint32_t tag = 0;
        IOCTL_Command_struct *iocommand = (IOCTL_Command_struct *)arg;
        dma_mem_t ioctl_dma_buf;
        pqisrc_raid_req_t request;
        raid_path_error_info_elem_t error_info;
        ib_queue_t *ib_q = &softs->op_raid_ib_q[PQI_DEFAULT_IB_QUEUE];
        ob_queue_t const *ob_q = &softs->op_ob_q[PQI_DEFAULT_IB_QUEUE];
        rcb_t *rcb = NULL;

        memset(&request, 0, sizeof(request));
        memset(&error_info, 0, sizeof(error_info));

        DBG_FUNC("IN\n");

        if (pqisrc_ctrl_offline(softs))
                return PQI_STATUS_FAILURE;

        if (!arg)
                return PQI_STATUS_FAILURE;

        if (iocommand->buf_size < 1 &&
                iocommand->Request.Type.Direction != PQIIOCTL_NONE)
                return PQI_STATUS_FAILURE;
        if (iocommand->Request.CDBLen > sizeof(request.cmd.cdb))
                return PQI_STATUS_FAILURE;

        switch (iocommand->Request.Type.Direction) {
                case PQIIOCTL_NONE:
                case PQIIOCTL_WRITE:
                case PQIIOCTL_READ:
                case PQIIOCTL_BIDIRECTIONAL:
                        break;
                default:
                        return PQI_STATUS_FAILURE;
        }

        if (iocommand->buf_size > 0) {
                memset(&ioctl_dma_buf, 0, sizeof(struct dma_mem));
                os_strlcpy(ioctl_dma_buf.tag, "Ioctl_PassthruCmd_Buffer", sizeof(ioctl_dma_buf.tag));
                ioctl_dma_buf.size = iocommand->buf_size;
                ioctl_dma_buf.align = PQISRC_DEFAULT_DMA_ALIGN;
                /* allocate memory */
                ret = os_dma_mem_alloc(softs, &ioctl_dma_buf);
                if (ret) {
                        DBG_ERR("Failed to Allocate dma mem for Ioctl PassthruCmd Buffer : %d\n", ret);
                        goto out;
                }

                DBG_IO("ioctl_dma_buf.dma_addr  = %p\n",(void*)ioctl_dma_buf.dma_addr);
                DBG_IO("ioctl_dma_buf.virt_addr = %p\n",(void*)ioctl_dma_buf.virt_addr);

                drv_buf = (char *)ioctl_dma_buf.virt_addr;
                if (iocommand->Request.Type.Direction & PQIIOCTL_WRITE) {
                        ret = os_copy_from_user(softs, (void *)drv_buf, (void *)iocommand->buf, iocommand->buf_size, mode);
                        if (ret != 0) {
                                goto free_mem;
                        }
                }
        }

        request.header.iu_type = PQI_IU_TYPE_RAID_PATH_IO_REQUEST;
        request.header.iu_length = offsetof(pqisrc_raid_req_t, sg_descriptors[1]) -
                                                                        PQI_REQUEST_HEADER_LENGTH;
        memcpy(request.lun_number, iocommand->LUN_info.LunAddrBytes,
                sizeof(request.lun_number));
        memcpy(request.cmd.cdb, iocommand->Request.CDB, iocommand->Request.CDBLen);
        request.additional_cdb_bytes_usage = PQI_ADDITIONAL_CDB_BYTES_0;

        switch (iocommand->Request.Type.Direction) {
        case PQIIOCTL_NONE:
                request.data_direction = SOP_DATA_DIR_NONE;
                break;
        case PQIIOCTL_WRITE:
                request.data_direction = SOP_DATA_DIR_FROM_DEVICE;
                break;
        case PQIIOCTL_READ:
                request.data_direction = SOP_DATA_DIR_TO_DEVICE;
                break;
        case PQIIOCTL_BIDIRECTIONAL:
                request.data_direction = SOP_DATA_DIR_BIDIRECTIONAL;
                break;
        }

        request.task_attribute = SOP_TASK_ATTRIBUTE_SIMPLE;
        if (iocommand->buf_size > 0) {
                request.buffer_length = iocommand->buf_size;
                request.sg_descriptors[0].addr = ioctl_dma_buf.dma_addr;
                request.sg_descriptors[0].len = iocommand->buf_size;
                request.sg_descriptors[0].flags =  SG_FLAG_LAST;
        }
        tag = pqisrc_get_tag(&softs->taglist);
        if (INVALID_ELEM == tag) {
                DBG_ERR("Tag not available\n");
                goto free_mem;
        }
        request.request_id = tag;
        request.response_queue_id = ob_q->q_id;
        request.error_index = request.request_id;
        if (softs->timeout_in_passthrough) {
                request.timeout_in_sec = iocommand->Request.Timeout;
        }

        rcb = &softs->rcb[tag];
        rcb->success_cmp_callback = pqisrc_process_internal_raid_response_success;
        rcb->error_cmp_callback = pqisrc_process_internal_raid_response_error;
        rcb->tag = tag;
        rcb->req_pending = true;
        /* Submit Command */
        ret = pqisrc_submit_cmnd(softs, ib_q, &request);
        if (ret != PQI_STATUS_SUCCESS) {
                DBG_ERR("Unable to submit command\n");
                goto err_out;
        }

        ret = pqisrc_wait_on_condition(softs, rcb, PQISRC_PASSTHROUGH_CMD_TIMEOUT);
        if (ret != PQI_STATUS_SUCCESS) {
                DBG_ERR("Passthru IOCTL cmd timed out !!\n");
                goto err_out;
        }

        memset(&iocommand->error_info, 0, sizeof(iocommand->error_info));


        if (rcb->status) {
                size_t sense_data_length;

                memcpy(&error_info, rcb->error_info, sizeof(error_info));
                iocommand->error_info.ScsiStatus = error_info.status;
                sense_data_length = error_info.sense_data_len;

                if (!sense_data_length)
                        sense_data_length = error_info.resp_data_len;

                if (sense_data_length &&
                        (sense_data_length > sizeof(error_info.data)))
                                sense_data_length = sizeof(error_info.data);

                if (sense_data_length) {
                        if (sense_data_length >
                                sizeof(iocommand->error_info.SenseInfo))
                                sense_data_length =
                                        sizeof(iocommand->error_info.SenseInfo);
                        memcpy (iocommand->error_info.SenseInfo,
                                        error_info.data, sense_data_length);
                        iocommand->error_info.SenseLen = sense_data_length;
                }

                if (error_info.data_out_result == PQI_RAID_DATA_IN_OUT_UNDERFLOW) {
                        rcb->status = PQI_STATUS_SUCCESS;
                }
        }

        if (rcb->status == PQI_STATUS_SUCCESS && iocommand->buf_size > 0 &&
                (iocommand->Request.Type.Direction & PQIIOCTL_READ)) {

                ret = os_copy_to_user(softs, (void*)iocommand->buf, (void*)drv_buf, iocommand->buf_size, mode);
                if (ret != 0) {
                        DBG_ERR("Failed to copy the response\n");
                        goto err_out;
                }
        }

        os_reset_rcb(rcb);
        pqisrc_put_tag(&softs->taglist, request.request_id);
        if (iocommand->buf_size > 0)
                os_dma_mem_free(softs,&ioctl_dma_buf);

        DBG_FUNC("OUT\n");
        return PQI_STATUS_SUCCESS;

err_out:
        os_reset_rcb(rcb);
        pqisrc_put_tag(&softs->taglist, request.request_id);

free_mem:
        if (iocommand->buf_size > 0)
                os_dma_mem_free(softs, &ioctl_dma_buf);

out:
        DBG_FUNC("Failed OUT\n");
        return PQI_STATUS_FAILURE;
}

/*
 * Function used to send big passthru commands to adapter
 * to support management tools. For eg. ssacli, sscon.
 */
int
pqisrc_big_passthru_ioctl(struct pqisrc_softstate *softs, void *arg, int mode)
{
        int ret;
        char *drv_buf = NULL;
        uint32_t tag = 0;
        BIG_IOCTL_Command_struct *iocommand = (BIG_IOCTL_Command_struct *)arg;
        dma_mem_t ioctl_dma_buf;
        pqisrc_raid_req_t request;
        raid_path_error_info_elem_t error_info;
        ib_queue_t *ib_q = &softs->op_raid_ib_q[PQI_DEFAULT_IB_QUEUE];
        ob_queue_t const *ob_q = &softs->op_ob_q[PQI_DEFAULT_IB_QUEUE];
        rcb_t *rcb = NULL;

        memset(&request, 0, sizeof(request));
        memset(&error_info, 0, sizeof(error_info));

        DBG_FUNC("IN\n");

        if (pqisrc_ctrl_offline(softs))
                return PQI_STATUS_FAILURE;

        if (!arg)
                return PQI_STATUS_FAILURE;

        if (iocommand->buf_size < 1 &&
                iocommand->Request.Type.Direction != PQIIOCTL_NONE)
                return PQI_STATUS_FAILURE;
        if (iocommand->Request.CDBLen > sizeof(request.cmd.cdb))
                return PQI_STATUS_FAILURE;

        switch (iocommand->Request.Type.Direction) {
                case PQIIOCTL_NONE:
                case PQIIOCTL_WRITE:
                case PQIIOCTL_READ:
                case PQIIOCTL_BIDIRECTIONAL:
                        break;
                default:
                        return PQI_STATUS_FAILURE;
        }

        if (iocommand->buf_size > 0) {
                memset(&ioctl_dma_buf, 0, sizeof(struct dma_mem));
                os_strlcpy(ioctl_dma_buf.tag, "Ioctl_PassthruCmd_Buffer", sizeof(ioctl_dma_buf.tag));
                ioctl_dma_buf.size = iocommand->buf_size;
                ioctl_dma_buf.align = PQISRC_DEFAULT_DMA_ALIGN;
                /* allocate memory */
                ret = os_dma_mem_alloc(softs, &ioctl_dma_buf);
                if (ret) {
                        DBG_ERR("Failed to Allocate dma mem for Ioctl PassthruCmd Buffer : %d\n", ret);
                        goto out;
                }

                DBG_IO("ioctl_dma_buf.dma_addr  = %p\n",(void*)ioctl_dma_buf.dma_addr);
                DBG_IO("ioctl_dma_buf.virt_addr = %p\n",(void*)ioctl_dma_buf.virt_addr);

                drv_buf = (char *)ioctl_dma_buf.virt_addr;
                if (iocommand->Request.Type.Direction & PQIIOCTL_WRITE) {
                        ret = os_copy_from_user(softs, (void *)drv_buf, (void *)iocommand->buf, iocommand->buf_size, mode);
                        if (ret != 0) {
                                goto free_mem;
                        }
                }
        }

        request.header.iu_type = PQI_IU_TYPE_RAID_PATH_IO_REQUEST;
        request.header.iu_length = offsetof(pqisrc_raid_req_t, sg_descriptors[1]) -
                                                                        PQI_REQUEST_HEADER_LENGTH;
        memcpy(request.lun_number, iocommand->LUN_info.LunAddrBytes,
                sizeof(request.lun_number));
        memcpy(request.cmd.cdb, iocommand->Request.CDB, iocommand->Request.CDBLen);
        request.additional_cdb_bytes_usage = PQI_ADDITIONAL_CDB_BYTES_0;

        switch (iocommand->Request.Type.Direction) {
        case PQIIOCTL_NONE:
                request.data_direction = SOP_DATA_DIR_NONE;
                break;
        case PQIIOCTL_WRITE:
                request.data_direction = SOP_DATA_DIR_FROM_DEVICE;
                break;
        case PQIIOCTL_READ:
                request.data_direction = SOP_DATA_DIR_TO_DEVICE;
                break;
        case PQIIOCTL_BIDIRECTIONAL:
                request.data_direction = SOP_DATA_DIR_BIDIRECTIONAL;
                break;
        }

        request.task_attribute = SOP_TASK_ATTRIBUTE_SIMPLE;
        if (iocommand->buf_size > 0) {
                request.buffer_length = iocommand->buf_size;
                request.sg_descriptors[0].addr = ioctl_dma_buf.dma_addr;
                request.sg_descriptors[0].len = iocommand->buf_size;
                request.sg_descriptors[0].flags =  SG_FLAG_LAST;
        }
        tag = pqisrc_get_tag(&softs->taglist);
        if (INVALID_ELEM == tag) {
                DBG_ERR("Tag not available\n");
                goto free_mem;
        }
        request.request_id = tag;
        request.response_queue_id = ob_q->q_id;
        request.error_index = request.request_id;
        if (softs->timeout_in_passthrough) {
                request.timeout_in_sec = iocommand->Request.Timeout;
        }

        rcb = &softs->rcb[tag];
        rcb->success_cmp_callback = pqisrc_process_internal_raid_response_success;
        rcb->error_cmp_callback = pqisrc_process_internal_raid_response_error;
        rcb->tag = tag;
        rcb->req_pending = true;
        /* Submit Command */
        ret = pqisrc_submit_cmnd(softs, ib_q, &request);
        if (ret != PQI_STATUS_SUCCESS) {
                DBG_ERR("Unable to submit command\n");
                goto err_out;
        }

        ret = pqisrc_wait_on_condition(softs, rcb, PQISRC_PASSTHROUGH_CMD_TIMEOUT);
        if (ret != PQI_STATUS_SUCCESS) {
                DBG_ERR("Passthru IOCTL cmd timed out !!\n");
                goto err_out;
        }

        memset(&iocommand->error_info, 0, sizeof(iocommand->error_info));


        if (rcb->status) {
                size_t sense_data_length;

                memcpy(&error_info, rcb->error_info, sizeof(error_info));
                iocommand->error_info.ScsiStatus = error_info.status;
                sense_data_length = error_info.sense_data_len;

                if (!sense_data_length)
                        sense_data_length = error_info.resp_data_len;

                if (sense_data_length &&
                        (sense_data_length > sizeof(error_info.data)))
                                sense_data_length = sizeof(error_info.data);

                if (sense_data_length) {
                        if (sense_data_length >
                                sizeof(iocommand->error_info.SenseInfo))
                                sense_data_length =
                                        sizeof(iocommand->error_info.SenseInfo);
                        memcpy (iocommand->error_info.SenseInfo,
                                        error_info.data, sense_data_length);
                        iocommand->error_info.SenseLen = sense_data_length;
                }

                if (error_info.data_out_result == PQI_RAID_DATA_IN_OUT_UNDERFLOW) {
                        rcb->status = PQI_STATUS_SUCCESS;
                }
        }

        if (rcb->status == PQI_STATUS_SUCCESS && iocommand->buf_size > 0 &&
                (iocommand->Request.Type.Direction & PQIIOCTL_READ)) {

                ret = os_copy_to_user(softs, (void*)iocommand->buf, (void*)drv_buf, iocommand->buf_size, mode);
                if (ret != 0) {
                        DBG_ERR("Failed to copy the response\n");
                        goto err_out;
                }
        }

        os_reset_rcb(rcb);
        pqisrc_put_tag(&softs->taglist, request.request_id);
        if (iocommand->buf_size > 0)
                os_dma_mem_free(softs,&ioctl_dma_buf);

        DBG_FUNC("OUT\n");
        return PQI_STATUS_SUCCESS;

err_out:
        os_reset_rcb(rcb);
        pqisrc_put_tag(&softs->taglist, request.request_id);

free_mem:
        if (iocommand->buf_size > 0)
                os_dma_mem_free(softs, &ioctl_dma_buf);

out:
        DBG_FUNC("Failed OUT\n");
        return PQI_STATUS_FAILURE;