Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2f4df8ba authored by Linux Build Service Account's avatar Linux Build Service Account Committed by Gerrit - the friendly Code Review server
Browse files

Merge "mmc: block: Error handling for WP violation in cmdq"

parents 52b37df9 dd812cfe
Loading
Loading
Loading
Loading
+217 −10
Original line number Diff line number Diff line
@@ -2983,8 +2983,6 @@ static void mmc_blk_cmdq_reset_all(struct mmc_host *host, int err)
			mmc_hostname(host), __func__,
			ctx_info->active_reqs, host->clk_requests);

	mmc_blk_cmdq_reset(host, false);

	for_each_set_bit(itag, &ctx_info->active_reqs,
			host->num_cq_slots) {
		ret = is_cmdq_dcmd_req(q, itag);
@@ -3096,6 +3094,215 @@ static enum blk_eh_timer_return mmc_blk_cmdq_req_timed_out(struct request *req)
	return BLK_EH_HANDLED;
}

static void mmc_blk_cmdq_print_mrq_info(struct mmc_request *mrq, u32 tag,
		int err)
{
	if (!mrq)
		return;

	if (mrq->cmd)
		pr_err("%s: Task: %d cmd: %u flags: %u failed: %d\n",
				mmc_hostname(mrq->host), tag, mrq->cmd->opcode,
				mrq->cmd->flags, err);

	if (mrq->data) {
		pr_err("%s: Task: %d, Blk Addr:0x%u,", mmc_hostname(mrq->host),
				tag, mrq->cmdq_req->blk_addr);
		pr_err(" Blk cnt:0x%u, Blk sz:%u failed: %d\n",
				mrq->cmdq_req->data.blocks,
				mrq->cmdq_req->data.blksz, err);
	}
}

static struct mmc_request *mmc_blk_cmdq_wp_handle(
		struct mmc_cmdq_err_info *err_info, struct mmc_host *host)
{
	u32 cmd = 0, dat = 0;
	struct mmc_request *err_mrq = NULL;

	cmd = err_info->cmd;
	dat = err_info->data_cmd;

	err_info->remove_task = false;
	err_info->fail_dev_pend = false;
	err_info->fail_comp_task = true;

	/*
	 * If a write task is not going on in the controller, then the task
	 * which caused error can be among the completed task list.
	 *
	 * For WP violations detected during DCMD commands,
	 * like ERASE command.
	 */
	if (err_info->cmd_tag == err_info->dcmd_slot &&
		(cmd == MMC_ERASE || cmd == MMC_SEND_STATUS) &&
			err_info->cmd_valid) {
		err_mrq = host->cmdq_ops->get_mrq_by_tag(host, err_info->tag);
		clear_bit(err_info->cmd_tag, &err_info->dev_pend);
		clear_bit(err_info->cmd_tag, &err_info->comp_status);
		if (!err_mrq)
			goto out;
		err_info->remove_task = true;
		err_info->tag = err_mrq->req->tag;
		return err_mrq;
	}

	/*
	 * If a write task was in execution when WP RED error was detected,
	 * the WP violation may have been caused by it. So remove it. In this
	 * case the completed tasks are innocent.
	 */
	if (dat == MMC_CMDQ_WRITE_TASK && err_info->data_valid) {
		err_mrq = host->cmdq_ops->get_mrq_by_tag(host, err_info->tag);
		clear_bit(err_info->data_tag, &err_info->dev_pend);
		clear_bit(err_info->data_tag, &err_info->comp_status);
		if (!err_mrq)
			goto out;
		err_info->remove_task = true;
		err_info->fail_comp_task = false;
	}
	/*
	 * Unless CMD45 is in CMD field of CQTERRI, the last executed CMD45 may
	 * have caused the WP violation.
	 * NOTE: We cannot get last executed CMD45 correctly always. So pick
	 * last set task in CQ_DEV_PEND to avoid infinite loop
	 */
	if (cmd != MMC_CMDQ_TASK_ADDR)
		err_info->fail_dev_pend = true;

	if (err_info->tag < 0 && err_info->fail_comp_task) {
		err_info->tag = 0;
		err_info->remove_task = false;
	}
	pr_err("%s: Write protect violation detected\n",
			mmc_hostname(host));
	return err_mrq;
out:
	err_info->tag = -1;
	return err_mrq;
}

static void mmc_blk_cmdq_remove_task(struct mmc_host *host,
		struct mmc_cmdq_err_info *err_info, struct mmc_request *mrq,
		int err, bool print_info) {

	struct mmc_cmdq_context_info *ctx_info = &host->cmdq_ctx;
	struct request *rq = mrq->req;
	int tag = mrq->cmdq_req->tag;

	if (!test_and_clear_bit(mrq->cmdq_req->tag, &ctx_info->active_reqs))
		return;

	if (print_info)
		mmc_blk_cmdq_print_mrq_info(mrq, tag, err);

	if (mrq->cmdq_req->cmdq_req_flags & DCMD) {
		clear_bit(CMDQ_STATE_DCMD_ACTIVE,
				&ctx_info->curr_state);
		blk_end_request_all(rq, err);
	} else {
		WARN_ON(!test_and_clear_bit(mrq->cmdq_req->tag,
					&ctx_info->data_active_reqs));
		mmc_cmdq_post_req(host, mrq->cmdq_req->tag, err);
		blk_end_request_all(rq, err);
	}
	mmc_host_clk_release(host);
	mmc_put_card(host->card);
	mmc_cmdq_clk_scaling_stop_busy(host, true, tag == err_info->dcmd_slot);

}

static void mmc_blk_cmdq_fail_mult_tasks(struct mmc_host *host,
		unsigned long task_list, struct mmc_cmdq_err_info *err_info,
		int err, bool print_info) {

	struct mmc_request *err_mrq = NULL;
	int tag;

	for_each_set_bit(tag, &task_list, err_info->max_slot) {
		err_mrq = host->cmdq_ops->get_mrq_by_tag(host, tag);
		if (!err_mrq || !err_mrq->req)
			continue;
		mmc_blk_cmdq_remove_task(host, err_info,
				err_mrq, err, print_info);
	}
};

/*
 * Error handler for cmdq if CQ enabled. Returns 0 if error is successfully
 * handled. Returns negative value if fails.
 */
static void mmc_blk_cmdq_err_handle(struct mmc_host *host, u32 status, int err)
{
	struct mmc_request *mrq = host->err_mrq;
	struct mmc_cmdq_err_info err_info;
	u32 red_err_info = 0;
	struct mmc_request *err_mrq = NULL;

	if (host->cmdq_ops->err_info && host->cmdq_ops->get_mrq_by_tag)
		host->cmdq_ops->err_info(host, &err_info, mrq);
	else
		return;

	if (mrq->cmdq_req->resp_arg & CQ_RED) {
		red_err_info = mrq->cmdq_req->resp_arg;
	} else if (status & CQ_RED) {
		red_err_info = status;
		err_info.timedout = true;
	}

	if (red_err_info & CQ_RED) {
		/*
		 * The request which caused RED error may need to be
		 * removed before resetting card and requeueing requests
		 */
		if (red_err_info & CQ_WP_RED) {
			err_mrq = mmc_blk_cmdq_wp_handle(&err_info, host);
			if (err_info.tag < 0)
				return;
			err = -EPERM;
		} else {
			pr_err("%s: unhandled RED error detected!\n",
					mmc_hostname(host));
			err_mrq = mrq;
		}
	}

	if (err_mrq)
		mrq = err_mrq;

	/* Remove the request if marked for removal */
	if (err_info.remove_task)
		mmc_blk_cmdq_remove_task(host, &err_info, mrq, err, true);

	/*
	 * If there is a chance that one of the completed task caused error,
	 * tell block layer that all completed tasks failed. Here priority is
	 * given to removing the errored task than on saving innocent task. This
	 * is to avoid data corruption at higher layers.
	 */
	if (err_info.fail_comp_task)
		mmc_blk_cmdq_fail_mult_tasks(host, err_info.comp_status,
				&err_info, err, true);
	else
		mmc_blk_cmdq_fail_mult_tasks(host, err_info.comp_status,
				&err_info, 0, false);
	/*
	 * If there is a chance that one of the Dev Pending task caused error,
	 * tell block layer that all Dev Pending tasks failed. Here priority is
	 * given to aviod looping with an error causing task than on saving
	 * innocent task.
	 */
	if (err_info.fail_dev_pend) {
		pr_err("%s: %s: Failing all tasks in Device Pending : 0x%lx\n",
				mmc_hostname(host), __func__,
				err_info.dev_pend);
		mmc_blk_cmdq_fail_mult_tasks(host, err_info.dev_pend,
				&err_info, err, true);
	}

}

/*
 * mmc_blk_cmdq_err: error handling of cmdq error requests.
 * Function should be called in context of error out request
@@ -3130,10 +3337,9 @@ static void mmc_blk_cmdq_err(struct mmc_queue *mq)
	}

	/* RED error - Fatal: requires reset */
	if (mrq->cmdq_req->resp_err) {
	if (mrq->cmdq_req->resp_err)
		err = mrq->cmdq_req->resp_err;
		goto reset;
	}


	/*
	 * TIMEOUT errrors can happen because of execution error
@@ -3164,8 +3370,10 @@ static void mmc_blk_cmdq_err(struct mmc_queue *mq)
		/* DCMD commands */
		err = mrq->cmd->error;
	}

reset:
	mmc_blk_cmdq_reset(host, false);
	mmc_blk_cmdq_err_handle(host, status, err);

	mmc_blk_cmdq_reset_all(host, err);
	if (mrq->cmdq_req->resp_err)
		mrq->cmdq_req->resp_err = false;
@@ -3214,11 +3422,10 @@ void mmc_blk_cmdq_complete_rq(struct request *rq)
	 * or disable state so cannot receive any completion of
	 * other requests.
	 */
	BUG_ON(test_bit(CMDQ_STATE_ERR, &ctx_info->curr_state));
	WARN_ON(test_bit(CMDQ_STATE_ERR, &ctx_info->curr_state));

	/* clear pending request */
	BUG_ON(!test_and_clear_bit(cmdq_req->tag,
				   &ctx_info->active_reqs));
	BUG_ON(!test_and_clear_bit(cmdq_req->tag, &ctx_info->active_reqs));
	if (cmdq_req->cmdq_req_flags & DCMD)
		is_dcmd = true;
	else
@@ -3237,7 +3444,7 @@ void mmc_blk_cmdq_complete_rq(struct request *rq)
out:

	mmc_cmdq_clk_scaling_stop_busy(host, true, is_dcmd);
	if (!test_bit(CMDQ_STATE_ERR, &ctx_info->curr_state)) {
	if (!(err || cmdq_req->resp_err)) {
		mmc_host_clk_release(host);
		wake_up(&ctx_info->wait);
		mmc_put_card(host->card);