|  | /* | 
|  | *  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 CLI request. */ | 
|  | void esas2r_build_cli_req(struct esas2r_adapter *a, | 
|  | struct esas2r_request *rq, | 
|  | u32 length, | 
|  | u32 cmd_rsp_len) | 
|  | { | 
|  | struct atto_vda_cli_req *vrq = &rq->vrq->cli; | 
|  |  | 
|  | clear_vda_request(rq); | 
|  |  | 
|  | rq->vrq->scsi.function = VDA_FUNC_CLI; | 
|  |  | 
|  | vrq->length = cpu_to_le32(length); | 
|  | vrq->cmd_rsp_len = cpu_to_le32(cmd_rsp_len); | 
|  | vrq->sg_list_offset = (u8)offsetof(struct atto_vda_cli_req, sge); | 
|  | } | 
|  |  | 
|  | /* 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); | 
|  | } |