18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * bsg endpoint that supports UPIUs
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (C) 2018 Western Digital Corporation
68c2ecf20Sopenharmony_ci */
78c2ecf20Sopenharmony_ci#include "ufs_bsg.h"
88c2ecf20Sopenharmony_ci
98c2ecf20Sopenharmony_cistatic int ufs_bsg_get_query_desc_size(struct ufs_hba *hba, int *desc_len,
108c2ecf20Sopenharmony_ci				       struct utp_upiu_query *qr)
118c2ecf20Sopenharmony_ci{
128c2ecf20Sopenharmony_ci	int desc_size = be16_to_cpu(qr->length);
138c2ecf20Sopenharmony_ci	int desc_id = qr->idn;
148c2ecf20Sopenharmony_ci
158c2ecf20Sopenharmony_ci	if (desc_size <= 0)
168c2ecf20Sopenharmony_ci		return -EINVAL;
178c2ecf20Sopenharmony_ci
188c2ecf20Sopenharmony_ci	ufshcd_map_desc_id_to_length(hba, desc_id, desc_len);
198c2ecf20Sopenharmony_ci	if (!*desc_len)
208c2ecf20Sopenharmony_ci		return -EINVAL;
218c2ecf20Sopenharmony_ci
228c2ecf20Sopenharmony_ci	*desc_len = min_t(int, *desc_len, desc_size);
238c2ecf20Sopenharmony_ci
248c2ecf20Sopenharmony_ci	return 0;
258c2ecf20Sopenharmony_ci}
268c2ecf20Sopenharmony_ci
278c2ecf20Sopenharmony_cistatic int ufs_bsg_verify_query_size(struct ufs_hba *hba,
288c2ecf20Sopenharmony_ci				     unsigned int request_len,
298c2ecf20Sopenharmony_ci				     unsigned int reply_len)
308c2ecf20Sopenharmony_ci{
318c2ecf20Sopenharmony_ci	int min_req_len = sizeof(struct ufs_bsg_request);
328c2ecf20Sopenharmony_ci	int min_rsp_len = sizeof(struct ufs_bsg_reply);
338c2ecf20Sopenharmony_ci
348c2ecf20Sopenharmony_ci	if (min_req_len > request_len || min_rsp_len > reply_len) {
358c2ecf20Sopenharmony_ci		dev_err(hba->dev, "not enough space assigned\n");
368c2ecf20Sopenharmony_ci		return -EINVAL;
378c2ecf20Sopenharmony_ci	}
388c2ecf20Sopenharmony_ci
398c2ecf20Sopenharmony_ci	return 0;
408c2ecf20Sopenharmony_ci}
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_cistatic int ufs_bsg_alloc_desc_buffer(struct ufs_hba *hba, struct bsg_job *job,
438c2ecf20Sopenharmony_ci				     uint8_t **desc_buff, int *desc_len,
448c2ecf20Sopenharmony_ci				     enum query_opcode desc_op)
458c2ecf20Sopenharmony_ci{
468c2ecf20Sopenharmony_ci	struct ufs_bsg_request *bsg_request = job->request;
478c2ecf20Sopenharmony_ci	struct utp_upiu_query *qr;
488c2ecf20Sopenharmony_ci	u8 *descp;
498c2ecf20Sopenharmony_ci
508c2ecf20Sopenharmony_ci	if (desc_op != UPIU_QUERY_OPCODE_WRITE_DESC &&
518c2ecf20Sopenharmony_ci	    desc_op != UPIU_QUERY_OPCODE_READ_DESC)
528c2ecf20Sopenharmony_ci		goto out;
538c2ecf20Sopenharmony_ci
548c2ecf20Sopenharmony_ci	qr = &bsg_request->upiu_req.qr;
558c2ecf20Sopenharmony_ci	if (ufs_bsg_get_query_desc_size(hba, desc_len, qr)) {
568c2ecf20Sopenharmony_ci		dev_err(hba->dev, "Illegal desc size\n");
578c2ecf20Sopenharmony_ci		return -EINVAL;
588c2ecf20Sopenharmony_ci	}
598c2ecf20Sopenharmony_ci
608c2ecf20Sopenharmony_ci	if (*desc_len > job->request_payload.payload_len) {
618c2ecf20Sopenharmony_ci		dev_err(hba->dev, "Illegal desc size\n");
628c2ecf20Sopenharmony_ci		return -EINVAL;
638c2ecf20Sopenharmony_ci	}
648c2ecf20Sopenharmony_ci
658c2ecf20Sopenharmony_ci	descp = kzalloc(*desc_len, GFP_KERNEL);
668c2ecf20Sopenharmony_ci	if (!descp)
678c2ecf20Sopenharmony_ci		return -ENOMEM;
688c2ecf20Sopenharmony_ci
698c2ecf20Sopenharmony_ci	if (desc_op == UPIU_QUERY_OPCODE_WRITE_DESC)
708c2ecf20Sopenharmony_ci		sg_copy_to_buffer(job->request_payload.sg_list,
718c2ecf20Sopenharmony_ci				  job->request_payload.sg_cnt, descp,
728c2ecf20Sopenharmony_ci				  *desc_len);
738c2ecf20Sopenharmony_ci
748c2ecf20Sopenharmony_ci	*desc_buff = descp;
758c2ecf20Sopenharmony_ci
768c2ecf20Sopenharmony_ciout:
778c2ecf20Sopenharmony_ci	return 0;
788c2ecf20Sopenharmony_ci}
798c2ecf20Sopenharmony_ci
808c2ecf20Sopenharmony_cistatic int ufs_bsg_request(struct bsg_job *job)
818c2ecf20Sopenharmony_ci{
828c2ecf20Sopenharmony_ci	struct ufs_bsg_request *bsg_request = job->request;
838c2ecf20Sopenharmony_ci	struct ufs_bsg_reply *bsg_reply = job->reply;
848c2ecf20Sopenharmony_ci	struct ufs_hba *hba = shost_priv(dev_to_shost(job->dev->parent));
858c2ecf20Sopenharmony_ci	unsigned int req_len = job->request_len;
868c2ecf20Sopenharmony_ci	unsigned int reply_len = job->reply_len;
878c2ecf20Sopenharmony_ci	struct uic_command uc = {};
888c2ecf20Sopenharmony_ci	int msgcode;
898c2ecf20Sopenharmony_ci	uint8_t *desc_buff = NULL;
908c2ecf20Sopenharmony_ci	int desc_len = 0;
918c2ecf20Sopenharmony_ci	enum query_opcode desc_op = UPIU_QUERY_OPCODE_NOP;
928c2ecf20Sopenharmony_ci	int ret;
938c2ecf20Sopenharmony_ci
948c2ecf20Sopenharmony_ci	ret = ufs_bsg_verify_query_size(hba, req_len, reply_len);
958c2ecf20Sopenharmony_ci	if (ret)
968c2ecf20Sopenharmony_ci		goto out;
978c2ecf20Sopenharmony_ci
988c2ecf20Sopenharmony_ci	bsg_reply->reply_payload_rcv_len = 0;
998c2ecf20Sopenharmony_ci
1008c2ecf20Sopenharmony_ci	pm_runtime_get_sync(hba->dev);
1018c2ecf20Sopenharmony_ci
1028c2ecf20Sopenharmony_ci	msgcode = bsg_request->msgcode;
1038c2ecf20Sopenharmony_ci	switch (msgcode) {
1048c2ecf20Sopenharmony_ci	case UPIU_TRANSACTION_QUERY_REQ:
1058c2ecf20Sopenharmony_ci		desc_op = bsg_request->upiu_req.qr.opcode;
1068c2ecf20Sopenharmony_ci		ret = ufs_bsg_alloc_desc_buffer(hba, job, &desc_buff,
1078c2ecf20Sopenharmony_ci						&desc_len, desc_op);
1088c2ecf20Sopenharmony_ci		if (ret) {
1098c2ecf20Sopenharmony_ci			pm_runtime_put_sync(hba->dev);
1108c2ecf20Sopenharmony_ci			goto out;
1118c2ecf20Sopenharmony_ci		}
1128c2ecf20Sopenharmony_ci
1138c2ecf20Sopenharmony_ci		fallthrough;
1148c2ecf20Sopenharmony_ci	case UPIU_TRANSACTION_NOP_OUT:
1158c2ecf20Sopenharmony_ci	case UPIU_TRANSACTION_TASK_REQ:
1168c2ecf20Sopenharmony_ci		ret = ufshcd_exec_raw_upiu_cmd(hba, &bsg_request->upiu_req,
1178c2ecf20Sopenharmony_ci					       &bsg_reply->upiu_rsp, msgcode,
1188c2ecf20Sopenharmony_ci					       desc_buff, &desc_len, desc_op);
1198c2ecf20Sopenharmony_ci		if (ret)
1208c2ecf20Sopenharmony_ci			dev_err(hba->dev,
1218c2ecf20Sopenharmony_ci				"exe raw upiu: error code %d\n", ret);
1228c2ecf20Sopenharmony_ci
1238c2ecf20Sopenharmony_ci		break;
1248c2ecf20Sopenharmony_ci	case UPIU_TRANSACTION_UIC_CMD:
1258c2ecf20Sopenharmony_ci		memcpy(&uc, &bsg_request->upiu_req.uc, UIC_CMD_SIZE);
1268c2ecf20Sopenharmony_ci		ret = ufshcd_send_uic_cmd(hba, &uc);
1278c2ecf20Sopenharmony_ci		if (ret)
1288c2ecf20Sopenharmony_ci			dev_err(hba->dev,
1298c2ecf20Sopenharmony_ci				"send uic cmd: error code %d\n", ret);
1308c2ecf20Sopenharmony_ci
1318c2ecf20Sopenharmony_ci		memcpy(&bsg_reply->upiu_rsp.uc, &uc, UIC_CMD_SIZE);
1328c2ecf20Sopenharmony_ci
1338c2ecf20Sopenharmony_ci		break;
1348c2ecf20Sopenharmony_ci	default:
1358c2ecf20Sopenharmony_ci		ret = -ENOTSUPP;
1368c2ecf20Sopenharmony_ci		dev_err(hba->dev, "unsupported msgcode 0x%x\n", msgcode);
1378c2ecf20Sopenharmony_ci
1388c2ecf20Sopenharmony_ci		break;
1398c2ecf20Sopenharmony_ci	}
1408c2ecf20Sopenharmony_ci
1418c2ecf20Sopenharmony_ci	pm_runtime_put_sync(hba->dev);
1428c2ecf20Sopenharmony_ci
1438c2ecf20Sopenharmony_ci	if (!desc_buff)
1448c2ecf20Sopenharmony_ci		goto out;
1458c2ecf20Sopenharmony_ci
1468c2ecf20Sopenharmony_ci	if (desc_op == UPIU_QUERY_OPCODE_READ_DESC && desc_len)
1478c2ecf20Sopenharmony_ci		bsg_reply->reply_payload_rcv_len =
1488c2ecf20Sopenharmony_ci			sg_copy_from_buffer(job->request_payload.sg_list,
1498c2ecf20Sopenharmony_ci					    job->request_payload.sg_cnt,
1508c2ecf20Sopenharmony_ci					    desc_buff, desc_len);
1518c2ecf20Sopenharmony_ci
1528c2ecf20Sopenharmony_ci	kfree(desc_buff);
1538c2ecf20Sopenharmony_ci
1548c2ecf20Sopenharmony_ciout:
1558c2ecf20Sopenharmony_ci	bsg_reply->result = ret;
1568c2ecf20Sopenharmony_ci	job->reply_len = sizeof(struct ufs_bsg_reply);
1578c2ecf20Sopenharmony_ci	/* complete the job here only if no error */
1588c2ecf20Sopenharmony_ci	if (ret == 0)
1598c2ecf20Sopenharmony_ci		bsg_job_done(job, ret, bsg_reply->reply_payload_rcv_len);
1608c2ecf20Sopenharmony_ci
1618c2ecf20Sopenharmony_ci	return ret;
1628c2ecf20Sopenharmony_ci}
1638c2ecf20Sopenharmony_ci
1648c2ecf20Sopenharmony_ci/**
1658c2ecf20Sopenharmony_ci * ufs_bsg_remove - detach and remove the added ufs-bsg node
1668c2ecf20Sopenharmony_ci * @hba: per adapter object
1678c2ecf20Sopenharmony_ci *
1688c2ecf20Sopenharmony_ci * Should be called when unloading the driver.
1698c2ecf20Sopenharmony_ci */
1708c2ecf20Sopenharmony_civoid ufs_bsg_remove(struct ufs_hba *hba)
1718c2ecf20Sopenharmony_ci{
1728c2ecf20Sopenharmony_ci	struct device *bsg_dev = &hba->bsg_dev;
1738c2ecf20Sopenharmony_ci
1748c2ecf20Sopenharmony_ci	if (!hba->bsg_queue)
1758c2ecf20Sopenharmony_ci		return;
1768c2ecf20Sopenharmony_ci
1778c2ecf20Sopenharmony_ci	bsg_remove_queue(hba->bsg_queue);
1788c2ecf20Sopenharmony_ci
1798c2ecf20Sopenharmony_ci	device_del(bsg_dev);
1808c2ecf20Sopenharmony_ci	put_device(bsg_dev);
1818c2ecf20Sopenharmony_ci}
1828c2ecf20Sopenharmony_ci
1838c2ecf20Sopenharmony_cistatic inline void ufs_bsg_node_release(struct device *dev)
1848c2ecf20Sopenharmony_ci{
1858c2ecf20Sopenharmony_ci	put_device(dev->parent);
1868c2ecf20Sopenharmony_ci}
1878c2ecf20Sopenharmony_ci
1888c2ecf20Sopenharmony_ci/**
1898c2ecf20Sopenharmony_ci * ufs_bsg_probe - Add ufs bsg device node
1908c2ecf20Sopenharmony_ci * @hba: per adapter object
1918c2ecf20Sopenharmony_ci *
1928c2ecf20Sopenharmony_ci * Called during initial loading of the driver, and before scsi_scan_host.
1938c2ecf20Sopenharmony_ci */
1948c2ecf20Sopenharmony_ciint ufs_bsg_probe(struct ufs_hba *hba)
1958c2ecf20Sopenharmony_ci{
1968c2ecf20Sopenharmony_ci	struct device *bsg_dev = &hba->bsg_dev;
1978c2ecf20Sopenharmony_ci	struct Scsi_Host *shost = hba->host;
1988c2ecf20Sopenharmony_ci	struct device *parent = &shost->shost_gendev;
1998c2ecf20Sopenharmony_ci	struct request_queue *q;
2008c2ecf20Sopenharmony_ci	int ret;
2018c2ecf20Sopenharmony_ci
2028c2ecf20Sopenharmony_ci	device_initialize(bsg_dev);
2038c2ecf20Sopenharmony_ci
2048c2ecf20Sopenharmony_ci	bsg_dev->parent = get_device(parent);
2058c2ecf20Sopenharmony_ci	bsg_dev->release = ufs_bsg_node_release;
2068c2ecf20Sopenharmony_ci
2078c2ecf20Sopenharmony_ci	dev_set_name(bsg_dev, "ufs-bsg%u", shost->host_no);
2088c2ecf20Sopenharmony_ci
2098c2ecf20Sopenharmony_ci	ret = device_add(bsg_dev);
2108c2ecf20Sopenharmony_ci	if (ret)
2118c2ecf20Sopenharmony_ci		goto out;
2128c2ecf20Sopenharmony_ci
2138c2ecf20Sopenharmony_ci	q = bsg_setup_queue(bsg_dev, dev_name(bsg_dev), ufs_bsg_request, NULL, 0);
2148c2ecf20Sopenharmony_ci	if (IS_ERR(q)) {
2158c2ecf20Sopenharmony_ci		ret = PTR_ERR(q);
2168c2ecf20Sopenharmony_ci		goto out;
2178c2ecf20Sopenharmony_ci	}
2188c2ecf20Sopenharmony_ci
2198c2ecf20Sopenharmony_ci	hba->bsg_queue = q;
2208c2ecf20Sopenharmony_ci
2218c2ecf20Sopenharmony_ci	return 0;
2228c2ecf20Sopenharmony_ci
2238c2ecf20Sopenharmony_ciout:
2248c2ecf20Sopenharmony_ci	dev_err(bsg_dev, "fail to initialize a bsg dev %d\n", shost->host_no);
2258c2ecf20Sopenharmony_ci	put_device(bsg_dev);
2268c2ecf20Sopenharmony_ci	return ret;
2278c2ecf20Sopenharmony_ci}
228