root/drivers/scsi/esas2r/esas2r_vda.c
/*
 *  linux/drivers/scsi/esas2r/esas2r_vda.c
 *      esas2r driver VDA firmware interface functions
 *
 *  Copyright (c) 2001-2013 ATTO Technology, Inc.
 *  (mailto:linuxdrivers@attotech.com)
 */
/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
/*
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; version 2 of the License.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  NO WARRANTY
 *  THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR
 *  CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT
 *  LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT,
 *  MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is
 *  solely responsible for determining the appropriateness of using and
 *  distributing the Program and assumes all risks associated with its
 *  exercise of rights under this Agreement, including but not limited to
 *  the risks and costs of program errors, damage to or loss of data,
 *  programs or equipment, and unavailability or interruption of operations.
 *
 *  DISCLAIMER OF LIABILITY
 *  NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY
 *  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 *  DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), 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 OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED
 *  HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/

#include "esas2r.h"

static u8 esas2r_vdaioctl_versions[] = {
        ATTO_VDA_VER_UNSUPPORTED,
        ATTO_VDA_FLASH_VER,
        ATTO_VDA_VER_UNSUPPORTED,
        ATTO_VDA_VER_UNSUPPORTED,
        ATTO_VDA_CLI_VER,
        ATTO_VDA_VER_UNSUPPORTED,
        ATTO_VDA_CFG_VER,
        ATTO_VDA_MGT_VER,
        ATTO_VDA_GSV_VER
};

static void clear_vda_request(struct esas2r_request *rq);

static void esas2r_complete_vda_ioctl(struct esas2r_adapter *a,
                                      struct esas2r_request *rq);

/* Prepare a VDA IOCTL request to be sent to the firmware. */
bool esas2r_process_vda_ioctl(struct esas2r_adapter *a,
                              struct atto_ioctl_vda *vi,
                              struct esas2r_request *rq,
                              struct esas2r_sg_context *sgc)
{
        u32 datalen = 0;
        struct atto_vda_sge *firstsg = NULL;
        u8 vercnt = (u8)ARRAY_SIZE(esas2r_vdaioctl_versions);

        vi->status = ATTO_STS_SUCCESS;
        vi->vda_status = RS_PENDING;

        if (vi->function >= vercnt) {
                vi->status = ATTO_STS_INV_FUNC;
                return false;
        }

        if (vi->version > esas2r_vdaioctl_versions[vi->function]) {
                vi->status = ATTO_STS_INV_VERSION;
                return false;
        }

        if (test_bit(AF_DEGRADED_MODE, &a->flags)) {
                vi->status = ATTO_STS_DEGRADED;
                return false;
        }

        if (vi->function != VDA_FUNC_SCSI)
                clear_vda_request(rq);

        rq->vrq->scsi.function = vi->function;
        rq->interrupt_cb = esas2r_complete_vda_ioctl;
        rq->interrupt_cx = vi;

        switch (vi->function) {
        case VDA_FUNC_FLASH:

                if (vi->cmd.flash.sub_func != VDA_FLASH_FREAD
                    && vi->cmd.flash.sub_func != VDA_FLASH_FWRITE
                    && vi->cmd.flash.sub_func != VDA_FLASH_FINFO) {
                        vi->status = ATTO_STS_INV_FUNC;
                        return false;
                }

                if (vi->cmd.flash.sub_func != VDA_FLASH_FINFO)
                        datalen = vi->data_length;

                rq->vrq->flash.length = cpu_to_le32(datalen);
                rq->vrq->flash.sub_func = vi->cmd.flash.sub_func;

                memcpy(rq->vrq->flash.data.file.file_name,
                       vi->cmd.flash.data.file.file_name,
                       sizeof(vi->cmd.flash.data.file.file_name));

                firstsg = rq->vrq->flash.data.file.sge;
                break;

        case VDA_FUNC_CLI:

                datalen = vi->data_length;

                rq->vrq->cli.cmd_rsp_len =
                        cpu_to_le32(vi->cmd.cli.cmd_rsp_len);
                rq->vrq->cli.length = cpu_to_le32(datalen);

                firstsg = rq->vrq->cli.sge;
                break;

        case VDA_FUNC_MGT:
        {
                u8 *cmdcurr_offset = sgc->cur_offset
                                     - offsetof(struct atto_ioctl_vda, data)
                                     + offsetof(struct atto_ioctl_vda, cmd)
                                     + offsetof(struct atto_ioctl_vda_mgt_cmd,
                                                data);
                /*
                 * build the data payload SGL here first since
                 * esas2r_sgc_init() will modify the S/G list offset for the
                 * management SGL (which is built below where the data SGL is
                 * usually built).
                 */

                if (vi->data_length) {
                        u32 payldlen = 0;

                        if (vi->cmd.mgt.mgt_func == VDAMGT_DEV_HEALTH_REQ
                            || vi->cmd.mgt.mgt_func == VDAMGT_DEV_METRICS) {
                                rq->vrq->mgt.payld_sglst_offset =
                                        (u8)offsetof(struct atto_vda_mgmt_req,
                                                     payld_sge);

                                payldlen = vi->data_length;
                                datalen = vi->cmd.mgt.data_length;
                        } else if (vi->cmd.mgt.mgt_func == VDAMGT_DEV_INFO2
                                   || vi->cmd.mgt.mgt_func ==
                                   VDAMGT_DEV_INFO2_BYADDR) {
                                datalen = vi->data_length;
                                cmdcurr_offset = sgc->cur_offset;
                        } else {
                                vi->status = ATTO_STS_INV_PARAM;
                                return false;
                        }

                        /* Setup the length so building the payload SGL works */
                        rq->vrq->mgt.length = cpu_to_le32(datalen);

                        if (payldlen) {
                                rq->vrq->mgt.payld_length =
                                        cpu_to_le32(payldlen);

                                esas2r_sgc_init(sgc, a, rq,
                                                rq->vrq->mgt.payld_sge);
                                sgc->length = payldlen;

                                if (!esas2r_build_sg_list(a, rq, sgc)) {
                                        vi->status = ATTO_STS_OUT_OF_RSRC;
                                        return false;
                                }
                        }
                } else {
                        datalen = vi->cmd.mgt.data_length;

                        rq->vrq->mgt.length = cpu_to_le32(datalen);
                }

                /*
                 * Now that the payload SGL is built, if any, setup to build
                 * the management SGL.
                 */
                firstsg = rq->vrq->mgt.sge;
                sgc->cur_offset = cmdcurr_offset;

                /* Finish initializing the management request. */
                rq->vrq->mgt.mgt_func = vi->cmd.mgt.mgt_func;
                rq->vrq->mgt.scan_generation = vi->cmd.mgt.scan_generation;
                rq->vrq->mgt.dev_index =
                        cpu_to_le32(vi->cmd.mgt.dev_index);

                esas2r_nuxi_mgt_data(rq->vrq->mgt.mgt_func, &vi->cmd.mgt.data);
                break;
        }

        case VDA_FUNC_CFG:

                if (vi->data_length
                    || vi->cmd.cfg.data_length == 0) {
                        vi->status = ATTO_STS_INV_PARAM;
                        return false;
                }

                if (vi->cmd.cfg.cfg_func == VDA_CFG_INIT) {
                        vi->status = ATTO_STS_INV_FUNC;
                        return false;
                }

                rq->vrq->cfg.sub_func = vi->cmd.cfg.cfg_func;
                rq->vrq->cfg.length = cpu_to_le32(vi->cmd.cfg.data_length);

                if (vi->cmd.cfg.cfg_func == VDA_CFG_GET_INIT) {
                        memcpy(&rq->vrq->cfg.data,
                               &vi->cmd.cfg.data,
                               vi->cmd.cfg.data_length);

                        esas2r_nuxi_cfg_data(rq->vrq->cfg.sub_func,
                                             &rq->vrq->cfg.data);
                } else {
                        vi->status = ATTO_STS_INV_FUNC;

                        return false;
                }

                break;

        case VDA_FUNC_GSV:

                vi->cmd.gsv.rsp_len = vercnt;

                memcpy(vi->cmd.gsv.version_info, esas2r_vdaioctl_versions,
                       vercnt);

                vi->vda_status = RS_SUCCESS;
                break;

        default:

                vi->status = ATTO_STS_INV_FUNC;
                return false;
        }

        if (datalen) {
                esas2r_sgc_init(sgc, a, rq, firstsg);
                sgc->length = datalen;

                if (!esas2r_build_sg_list(a, rq, sgc)) {
                        vi->status = ATTO_STS_OUT_OF_RSRC;
                        return false;
                }
        }

        esas2r_start_request(a, rq);

        return true;
}

static void esas2r_complete_vda_ioctl(struct esas2r_adapter *a,
                                      struct esas2r_request *rq)
{
        struct atto_ioctl_vda *vi = (struct atto_ioctl_vda *)rq->interrupt_cx;

        vi->vda_status = rq->req_stat;

        switch (vi->function) {
        case VDA_FUNC_FLASH:

                if (vi->cmd.flash.sub_func == VDA_FLASH_FINFO
                    || vi->cmd.flash.sub_func == VDA_FLASH_FREAD)
                        vi->cmd.flash.data.file.file_size =
                                le32_to_cpu(rq->func_rsp.flash_rsp.file_size);

                break;

        case VDA_FUNC_MGT:

                vi->cmd.mgt.scan_generation =
                        rq->func_rsp.mgt_rsp.scan_generation;
                vi->cmd.mgt.dev_index = le16_to_cpu(
                        rq->func_rsp.mgt_rsp.dev_index);

                if (vi->data_length == 0)
                        vi->cmd.mgt.data_length =
                                le32_to_cpu(rq->func_rsp.mgt_rsp.length);

                esas2r_nuxi_mgt_data(rq->vrq->mgt.mgt_func, &vi->cmd.mgt.data);
                break;

        case VDA_FUNC_CFG:

                if (vi->cmd.cfg.cfg_func == VDA_CFG_GET_INIT) {
                        struct atto_ioctl_vda_cfg_cmd *cfg = &vi->cmd.cfg;
                        struct atto_vda_cfg_rsp *rsp = &rq->func_rsp.cfg_rsp;
                        char buf[sizeof(cfg->data.init.fw_release) + 1];

                        cfg->data_length =
                                cpu_to_le32(sizeof(struct atto_vda_cfg_init));
                        cfg->data.init.vda_version =
                                le32_to_cpu(rsp->vda_version);
                        cfg->data.init.fw_build = rsp->fw_build;

                        snprintf(buf, sizeof(buf), "%1.1u.%2.2u",
                                 (int)LOBYTE(le16_to_cpu(rsp->fw_release)),
                                 (int)HIBYTE(le16_to_cpu(rsp->fw_release)));

                        memcpy(&cfg->data.init.fw_release, buf,
                               sizeof(cfg->data.init.fw_release));

                        if (LOWORD(LOBYTE(cfg->data.init.fw_build)) == 'A')
                                cfg->data.init.fw_version =
                                        cfg->data.init.fw_build;
                        else
                                cfg->data.init.fw_version =
                                        cfg->data.init.fw_release;
                } else {
                        esas2r_nuxi_cfg_data(rq->vrq->cfg.sub_func,
                                             &vi->cmd.cfg.data);
                }

                break;

        case VDA_FUNC_CLI:

                vi->cmd.cli.cmd_rsp_len =
                        le32_to_cpu(rq->func_rsp.cli_rsp.cmd_rsp_len);
                break;

        default:

                break;
        }
}

/* Build a flash VDA request. */
void esas2r_build_flash_req(struct esas2r_adapter *a,
                            struct esas2r_request *rq,
                            u8 sub_func,
                            u8 cksum,
                            u32 addr,
                            u32 length)
{
        struct atto_vda_flash_req *vrq = &rq->vrq->flash;

        clear_vda_request(rq);

        rq->vrq->scsi.function = VDA_FUNC_FLASH;

        if (sub_func == VDA_FLASH_BEGINW
            || sub_func == VDA_FLASH_WRITE
            || sub_func == VDA_FLASH_READ)
                vrq->sg_list_offset = (u8)offsetof(struct atto_vda_flash_req,
                                                   data.sge);

        vrq->length = cpu_to_le32(length);
        vrq->flash_addr = cpu_to_le32(addr);
        vrq->checksum = cksum;
        vrq->sub_func = sub_func;
}

/* Build a VDA management request. */
void esas2r_build_mgt_req(struct esas2r_adapter *a,
                          struct esas2r_request *rq,
                          u8 sub_func,
                          u8 scan_gen,
                          u16 dev_index,
                          u32 length,
                          void *data)
{
        struct atto_vda_mgmt_req *vrq = &rq->vrq->mgt;

        clear_vda_request(rq);

        rq->vrq->scsi.function = VDA_FUNC_MGT;

        vrq->mgt_func = sub_func;
        vrq->scan_generation = scan_gen;
        vrq->dev_index = cpu_to_le16(dev_index);
        vrq->length = cpu_to_le32(length);

        if (vrq->length) {
                if (test_bit(AF_LEGACY_SGE_MODE, &a->flags)) {
                        vrq->sg_list_offset = (u8)offsetof(
                                struct atto_vda_mgmt_req, sge);

                        vrq->sge[0].length = cpu_to_le32(SGE_LAST | length);
                        vrq->sge[0].address = cpu_to_le64(
                                rq->vrq_md->phys_addr +
                                sizeof(union atto_vda_req));
                } else {
                        vrq->sg_list_offset = (u8)offsetof(
                                struct atto_vda_mgmt_req, prde);

                        vrq->prde[0].ctl_len = cpu_to_le32(length);
                        vrq->prde[0].address = cpu_to_le64(
                                rq->vrq_md->phys_addr +
                                sizeof(union atto_vda_req));
                }
        }

        if (data) {
                esas2r_nuxi_mgt_data(sub_func, data);

                memcpy(&rq->vda_rsp_data->mgt_data.data.bytes[0], data,
                       length);
        }
}

/* Build a VDA asyncronous event (AE) request. */
void esas2r_build_ae_req(struct esas2r_adapter *a, struct esas2r_request *rq)
{
        struct atto_vda_ae_req *vrq = &rq->vrq->ae;

        clear_vda_request(rq);

        rq->vrq->scsi.function = VDA_FUNC_AE;

        vrq->length = cpu_to_le32(sizeof(struct atto_vda_ae_data));

        if (test_bit(AF_LEGACY_SGE_MODE, &a->flags)) {
                vrq->sg_list_offset =
                        (u8)offsetof(struct atto_vda_ae_req, sge);
                vrq->sge[0].length = cpu_to_le32(SGE_LAST | vrq->length);
                vrq->sge[0].address = cpu_to_le64(
                        rq->vrq_md->phys_addr +
                        sizeof(union atto_vda_req));
        } else {
                vrq->sg_list_offset = (u8)offsetof(struct atto_vda_ae_req,
                                                   prde);
                vrq->prde[0].ctl_len = cpu_to_le32(vrq->length);
                vrq->prde[0].address = cpu_to_le64(
                        rq->vrq_md->phys_addr +
                        sizeof(union atto_vda_req));
        }
}

/* Build a VDA IOCTL request. */
void esas2r_build_ioctl_req(struct esas2r_adapter *a,
                            struct esas2r_request *rq,
                            u32 length,
                            u8 sub_func)
{
        struct atto_vda_ioctl_req *vrq = &rq->vrq->ioctl;

        clear_vda_request(rq);

        rq->vrq->scsi.function = VDA_FUNC_IOCTL;

        vrq->length = cpu_to_le32(length);
        vrq->sub_func = sub_func;
        vrq->sg_list_offset = (u8)offsetof(struct atto_vda_ioctl_req, sge);
}

/* Build a VDA configuration request. */
void esas2r_build_cfg_req(struct esas2r_adapter *a,
                          struct esas2r_request *rq,
                          u8 sub_func,
                          u32 length,
                          void *data)
{
        struct atto_vda_cfg_req *vrq = &rq->vrq->cfg;

        clear_vda_request(rq);

        rq->vrq->scsi.function = VDA_FUNC_CFG;

        vrq->sub_func = sub_func;
        vrq->length = cpu_to_le32(length);

        if (data) {
                esas2r_nuxi_cfg_data(sub_func, data);

                memcpy(&vrq->data, data, length);
        }
}

static void clear_vda_request(struct esas2r_request *rq)
{
        u32 handle = rq->vrq->scsi.handle;

        memset(rq->vrq, 0, sizeof(*rq->vrq));

        rq->vrq->scsi.handle = handle;

        rq->req_stat = RS_PENDING;

        /* since the data buffer is separate clear that too */

        memset(rq->data_buf, 0, ESAS2R_DATA_BUF_LEN);

        /*
         * Setup next and prev pointer in case the request is not going through
         * esas2r_start_request().
         */

        INIT_LIST_HEAD(&rq->req_list);
}