162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci#include <linux/bsg.h> 362306a36Sopenharmony_ci#include <scsi/scsi.h> 462306a36Sopenharmony_ci#include <scsi/scsi_ioctl.h> 562306a36Sopenharmony_ci#include <scsi/scsi_cmnd.h> 662306a36Sopenharmony_ci#include <scsi/scsi_device.h> 762306a36Sopenharmony_ci#include <scsi/sg.h> 862306a36Sopenharmony_ci#include "scsi_priv.h" 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#define uptr64(val) ((void __user *)(uintptr_t)(val)) 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_cistatic int scsi_bsg_sg_io_fn(struct request_queue *q, struct sg_io_v4 *hdr, 1362306a36Sopenharmony_ci bool open_for_write, unsigned int timeout) 1462306a36Sopenharmony_ci{ 1562306a36Sopenharmony_ci struct scsi_cmnd *scmd; 1662306a36Sopenharmony_ci struct request *rq; 1762306a36Sopenharmony_ci struct bio *bio; 1862306a36Sopenharmony_ci int ret; 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ci if (hdr->protocol != BSG_PROTOCOL_SCSI || 2162306a36Sopenharmony_ci hdr->subprotocol != BSG_SUB_PROTOCOL_SCSI_CMD) 2262306a36Sopenharmony_ci return -EINVAL; 2362306a36Sopenharmony_ci if (hdr->dout_xfer_len && hdr->din_xfer_len) { 2462306a36Sopenharmony_ci pr_warn_once("BIDI support in bsg has been removed.\n"); 2562306a36Sopenharmony_ci return -EOPNOTSUPP; 2662306a36Sopenharmony_ci } 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ci rq = scsi_alloc_request(q, hdr->dout_xfer_len ? 2962306a36Sopenharmony_ci REQ_OP_DRV_OUT : REQ_OP_DRV_IN, 0); 3062306a36Sopenharmony_ci if (IS_ERR(rq)) 3162306a36Sopenharmony_ci return PTR_ERR(rq); 3262306a36Sopenharmony_ci rq->timeout = timeout; 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_ci scmd = blk_mq_rq_to_pdu(rq); 3562306a36Sopenharmony_ci scmd->cmd_len = hdr->request_len; 3662306a36Sopenharmony_ci if (scmd->cmd_len > sizeof(scmd->cmnd)) { 3762306a36Sopenharmony_ci ret = -EINVAL; 3862306a36Sopenharmony_ci goto out_put_request; 3962306a36Sopenharmony_ci } 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_ci ret = -EFAULT; 4262306a36Sopenharmony_ci if (copy_from_user(scmd->cmnd, uptr64(hdr->request), scmd->cmd_len)) 4362306a36Sopenharmony_ci goto out_put_request; 4462306a36Sopenharmony_ci ret = -EPERM; 4562306a36Sopenharmony_ci if (!scsi_cmd_allowed(scmd->cmnd, open_for_write)) 4662306a36Sopenharmony_ci goto out_put_request; 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_ci ret = 0; 4962306a36Sopenharmony_ci if (hdr->dout_xfer_len) { 5062306a36Sopenharmony_ci ret = blk_rq_map_user(rq->q, rq, NULL, uptr64(hdr->dout_xferp), 5162306a36Sopenharmony_ci hdr->dout_xfer_len, GFP_KERNEL); 5262306a36Sopenharmony_ci } else if (hdr->din_xfer_len) { 5362306a36Sopenharmony_ci ret = blk_rq_map_user(rq->q, rq, NULL, uptr64(hdr->din_xferp), 5462306a36Sopenharmony_ci hdr->din_xfer_len, GFP_KERNEL); 5562306a36Sopenharmony_ci } 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci if (ret) 5862306a36Sopenharmony_ci goto out_put_request; 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_ci bio = rq->bio; 6162306a36Sopenharmony_ci blk_execute_rq(rq, !(hdr->flags & BSG_FLAG_Q_AT_TAIL)); 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci /* 6462306a36Sopenharmony_ci * fill in all the output members 6562306a36Sopenharmony_ci */ 6662306a36Sopenharmony_ci hdr->device_status = scmd->result & 0xff; 6762306a36Sopenharmony_ci hdr->transport_status = host_byte(scmd->result); 6862306a36Sopenharmony_ci hdr->driver_status = 0; 6962306a36Sopenharmony_ci if (scsi_status_is_check_condition(scmd->result)) 7062306a36Sopenharmony_ci hdr->driver_status = DRIVER_SENSE; 7162306a36Sopenharmony_ci hdr->info = 0; 7262306a36Sopenharmony_ci if (hdr->device_status || hdr->transport_status || hdr->driver_status) 7362306a36Sopenharmony_ci hdr->info |= SG_INFO_CHECK; 7462306a36Sopenharmony_ci hdr->response_len = 0; 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci if (scmd->sense_len && hdr->response) { 7762306a36Sopenharmony_ci int len = min_t(unsigned int, hdr->max_response_len, 7862306a36Sopenharmony_ci scmd->sense_len); 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci if (copy_to_user(uptr64(hdr->response), scmd->sense_buffer, 8162306a36Sopenharmony_ci len)) 8262306a36Sopenharmony_ci ret = -EFAULT; 8362306a36Sopenharmony_ci else 8462306a36Sopenharmony_ci hdr->response_len = len; 8562306a36Sopenharmony_ci } 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_ci if (rq_data_dir(rq) == READ) 8862306a36Sopenharmony_ci hdr->din_resid = scmd->resid_len; 8962306a36Sopenharmony_ci else 9062306a36Sopenharmony_ci hdr->dout_resid = scmd->resid_len; 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_ci blk_rq_unmap_user(bio); 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ciout_put_request: 9562306a36Sopenharmony_ci blk_mq_free_request(rq); 9662306a36Sopenharmony_ci return ret; 9762306a36Sopenharmony_ci} 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_cistruct bsg_device *scsi_bsg_register_queue(struct scsi_device *sdev) 10062306a36Sopenharmony_ci{ 10162306a36Sopenharmony_ci return bsg_register_queue(sdev->request_queue, &sdev->sdev_gendev, 10262306a36Sopenharmony_ci dev_name(&sdev->sdev_gendev), scsi_bsg_sg_io_fn); 10362306a36Sopenharmony_ci} 104