| /* | 
 |  * Copyright 2014 Cisco Systems, Inc.  All rights reserved. | 
 |  * | 
 |  * This program is free software; you may 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. | 
 |  * | 
 |  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | 
 |  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | 
 |  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | 
 |  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS | 
 |  * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN | 
 |  * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | 
 |  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | 
 |  * SOFTWARE. | 
 |  */ | 
 |  | 
 | #include <linux/errno.h> | 
 | #include <linux/pci.h> | 
 | #include <linux/slab.h> | 
 |  | 
 | #include <linux/interrupt.h> | 
 | #include <linux/workqueue.h> | 
 | #include <linux/spinlock.h> | 
 | #include <linux/mempool.h> | 
 | #include <scsi/scsi_tcq.h> | 
 | #include <linux/ctype.h> | 
 |  | 
 | #include "snic_io.h" | 
 | #include "snic.h" | 
 | #include "cq_enet_desc.h" | 
 | #include "snic_fwint.h" | 
 |  | 
 | /* | 
 |  * snic_handle_link : Handles link flaps. | 
 |  */ | 
 | void | 
 | snic_handle_link(struct work_struct *work) | 
 | { | 
 | 	struct snic *snic = container_of(work, struct snic, link_work); | 
 |  | 
 | 	if (snic->config.xpt_type == SNIC_DAS) | 
 | 		return; | 
 |  | 
 | 	snic->link_status = svnic_dev_link_status(snic->vdev); | 
 | 	snic->link_down_cnt = svnic_dev_link_down_cnt(snic->vdev); | 
 | 	SNIC_HOST_INFO(snic->shost, "Link Event: Link %s.\n", | 
 | 		       ((snic->link_status) ? "Up" : "Down")); | 
 |  | 
 | 	SNIC_ASSERT_NOT_IMPL(1); | 
 | } | 
 |  | 
 |  | 
 | /* | 
 |  * snic_ver_enc : Encodes version str to int | 
 |  * version string is similar to netmask string | 
 |  */ | 
 | static int | 
 | snic_ver_enc(const char *s) | 
 | { | 
 | 	int v[4] = {0}; | 
 | 	int  i = 0, x = 0; | 
 | 	char c; | 
 | 	const char *p = s; | 
 |  | 
 | 	/* validate version string */ | 
 | 	if ((strlen(s) > 15) || (strlen(s) < 7)) | 
 | 		goto end; | 
 |  | 
 | 	while ((c = *p++)) { | 
 | 		if (c == '.') { | 
 | 			i++; | 
 | 			continue; | 
 | 		} | 
 |  | 
 | 		if (i > 3 || !isdigit(c)) | 
 | 			goto end; | 
 |  | 
 | 		v[i] = v[i] * 10 + (c - '0'); | 
 | 	} | 
 |  | 
 | 	/* validate sub version numbers */ | 
 | 	for (i = 3; i >= 0; i--) | 
 | 		if (v[i] > 0xff) | 
 | 			goto end; | 
 |  | 
 | 	x |= (v[0] << 24) | v[1] << 16 | v[2] << 8 | v[3]; | 
 |  | 
 | end: | 
 | 	if (x == 0) { | 
 | 		SNIC_ERR("Invalid version string [%s].\n", s); | 
 |  | 
 | 		return -1; | 
 | 	} | 
 |  | 
 | 	return x; | 
 | } /* end of snic_ver_enc */ | 
 |  | 
 | /* | 
 |  * snic_qeueue_exch_ver_req : | 
 |  * | 
 |  * Queues Exchange Version Request, to communicate host information | 
 |  * in return, it gets firmware version details | 
 |  */ | 
 | int | 
 | snic_queue_exch_ver_req(struct snic *snic) | 
 | { | 
 | 	struct snic_req_info *rqi = NULL; | 
 | 	struct snic_host_req *req = NULL; | 
 | 	u32 ver = 0; | 
 | 	int ret = 0; | 
 |  | 
 | 	SNIC_HOST_INFO(snic->shost, "Exch Ver Req Preparing...\n"); | 
 |  | 
 | 	rqi = snic_req_init(snic, 0); | 
 | 	if (!rqi) { | 
 | 		SNIC_HOST_ERR(snic->shost, | 
 | 			      "Queuing Exch Ver Req failed, err = %d\n", | 
 | 			      ret); | 
 |  | 
 | 		ret = -ENOMEM; | 
 | 		goto error; | 
 | 	} | 
 |  | 
 | 	req = rqi_to_req(rqi); | 
 |  | 
 | 	/* Initialize snic_host_req */ | 
 | 	snic_io_hdr_enc(&req->hdr, SNIC_REQ_EXCH_VER, 0, SCSI_NO_TAG, | 
 | 			snic->config.hid, 0, (ulong)rqi); | 
 | 	ver = snic_ver_enc(SNIC_DRV_VERSION); | 
 | 	req->u.exch_ver.drvr_ver = cpu_to_le32(ver); | 
 | 	req->u.exch_ver.os_type = cpu_to_le32(SNIC_OS_LINUX); | 
 |  | 
 | 	snic_handle_untagged_req(snic, rqi); | 
 |  | 
 | 	ret = snic_queue_wq_desc(snic, req, sizeof(*req)); | 
 | 	if (ret) { | 
 | 		snic_release_untagged_req(snic, rqi); | 
 | 		SNIC_HOST_ERR(snic->shost, | 
 | 			      "Queuing Exch Ver Req failed, err = %d\n", | 
 | 			      ret); | 
 | 		goto error; | 
 | 	} | 
 |  | 
 | 	SNIC_HOST_INFO(snic->shost, "Exch Ver Req is issued. ret = %d\n", ret); | 
 |  | 
 | error: | 
 | 	return ret; | 
 | } /* end of snic_queue_exch_ver_req */ | 
 |  | 
 | /* | 
 |  * snic_io_exch_ver_cmpl_handler | 
 |  */ | 
 | int | 
 | snic_io_exch_ver_cmpl_handler(struct snic *snic, struct snic_fw_req *fwreq) | 
 | { | 
 | 	struct snic_req_info *rqi = NULL; | 
 | 	struct snic_exch_ver_rsp *exv_cmpl = &fwreq->u.exch_ver_cmpl; | 
 | 	u8 typ, hdr_stat; | 
 | 	u32 cmnd_id, hid, max_sgs; | 
 | 	ulong ctx = 0; | 
 | 	unsigned long flags; | 
 | 	int ret = 0; | 
 |  | 
 | 	SNIC_HOST_INFO(snic->shost, "Exch Ver Compl Received.\n"); | 
 | 	snic_io_hdr_dec(&fwreq->hdr, &typ, &hdr_stat, &cmnd_id, &hid, &ctx); | 
 | 	SNIC_BUG_ON(snic->config.hid != hid); | 
 | 	rqi = (struct snic_req_info *) ctx; | 
 |  | 
 | 	if (hdr_stat) { | 
 | 		SNIC_HOST_ERR(snic->shost, | 
 | 			      "Exch Ver Completed w/ err status %d\n", | 
 | 			      hdr_stat); | 
 |  | 
 | 		goto exch_cmpl_end; | 
 | 	} | 
 |  | 
 | 	spin_lock_irqsave(&snic->snic_lock, flags); | 
 | 	snic->fwinfo.fw_ver = le32_to_cpu(exv_cmpl->version); | 
 | 	snic->fwinfo.hid = le32_to_cpu(exv_cmpl->hid); | 
 | 	snic->fwinfo.max_concur_ios = le32_to_cpu(exv_cmpl->max_concur_ios); | 
 | 	snic->fwinfo.max_sgs_per_cmd = le32_to_cpu(exv_cmpl->max_sgs_per_cmd); | 
 | 	snic->fwinfo.max_io_sz = le32_to_cpu(exv_cmpl->max_io_sz); | 
 | 	snic->fwinfo.max_tgts = le32_to_cpu(exv_cmpl->max_tgts); | 
 | 	snic->fwinfo.io_tmo = le16_to_cpu(exv_cmpl->io_timeout); | 
 |  | 
 | 	SNIC_HOST_INFO(snic->shost, | 
 | 		       "vers %u hid %u max_concur_ios %u max_sgs_per_cmd %u max_io_sz %u max_tgts %u fw tmo %u\n", | 
 | 		       snic->fwinfo.fw_ver, | 
 | 		       snic->fwinfo.hid, | 
 | 		       snic->fwinfo.max_concur_ios, | 
 | 		       snic->fwinfo.max_sgs_per_cmd, | 
 | 		       snic->fwinfo.max_io_sz, | 
 | 		       snic->fwinfo.max_tgts, | 
 | 		       snic->fwinfo.io_tmo); | 
 |  | 
 | 	SNIC_HOST_INFO(snic->shost, | 
 | 		       "HBA Capabilities = 0x%x\n", | 
 | 		       le32_to_cpu(exv_cmpl->hba_cap)); | 
 |  | 
 | 	/* Updating SGList size */ | 
 | 	max_sgs = snic->fwinfo.max_sgs_per_cmd; | 
 | 	if (max_sgs && max_sgs < SNIC_MAX_SG_DESC_CNT) { | 
 | 		snic->shost->sg_tablesize = max_sgs; | 
 | 		SNIC_HOST_INFO(snic->shost, "Max SGs set to %d\n", | 
 | 			       snic->shost->sg_tablesize); | 
 | 	} else if (max_sgs > snic->shost->sg_tablesize) { | 
 | 		SNIC_HOST_INFO(snic->shost, | 
 | 			       "Target type %d Supports Larger Max SGList %d than driver's Max SG List %d.\n", | 
 | 			       snic->config.xpt_type, max_sgs, | 
 | 			       snic->shost->sg_tablesize); | 
 | 	} | 
 |  | 
 | 	if (snic->shost->can_queue > snic->fwinfo.max_concur_ios) | 
 | 		snic->shost->can_queue = snic->fwinfo.max_concur_ios; | 
 |  | 
 | 	snic->shost->max_sectors = snic->fwinfo.max_io_sz >> 9; | 
 | 	if (snic->fwinfo.wait) | 
 | 		complete(snic->fwinfo.wait); | 
 |  | 
 | 	spin_unlock_irqrestore(&snic->snic_lock, flags); | 
 |  | 
 | exch_cmpl_end: | 
 | 	snic_release_untagged_req(snic, rqi); | 
 |  | 
 | 	SNIC_HOST_INFO(snic->shost, "Exch_cmpl Done, hdr_stat %d.\n", hdr_stat); | 
 |  | 
 | 	return ret; | 
 | } /* end of snic_io_exch_ver_cmpl_handler */ | 
 |  | 
 | /* | 
 |  * snic_get_conf | 
 |  * | 
 |  * Synchronous call, and Retrieves snic params. | 
 |  */ | 
 | int | 
 | snic_get_conf(struct snic *snic) | 
 | { | 
 | 	DECLARE_COMPLETION_ONSTACK(wait); | 
 | 	unsigned long flags; | 
 | 	int ret; | 
 | 	int nr_retries = 3; | 
 |  | 
 | 	SNIC_HOST_INFO(snic->shost, "Retrieving snic params.\n"); | 
 | 	spin_lock_irqsave(&snic->snic_lock, flags); | 
 | 	memset(&snic->fwinfo, 0, sizeof(snic->fwinfo)); | 
 | 	snic->fwinfo.wait = &wait; | 
 | 	spin_unlock_irqrestore(&snic->snic_lock, flags); | 
 |  | 
 | 	/* Additional delay to handle HW Resource initialization. */ | 
 | 	msleep(50); | 
 |  | 
 | 	/* | 
 | 	 * Exch ver req can be ignored by FW, if HW Resource initialization | 
 | 	 * is in progress, Hence retry. | 
 | 	 */ | 
 | 	do { | 
 | 		ret = snic_queue_exch_ver_req(snic); | 
 | 		if (ret) | 
 | 			return ret; | 
 |  | 
 | 		wait_for_completion_timeout(&wait, msecs_to_jiffies(2000)); | 
 | 		spin_lock_irqsave(&snic->snic_lock, flags); | 
 | 		ret = (snic->fwinfo.fw_ver != 0) ? 0 : -ETIMEDOUT; | 
 | 		if (ret) | 
 | 			SNIC_HOST_ERR(snic->shost, | 
 | 				      "Failed to retrieve snic params,\n"); | 
 |  | 
 | 		/* Unset fwinfo.wait, on success or on last retry */ | 
 | 		if (ret == 0 || nr_retries == 1) | 
 | 			snic->fwinfo.wait = NULL; | 
 |  | 
 | 		spin_unlock_irqrestore(&snic->snic_lock, flags); | 
 | 	} while (ret && --nr_retries); | 
 |  | 
 | 	return ret; | 
 | } /* end of snic_get_info */ |