root/drivers/nvme/host/zns.c
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) 2020 Western Digital Corporation or its affiliates.
 */

#include <linux/blkdev.h>
#include <linux/vmalloc.h>
#include "nvme.h"

static int nvme_set_max_append(struct nvme_ctrl *ctrl)
{
        struct nvme_command c = { };
        struct nvme_id_ctrl_zns *id;
        int status;

        id = kzalloc_obj(*id);
        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_query_zone_info(struct nvme_ns *ns, unsigned lbaf,
                struct nvme_zone_info *zi)
{
        struct nvme_effects_log *log = ns->head->effects;
        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_obj(*id);
        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;
        }

        zi->zone_size = le64_to_cpu(id->lbafe[lbaf].zsze);
        if (!is_power_of_2(zi->zone_size)) {
                dev_warn(ns->ctrl->device,
                        "invalid zone size: %llu for namespace: %u\n",
                        zi->zone_size, ns->head->ns_id);
                status = -ENODEV;
                goto free_data;
        }
        zi->max_open_zones = le32_to_cpu(id->mor) + 1;
        zi->max_active_zones = le32_to_cpu(id->mar) + 1;

free_data:
        kfree(id);
        return status;
}

void nvme_update_zone_info(struct nvme_ns *ns, struct queue_limits *lim,
                struct nvme_zone_info *zi)
{
        lim->features |= BLK_FEAT_ZONED;
        lim->max_open_zones = zi->max_open_zones;
        lim->max_active_zones = zi->max_active_zones;
        lim->max_hw_zone_append_sectors = ns->ctrl->max_zone_append;
        lim->chunk_sectors = ns->head->zsze =
                nvme_lba_to_sect(ns->head, zi->zone_size);
}

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->head->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,
                                 struct blk_report_zones_args *args)
{
        struct nvme_ns_head *head = ns->head;
        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 = head->zsze;
        zone.capacity = nvme_lba_to_sect(head, le64_to_cpu(entry->zcap));
        zone.start = nvme_lba_to_sect(head, le64_to_cpu(entry->zslba));
        if (zone.cond == BLK_ZONE_COND_FULL)
                zone.wp = zone.start + zone.len;
        else
                zone.wp = nvme_lba_to_sect(head, le64_to_cpu(entry->wp));

        return disk_report_zone(ns->disk, &zone, idx, args);
}

int nvme_ns_report_zones(struct nvme_ns *ns, sector_t sector,
                unsigned int nr_zones, struct blk_report_zones_args *args)
{
        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->head->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->head, 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, args);
                        if (ret)
                                goto out_free;
                        zone_idx++;
                }

                sector += ns->head->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)
{
        memset(c, 0, sizeof(*c));

        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->head, 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;
}