nvme: Discard workaround for non-conformant devices
authorEduard Hasenleithner <eduard@hasenleithner.at>
Tue, 12 Nov 2019 20:55:01 +0000 (21:55 +0100)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Tue, 31 Dec 2019 15:45:24 +0000 (16:45 +0100)
[ Upstream commit 530436c45ef2e446c12538a400e465929a0b3ade ]

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
Signed-off-by: Eduard Hasenleithner <eduard@hasenleithner.at>
[changelog, use existing define, kernel coding style]
Signed-off-by: Keith Busch <kbusch@kernel.org>
Signed-off-by: Sasha Levin <sashal@kernel.org>
drivers/nvme/host/core.c

index b4e1e43..a6b7b24 100644 (file)
@@ -611,8 +611,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 | __GFP_NOWARN);
+       /*
+        * 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) {
                /*
                 * If we fail allocation our range, fallback to the controller
@@ -652,7 +658,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;