diff options
Diffstat (limited to 'drivers/scsi/esas2r/esas2r_vda.c')
| -rw-r--r-- | drivers/scsi/esas2r/esas2r_vda.c | 524 | 
1 files changed, 524 insertions, 0 deletions
diff --git a/drivers/scsi/esas2r/esas2r_vda.c b/drivers/scsi/esas2r/esas2r_vda.c new file mode 100644 index 00000000000..30028e56df6 --- /dev/null +++ b/drivers/scsi/esas2r/esas2r_vda.c @@ -0,0 +1,524 @@ +/* + *  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); +}  | 
