int fsi_occ_submit()

in fsi-occ.c [454:565]


int fsi_occ_submit(struct device *dev, const void *request, size_t req_len,
		   void *response, size_t *resp_len)
{
	const unsigned long timeout = msecs_to_jiffies(OCC_TIMEOUT_MS);
	const unsigned long wait_time =
		msecs_to_jiffies(OCC_CMD_IN_PRG_WAIT_MS);
	struct occ *occ = dev_get_drvdata(dev);
	struct occ_response *resp = response;
	size_t user_resp_len = *resp_len;
	u8 seq_no;
	u16 checksum = 0;
	u16 resp_data_length;
	const u8 *byte_request = (const u8 *)request;
	unsigned long start;
	int rc;
	size_t i;

	*resp_len = 0;

	if (!occ)
		return -ENODEV;

	if (user_resp_len < 7) {
		dev_dbg(dev, "Bad resplen %zd\n", user_resp_len);
		return -EINVAL;
	}

	/* Checksum the request, ignoring first byte (sequence number). */
	for (i = 1; i < req_len - 2; ++i)
		checksum += byte_request[i];

	mutex_lock(&occ->occ_lock);

	occ->client_buffer = response;
	occ->client_buffer_size = user_resp_len;
	occ->client_response_size = 0;

	/*
	 * Get a sequence number and update the counter. Avoid a sequence
	 * number of 0 which would pass the response check below even if the
	 * OCC response is uninitialized. Any sequence number the user is
	 * trying to send is overwritten since this function is the only common
	 * interface to the OCC and therefore the only place we can guarantee
	 * unique sequence numbers.
	 */
	seq_no = occ->sequence_number++;
	if (!occ->sequence_number)
		occ->sequence_number = 1;
	checksum += seq_no;

	rc = occ_putsram(occ, request, req_len, seq_no, checksum);
	if (rc)
		goto done;

	rc = occ_trigger_attn(occ);
	if (rc)
		goto done;

	/* Read occ response header */
	start = jiffies;
	do {
		rc = occ_getsram(occ, 0, resp, 8);
		if (rc)
			goto done;

		if (resp->return_status == OCC_RESP_CMD_IN_PRG ||
		    resp->return_status == OCC_RESP_CRIT_INIT ||
		    resp->seq_no != seq_no) {
			rc = -ETIMEDOUT;

			if (time_after(jiffies, start + timeout)) {
				dev_err(occ->dev, "resp timeout status=%02x "
					"resp seq_no=%d our seq_no=%d\n",
					resp->return_status, resp->seq_no,
					seq_no);
				goto done;
			}

			set_current_state(TASK_UNINTERRUPTIBLE);
			schedule_timeout(wait_time);
		}
	} while (rc);

	/* Extract size of response data */
	resp_data_length = get_unaligned_be16(&resp->data_length);

	/* Message size is data length + 5 bytes header + 2 bytes checksum */
	if ((resp_data_length + 7) > user_resp_len) {
		rc = -EMSGSIZE;
		goto done;
	}

	dev_dbg(dev, "resp_status=%02x resp_data_len=%d\n",
		resp->return_status, resp_data_length);

	/* Grab the rest */
	if (resp_data_length > 1) {
		/* already got 3 bytes resp, also need 2 bytes checksum */
		rc = occ_getsram(occ, 8, &resp->data[3], resp_data_length - 1);
		if (rc)
			goto done;
	}

	occ->client_response_size = resp_data_length + 7;
	rc = occ_verify_checksum(occ, resp, resp_data_length);

 done:
	*resp_len = occ->client_response_size;
	mutex_unlock(&occ->occ_lock);

	return rc;
}