// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2020 Western Digital Corporation or its affiliates. */ #include #include #include "nvme.h" int nvme_revalidate_zones(struct nvme_ns *ns) { struct request_queue *q = ns->queue; int ret; ret = blk_revalidate_disk_zones(ns->disk, NULL); if (!ret) blk_queue_max_zone_append_sectors(q, ns->ctrl->max_zone_append); return ret; } static int nvme_set_max_append(struct nvme_ctrl *ctrl) { struct nvme_command c = { }; struct nvme_id_ctrl_zns *id; int status; id = kzalloc(sizeof(*id), GFP_KERNEL); if (!id) return -ENOMEM; c.identify.opcode = nvme_admin_identify; c.identify.cns = NVME_ID_CNS_CS_CTRL; c.identify.csi = NVME_CSI_ZNS; status = nvme_submit_sync_cmd(ctrl->admin_q, &c, id, sizeof(*id)); if (status) { kfree(id); return status; } if (id->zasl) ctrl->max_zone_append = 1 << (id->zasl + 3); else ctrl->max_zone_append = ctrl->max_hw_sectors; kfree(id); return 0; } int nvme_update_zone_info(struct nvme_ns *ns, unsigned lbaf) { struct nvme_effects_log *log = ns->head->effects; struct request_queue *q = ns->queue; struct nvme_command c = { }; struct nvme_id_ns_zns *id; int status; /* Driver requires zone append support */ if ((le32_to_cpu(log->iocs[nvme_cmd_zone_append]) & NVME_CMD_EFFECTS_CSUPP)) { if (test_and_clear_bit(NVME_NS_FORCE_RO, &ns->flags)) dev_warn(ns->ctrl->device, "Zone Append supported for zoned namespace:%d. Remove read-only mode\n", ns->head->ns_id); } else { set_bit(NVME_NS_FORCE_RO, &ns->flags); dev_warn(ns->ctrl->device, "Zone Append not supported for zoned namespace:%d. Forcing to read-only mode\n", ns->head->ns_id); } /* Lazily query controller append limit for the first zoned namespace */ if (!ns->ctrl->max_zone_append) { status = nvme_set_max_append(ns->ctrl); if (status) return status; } id = kzalloc(sizeof(*id), GFP_KERNEL); if (!id) return -ENOMEM; c.identify.opcode = nvme_admin_identify; c.identify.nsid = cpu_to_le32(ns->head->ns_id); c.identify.cns = NVME_ID_CNS_CS_NS; c.identify.csi = NVME_CSI_ZNS; status = nvme_submit_sync_cmd(ns->ctrl->admin_q, &c, id, sizeof(*id)); if (status) goto free_data; /* * We currently do not handle devices requiring any of the zoned * operation characteristics. */ if (id->zoc) { dev_warn(ns->ctrl->device, "zone operations:%x not supported for namespace:%u\n", le16_to_cpu(id->zoc), ns->head->ns_id); status = -ENODEV; goto free_data; } ns->zsze = nvme_lba_to_sect(ns, le64_to_cpu(id->lbafe[lbaf].zsze)); if (!is_power_of_2(ns->zsze)) { dev_warn(ns->ctrl->device, "invalid zone size:%llu for namespace:%u\n", ns->zsze, ns->head->ns_id); status = -ENODEV; goto free_data; } blk_queue_set_zoned(ns->disk, BLK_ZONED_HM); blk_queue_flag_set(QUEUE_FLAG_ZONE_RESETALL, q); blk_queue_max_open_zones(q, le32_to_cpu(id->mor) + 1); blk_queue_max_active_zones(q, le32_to_cpu(id->mar) + 1); free_data: kfree(id); return status; } static void *nvme_zns_alloc_report_buffer(struct nvme_ns *ns, unsigned int nr_zones, size_t *buflen) { struct request_queue *q = ns->disk->queue; size_t bufsize; void *buf; const size_t min_bufsize = sizeof(struct nvme_zone_report) + sizeof(struct nvme_zone_descriptor); nr_zones = min_t(unsigned int, nr_zones, get_capacity(ns->disk) >> ilog2(ns->zsze)); bufsize = sizeof(struct nvme_zone_report) + nr_zones * sizeof(struct nvme_zone_descriptor); bufsize = min_t(size_t, bufsize, queue_max_hw_sectors(q) << SECTOR_SHIFT); bufsize = min_t(size_t, bufsize, queue_max_segments(q) << PAGE_SHIFT); while (bufsize >= min_bufsize) { buf = __vmalloc(bufsize, GFP_KERNEL | __GFP_NORETRY); if (buf) { *buflen = bufsize; return buf; } bufsize >>= 1; } return NULL; } static int nvme_zone_parse_entry(struct nvme_ns *ns, struct nvme_zone_descriptor *entry, unsigned int idx, report_zones_cb cb, void *data) { struct blk_zone zone = { }; if ((entry->zt & 0xf) != NVME_ZONE_TYPE_SEQWRITE_REQ) { dev_err(ns->ctrl->device, "invalid zone type %#x\n", entry->zt); return -EINVAL; } zone.type = BLK_ZONE_TYPE_SEQWRITE_REQ; zone.cond = entry->zs >> 4; zone.len = ns->zsze; zone.capacity = nvme_lba_to_sect(ns, le64_to_cpu(entry->zcap)); zone.start = nvme_lba_to_sect(ns, le64_to_cpu(entry->zslba)); zone.wp = nvme_lba_to_sect(ns, le64_to_cpu(entry->wp)); return cb(&zone, idx, data); } int nvme_ns_report_zones(struct nvme_ns *ns, sector_t sector, unsigned int nr_zones, report_zones_cb cb, void *data) { struct nvme_zone_report *report; struct nvme_command c = { }; int ret, zone_idx = 0; unsigned int nz, i; size_t buflen; if (ns->head->ids.csi != NVME_CSI_ZNS) return -EINVAL; report = nvme_zns_alloc_report_buffer(ns, nr_zones, &buflen); if (!report) return -ENOMEM; c.zmr.opcode = nvme_cmd_zone_mgmt_recv; c.zmr.nsid = cpu_to_le32(ns->head->ns_id); c.zmr.numd = cpu_to_le32(nvme_bytes_to_numd(buflen)); c.zmr.zra = NVME_ZRA_ZONE_REPORT; c.zmr.zrasf = NVME_ZRASF_ZONE_REPORT_ALL; c.zmr.pr = NVME_REPORT_ZONE_PARTIAL; sector &= ~(ns->zsze - 1); while (zone_idx < nr_zones && sector < get_capacity(ns->disk)) { memset(report, 0, buflen); c.zmr.slba = cpu_to_le64(nvme_sect_to_lba(ns, sector)); ret = nvme_submit_sync_cmd(ns->queue, &c, report, buflen); if (ret) { if (ret > 0) ret = -EIO; goto out_free; } nz = min((unsigned int)le64_to_cpu(report->nr_zones), nr_zones); if (!nz) break; for (i = 0; i < nz && zone_idx < nr_zones; i++) { ret = nvme_zone_parse_entry(ns, &report->entries[i], zone_idx, cb, data); if (ret) goto out_free; zone_idx++; } sector += ns->zsze * nz; } if (zone_idx > 0) ret = zone_idx; else ret = -EINVAL; out_free: kvfree(report); return ret; } blk_status_t nvme_setup_zone_mgmt_send(struct nvme_ns *ns, struct request *req, struct nvme_command *c, enum nvme_zone_mgmt_action action) { c->zms.opcode = nvme_cmd_zone_mgmt_send; c->zms.nsid = cpu_to_le32(ns->head->ns_id); c->zms.slba = cpu_to_le64(nvme_sect_to_lba(ns, blk_rq_pos(req))); c->zms.zsa = action; if (req_op(req) == REQ_OP_ZONE_RESET_ALL) c->zms.select_all = 1; return BLK_STS_OK; }