[Bionic,SRU] nvme: Discard workaround for non-conformant devices
diff mbox series

Message ID 20200114190446.28226-2-connor.kuehl@canonical.com
State New
Headers show
  • [Bionic,SRU] nvme: Discard workaround for non-conformant devices
Related show

Commit Message

Connor Kuehl Jan. 14, 2020, 7:04 p.m. UTC
From: Eduard Hasenleithner <eduard@hasenleithner.at>

BugLink: https://bugs.launchpad.net/bugs/1856603

Users observe IOMMU related errors when performing discard on nvme from
non-compliant nvme devices reading beyond the end of the DMA mapped
ranges to discard.

Two different variants of this behavior have been observed: SM22XX
controllers round up the read size to a multiple of 512 bytes, and Phison
E12 unconditionally reads the maximum discard size allowed by the spec
(256 segments or 4kB).

Make nvme_setup_discard unconditionally allocate the maximum DSM buffer
so the driver DMA maps a memory range that will always succeed.

Link: https://bugzilla.kernel.org/show_bug.cgi?id=202665 many
Signed-off-by: Eduard Hasenleithner <eduard@hasenleithner.at>
[changelog, use existing define, kernel coding style]
Signed-off-by: Keith Busch <kbusch@kernel.org>
(backported from commit 530436c45ef2e446c12538a400e465929a0b3ade)
[ Connor Kuehl: context adjustments. ]
Signed-off-by: Connor Kuehl <connor.kuehl@canonical.com>
 drivers/nvme/host/core.c | 11 +++++++++--
 1 file changed, 9 insertions(+), 2 deletions(-)

diff mbox series

diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c
index 52b1635a5a52..6df070db0adb 100644
--- a/drivers/nvme/host/core.c
+++ b/drivers/nvme/host/core.c
@@ -470,7 +470,14 @@  static blk_status_t nvme_setup_discard(struct nvme_ns *ns, struct request *req,
 	struct nvme_dsm_range *range;
 	struct bio *bio;
-	range = kmalloc_array(segments, sizeof(*range), GFP_ATOMIC);
+	/*
+	 * Some devices do not consider the DSM 'Number of Ranges' field when
+	 * determining how much data to DMA. Always allocate memory for maximum
+	 * number of segments to prevent device reading beyond end of buffer.
+	 */
+	static const size_t alloc_size = sizeof(*range) * NVME_DSM_MAX_RANGES;
+	range = kzalloc(alloc_size, GFP_ATOMIC | __GFP_NOWARN);
 	if (!range)
@@ -497,7 +504,7 @@  static blk_status_t nvme_setup_discard(struct nvme_ns *ns, struct request *req,
 	req->special_vec.bv_page = virt_to_page(range);
 	req->special_vec.bv_offset = offset_in_page(range);
-	req->special_vec.bv_len = sizeof(*range) * segments;
+	req->special_vec.bv_len = alloc_size;
 	req->rq_flags |= RQF_SPECIAL_PAYLOAD;
 	return BLK_STS_OK;