@@ -279,8 +279,8 @@ static int blk_complete_sghdr_rq(struct request *rq, struct sg_io_hdr *hdr,
return ret;
}
-static int sg_io(struct request_queue *q, struct gendisk *bd_disk,
- struct sg_io_hdr *hdr, fmode_t mode)
+int sg_io(struct request_queue *q, struct gendisk *bd_disk,
+ struct sg_io_hdr *hdr, fmode_t mode)
{
unsigned long start_time;
ssize_t ret = 0;
@@ -369,6 +369,7 @@ static int sg_io(struct request_queue *q, struct gendisk *bd_disk,
blk_put_request(rq);
return ret;
}
+EXPORT_SYMBOL_GPL(sg_io);
/**
* sg_scsi_ioctl -- handle deprecated SCSI_IOCTL_SEND_COMMAND ioctl
@@ -473,6 +473,17 @@ config DM_MULTIPATH_IOA
If unsure, say N.
+config DM_MULTIPATH_SG_IO
+ bool "Retry SCSI generic I/O on multipath devices"
+ depends on DM_MULTIPATH && BLK_SCSI_REQUEST
+ help
+ With this option, SCSI generic (SG) requests issued on multipath
+ devices will behave similar to regular block I/O: upon failure,
+ they are repeated on a different path, and the erroring device
+ is marked as failed.
+
+ If unsure, say N.
+
config DM_DELAY
tristate "I/O delaying target"
depends on BLK_DEV_DM
@@ -88,6 +88,10 @@ ifeq ($(CONFIG_DM_INIT),y)
dm-mod-objs += dm-init.o
endif
+ifeq ($(CONFIG_DM_MULTIPATH_SG_IO),y)
+dm-mod-objs += dm-scsi_ioctl.o
+endif
+
ifeq ($(CONFIG_DM_UEVENT),y)
dm-mod-objs += dm-uevent.o
endif
@@ -189,4 +189,9 @@ extern atomic_t dm_global_event_nr;
extern wait_queue_head_t dm_global_eventq;
void dm_issue_global_event(void);
+int __dm_prepare_ioctl(struct mapped_device *md, int *srcu_idx,
+ struct block_device **bdev,
+ struct dm_target **target);
+void dm_unprepare_ioctl(struct mapped_device *md, int srcu_idx);
+
#endif
@@ -44,4 +44,15 @@ ssize_t dm_attr_rq_based_seq_io_merge_deadline_show(struct mapped_device *md, ch
ssize_t dm_attr_rq_based_seq_io_merge_deadline_store(struct mapped_device *md,
const char *buf, size_t count);
+#ifdef CONFIG_DM_MULTIPATH_SG_IO
+int dm_sg_io_ioctl(struct block_device *bdev, fmode_t mode,
+ unsigned int cmd, unsigned long uarg);
+#else
+static inline int dm_sg_io_ioctl(struct block_device *bdev, fmode_t mode,
+ unsigned int cmd, unsigned long uarg)
+{
+ return -ENOTTY;
+}
+#endif
+
#endif
new file mode 100644
@@ -0,0 +1,127 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2021 Martin Wilck, SUSE LLC
+ */
+
+#include "dm-core.h"
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/uaccess.h>
+#include <linux/blk_types.h>
+#include <linux/blkdev.h>
+#include <linux/device-mapper.h>
+#include <scsi/sg.h>
+#include <scsi/scsi_cmnd.h>
+
+#define DM_MSG_PREFIX "sg_io"
+
+int dm_sg_io_ioctl(struct block_device *bdev, fmode_t mode,
+ unsigned int cmd, unsigned long uarg)
+{
+ struct mapped_device *md = bdev->bd_disk->private_data;
+ struct sg_io_hdr hdr;
+ void __user *arg = (void __user *)uarg;
+ int rc, srcu_idx;
+ char path_name[BDEVNAME_SIZE];
+
+ if (cmd != SG_IO)
+ return -ENOTTY;
+
+ if (copy_from_user(&hdr, arg, sizeof(hdr)))
+ return -EFAULT;
+
+ if (hdr.interface_id != 'S')
+ return -EINVAL;
+
+ if (hdr.dxfer_len > (queue_max_hw_sectors(bdev->bd_disk->queue) << 9))
+ return -EIO;
+
+ for (;;) {
+ struct dm_target *tgt;
+ struct sg_io_hdr rhdr;
+
+ rc = __dm_prepare_ioctl(md, &srcu_idx, &bdev, &tgt);
+ if (rc < 0) {
+ DMERR("%s: failed to get path: %d",
+ __func__, rc);
+ goto out;
+ }
+
+ rhdr = hdr;
+
+ rc = sg_io(bdev->bd_disk->queue, bdev->bd_disk, &rhdr, mode);
+
+ DMDEBUG("SG_IO via %s: rc = %d D%02xH%02xM%02xS%02x",
+ bdevname(bdev, path_name), rc,
+ rhdr.driver_status, rhdr.host_status,
+ rhdr.msg_status, rhdr.status);
+
+ /*
+ * Errors resulting from invalid parameters shouldn't be retried
+ * on another path.
+ */
+ switch (rc) {
+ case -ENOIOCTLCMD:
+ case -EFAULT:
+ case -EINVAL:
+ case -EPERM:
+ goto out;
+ default:
+ break;
+ }
+
+ if (rhdr.info & SG_INFO_CHECK) {
+ int result;
+ blk_status_t sts;
+
+ __set_status_byte(&result, rhdr.status);
+ __set_msg_byte(&result, rhdr.msg_status);
+ __set_host_byte(&result, rhdr.host_status);
+ __set_driver_byte(&result, rhdr.driver_status);
+
+ sts = __scsi_result_to_blk_status(&result, result);
+ rhdr.host_status = host_byte(result);
+
+ /* See if this is a target or path error. */
+ if (sts == BLK_STS_OK)
+ rc = 0;
+ else if (blk_path_error(sts))
+ rc = -EIO;
+ else {
+ rc = blk_status_to_errno(sts);
+ goto out;
+ }
+ }
+
+ if (rc == 0) {
+ /* success */
+ if (copy_to_user(arg, &rhdr, sizeof(rhdr)))
+ rc = -EFAULT;
+ goto out;
+ }
+
+ /* Failure - fail path by sending a message to the target */
+ if (!tgt->type->message) {
+ DMWARN("invalid target!");
+ rc = -EIO;
+ goto out;
+ } else {
+ char bdbuf[BDEVT_SIZE];
+ char *argv[2] = { "fail_path", bdbuf };
+
+ scnprintf(bdbuf, sizeof(bdbuf), "%u:%u",
+ MAJOR(bdev->bd_dev), MINOR(bdev->bd_dev));
+
+ DMDEBUG("sending \"%s %s\" to target", argv[0], argv[1]);
+ rc = tgt->type->message(tgt, 2, argv, NULL, 0);
+ if (rc < 0)
+ goto out;
+ }
+
+ dm_unprepare_ioctl(md, srcu_idx);
+ }
+out:
+ dm_unprepare_ioctl(md, srcu_idx);
+ return rc;
+}
@@ -522,8 +522,9 @@ static int dm_blk_report_zones(struct gendisk *disk, sector_t sector,
#define dm_blk_report_zones NULL
#endif /* CONFIG_BLK_DEV_ZONED */
-static int dm_prepare_ioctl(struct mapped_device *md, int *srcu_idx,
- struct block_device **bdev)
+int __dm_prepare_ioctl(struct mapped_device *md, int *srcu_idx,
+ struct block_device **bdev,
+ struct dm_target **target)
{
struct dm_target *tgt;
struct dm_table *map;
@@ -553,10 +554,19 @@ static int dm_prepare_ioctl(struct mapped_device *md, int *srcu_idx,
goto retry;
}
+ if (r >= 0 && target)
+ *target = tgt;
+
return r;
}
-static void dm_unprepare_ioctl(struct mapped_device *md, int srcu_idx)
+static int dm_prepare_ioctl(struct mapped_device *md, int *srcu_idx,
+ struct block_device **bdev)
+{
+ return __dm_prepare_ioctl(md, srcu_idx, bdev, NULL);
+}
+
+void dm_unprepare_ioctl(struct mapped_device *md, int srcu_idx)
{
dm_put_live_table(md, srcu_idx);
}
@@ -567,6 +577,10 @@ static int dm_blk_ioctl(struct block_device *bdev, fmode_t mode,
struct mapped_device *md = bdev->bd_disk->private_data;
int r, srcu_idx;
+ if ((dm_get_md_type(md) == DM_TYPE_REQUEST_BASED) &&
+ ((r = dm_sg_io_ioctl(bdev, mode, cmd, arg)) != -ENOTTY))
+ return r;
+
r = dm_prepare_ioctl(md, &srcu_idx, &bdev);
if (r < 0)
goto out;
@@ -934,6 +934,8 @@ extern int scsi_cmd_ioctl(struct request_queue *, struct gendisk *, fmode_t,
unsigned int, void __user *);
extern int sg_scsi_ioctl(struct request_queue *, struct gendisk *, fmode_t,
struct scsi_ioctl_command __user *);
+extern int sg_io(struct request_queue *, struct gendisk *,
+ struct sg_io_hdr *, fmode_t);
extern int get_sg_io_hdr(struct sg_io_hdr *hdr, const void __user *argp);
extern int put_sg_io_hdr(const struct sg_io_hdr *hdr, void __user *argp);