@@ -934,6 +934,30 @@ static inline blk_status_t nvme_setup_write_zeroes(struct nvme_ns *ns,
return BLK_STS_OK;
}
+static bool nvme_valid_atomic_write(struct request *req)
+{
+ struct request_queue *q = req->q;
+ u32 boundary_bytes = queue_atomic_write_boundary_bytes(q);
+
+ if (blk_rq_bytes(req) > queue_atomic_write_unit_max_bytes(q))
+ return false;
+
+ if (boundary_bytes) {
+ u64 mask = boundary_bytes - 1, imask = ~mask;
+ u64 start = blk_rq_pos(req) << SECTOR_SHIFT;
+ u64 end = start + blk_rq_bytes(req) - 1;
+
+ /* If greater then must be crossing a boundary */
+ if (blk_rq_bytes(req) > boundary_bytes)
+ return false;
+
+ if ((start & imask) != (end & imask))
+ return false;
+ }
+
+ return true;
+}
+
static inline blk_status_t nvme_setup_rw(struct nvme_ns *ns,
struct request *req, struct nvme_command *cmnd,
enum nvme_opcode op)
@@ -948,6 +972,12 @@ static inline blk_status_t nvme_setup_rw(struct nvme_ns *ns,
if (req->cmd_flags & REQ_RAHEAD)
dsmgmt |= NVME_RW_DSM_FREQ_PREFETCH;
+ /*
+ * Ensure that nothing has been sent which cannot be executed
+ * atomically.
+ */
+ if (req->cmd_flags & REQ_ATOMIC && !nvme_valid_atomic_write(req))
+ return BLK_STS_IOERR;
cmnd->rw.opcode = op;
cmnd->rw.flags = 0;
@@ -1960,6 +1990,45 @@ static void nvme_set_queue_limits(struct nvme_ctrl *ctrl,
blk_queue_write_cache(q, vwc, vwc);
}
+static void nvme_update_atomic_write_disk_info(struct nvme_ctrl *ctrl,
+ struct gendisk *disk, struct nvme_id_ns *id, u32 bs,
+ u32 atomic_bs)
+{
+ unsigned int unit_min = 0, unit_max = 0, boundary = 0, max_bytes = 0;
+ struct request_queue *q = disk->queue;
+
+ if (id->nsfeat & NVME_NS_FEAT_ATOMICS && id->nawupf) {
+ if (le16_to_cpu(id->nabspf))
+ boundary = (le16_to_cpu(id->nabspf) + 1) * bs;
+
+ /*
+ * The boundary size just needs to be a multiple of unit_max
+ * (and not necessarily a power-of-2), so this could be relaxed
+ * in the block layer in future.
+ * Furthermore, if needed, unit_max could be reduced so that the
+ * boundary size was compliant.
+ */
+ if (!boundary || is_power_of_2(boundary)) {
+ max_bytes = atomic_bs;
+ unit_min = bs;
+ unit_max = rounddown_pow_of_two(atomic_bs);
+ } else {
+ dev_notice(ctrl->device, "Unsupported atomic write boundary (%d)\n",
+ boundary);
+ boundary = 0;
+ }
+ } else if (ctrl->subsys->awupf) {
+ max_bytes = atomic_bs;
+ unit_min = bs;
+ unit_max = rounddown_pow_of_two(atomic_bs);
+ }
+
+ blk_queue_atomic_write_max_bytes(q, max_bytes);
+ blk_queue_atomic_write_unit_min_sectors(q, unit_min >> SECTOR_SHIFT);
+ blk_queue_atomic_write_unit_max_sectors(q, unit_max >> SECTOR_SHIFT);
+ blk_queue_atomic_write_boundary_bytes(q, boundary);
+}
+
static void nvme_update_disk_info(struct nvme_ctrl *ctrl, struct gendisk *disk,
struct nvme_ns_head *head, struct nvme_id_ns *id)
{
@@ -1990,6 +2059,9 @@ static void nvme_update_disk_info(struct nvme_ctrl *ctrl, struct gendisk *disk,
atomic_bs = (1 + le16_to_cpu(id->nawupf)) * bs;
else
atomic_bs = (1 + ctrl->subsys->awupf) * bs;
+
+ nvme_update_atomic_write_disk_info(ctrl, disk, id, bs,
+ atomic_bs);
}
if (id->nsfeat & NVME_NS_FEAT_IO_OPT) {