root/usr/src/test/nvme-tests/tests/libnvme/info-roundtrip.c
/*
 * This file and its contents are supplied under the terms of the
 * Common Development and Distribution License ("CDDL"), version 1.0.
 * You may only use this file in accordance with the terms of version
 * 1.0 of the CDDL.
 *
 * A full copy of the text of the CDDL should have accompanied this
 * source.  A copy of the CDDL is also available via the Internet at
 * http://www.illumos.org/license/CDDL.
 */

/*
 * Copyright 2024 Oxide Computer Company
 */

/*
 * Take a controller snapshot. Roundtrip it through a save and restore and make
 * sure that all the data is the same across the two.
 */

#include <err.h>
#include <string.h>

#include "libnvme_test_common.h"

static bool
info_roundtrip_pci(nvme_ctrl_info_t *info, nvme_ctrl_info_t *rest_info)
{
        bool ret = true;
        uint32_t id32, rest_id32;
        uint16_t id16, rest_id16;
        uint8_t id8, rest_id8;

        if (!nvme_ctrl_info_pci_vid(info, &id16)) {
                libnvme_test_ctrl_info_warn(info, "failed to get PCI vendor "
                    "from original snapshot");
                ret = false;
        } else if (!nvme_ctrl_info_pci_vid(rest_info, &rest_id16)) {
                libnvme_test_ctrl_info_warn(info, "failed to get PCI vendor "
                    "from restored snapshot");
                ret = false;
        } else if (id16 != rest_id16) {
                warnx("TEST FAILED: PCI vendor mismatch: was %u now %u",
                    id16, rest_id16);
                ret = false;
        } else {
                (void) printf("TEST PASSED: PCI vendor successfully "
                    "restored\n");
        }

        if (!nvme_ctrl_info_pci_did(info, &id16)) {
                libnvme_test_ctrl_info_warn(info, "failed to get PCI device "
                    "from original snapshot");
                ret = false;
        } else if (!nvme_ctrl_info_pci_did(rest_info, &rest_id16)) {
                libnvme_test_ctrl_info_warn(info, "failed to get PCI device "
                    "from restored snapshot");
                ret = false;
        } else if (id16 != rest_id16) {
                warnx("TEST FAILED: PCI device mismatch: was %u now %u",
                    id16, rest_id16);
                ret = false;
        } else {
                (void) printf("TEST PASSED: PCI device successfully "
                    "restored\n");
        }

        if (!nvme_ctrl_info_pci_subvid(info, &id16)) {
                libnvme_test_ctrl_info_warn(info, "failed to get PCI subsystem "
                    "vendor from original snapshot");
                ret = false;
        } else if (!nvme_ctrl_info_pci_subvid(rest_info, &rest_id16)) {
                libnvme_test_ctrl_info_warn(info, "failed to get PCI subsystem "
                    "vendor from restored snapshot");
                ret = false;
        } else if (id16 != rest_id16) {
                warnx("TEST FAILED: PCI subsystem vendor mismatch: was %u "
                    "now %u", id16, rest_id16);
                ret = false;
        } else {
                (void) printf("TEST PASSED: PCI subsystem vendor successfully "
                    "restored\n");
        }

        if (!nvme_ctrl_info_pci_subsys(info, &id16)) {
                libnvme_test_ctrl_info_warn(info, "failed to get PCI subsystem "
                    "id from original snapshot");
                ret = false;
        } else if (!nvme_ctrl_info_pci_subsys(rest_info, &rest_id16)) {
                libnvme_test_ctrl_info_warn(info, "failed to get PCI subsystem "
                    "id from restored snapshot");
                ret = false;
        } else if (id16 != rest_id16) {
                warnx("TEST FAILED: PCI subsystem id mismatch: was %u "
                    "now %u", id16, rest_id16);
                ret = false;
        } else {
                (void) printf("TEST PASSED: PCI subsystem id successfully "
                    "restored\n");
        }

        if (!nvme_ctrl_info_pci_rev(info, &id8)) {
                libnvme_test_ctrl_info_warn(info, "failed to get PCI revision "
                    "from original snapshot");
                ret = false;
        } else if (!nvme_ctrl_info_pci_rev(rest_info, &rest_id8)) {
                libnvme_test_ctrl_info_warn(info, "failed to get PCI revision "
                    "from restored snapshot");
                ret = false;
        } else if (id8 != rest_id8) {
                warnx("TEST FAILED: PCI revision mismatch: was %u now %u",
                    id8, rest_id8);
                ret = false;
        } else {
                (void) printf("TEST PASSED: PCI revision successfully "
                    "restored\n");
        }

        if (!nvme_ctrl_info_pci_mps_min(info, &id32)) {
                libnvme_test_ctrl_info_warn(info, "failed to get PCI MPS min "
                    "from original snapshot");
                ret = false;
        } else if (!nvme_ctrl_info_pci_mps_min(rest_info, &rest_id32)) {
                libnvme_test_ctrl_info_warn(info, "failed to get PCI MPS min "
                    "from restored snapshot");
                ret = false;
        } else if (id32 != rest_id32) {
                warnx("TEST FAILED: PCI MPS min mismatch: was %u now %u",
                    id32, rest_id32);
                ret = false;
        } else {
                (void) printf("TEST PASSED: PCI MPS min successfully "
                    "restored\n");
        }

        if (!nvme_ctrl_info_pci_mps_max(info, &id32)) {
                libnvme_test_ctrl_info_warn(info, "failed to get PCI MPS max "
                    "from original snapshot");
                ret = false;
        } else if (!nvme_ctrl_info_pci_mps_max(rest_info, &rest_id32)) {
                libnvme_test_ctrl_info_warn(info, "failed to get PCI MPS max "
                    "from restored snapshot");
                ret = false;
        } else if (id32 != rest_id32) {
                warnx("TEST FAILED: PCI MPS max mismatch: was %u now %u",
                    id32, rest_id32);
                ret = false;
        } else {
                (void) printf("TEST PASSED: PCI MPS max successfully "
                    "restored\n");
        }

        if (!nvme_ctrl_info_pci_nintrs(info, &id32)) {
                libnvme_test_ctrl_info_warn(info, "failed to get PCI intr "
                    "count from original snapshot");
                ret = false;
        } else if (!nvme_ctrl_info_pci_nintrs(rest_info, &rest_id32)) {
                libnvme_test_ctrl_info_warn(info, "failed to get PCI intr "
                    "count from restored snapshot");
                ret = false;
        } else if (id32 != rest_id32) {
                warnx("TEST FAILED: PCI intr count mismatch: was %u now %u",
                    id32, rest_id32);
                ret = false;
        } else {
                (void) printf("TEST PASSED: PCI intr count successfully "
                    "restored\n");
        }

        return (ret);
}

static bool
info_roundtrip_ns(nvme_ctrl_info_t *info, nvme_ctrl_info_t *rest_info)
{
        bool ret = true;
        nvme_uint128_t u128, rest_u128;
        const nvme_identify_nsid_t *idns, *rest_idns;

        if (!nvme_ctrl_info_cap(info, &u128)) {
                libnvme_test_ctrl_info_warn(info, "failed to get NVM capacity "
                    "from original snapshot");
                ret = false;
        } else if (!nvme_ctrl_info_cap(rest_info, &rest_u128)) {
                libnvme_test_ctrl_info_warn(info, "failed to get NVM capacity "
                    "from restored snapshot");
                ret = false;
        } else if (memcmp(&u128, &rest_u128, sizeof (nvme_uint128_t)) != 0) {
                warnx("TEST FAILED: NVM capacity mismatch");
                ret = false;
        } else {
                (void) printf("TEST PASSED: NVM capacity successfully "
                    "restored\n");
        }

        if (!nvme_ctrl_info_unalloc_cap(info, &u128)) {
                libnvme_test_ctrl_info_warn(info, "failed to get NVM "
                    "unallocated capacity from original snapshot");
                ret = false;
        } else if (!nvme_ctrl_info_unalloc_cap(rest_info, &rest_u128)) {
                libnvme_test_ctrl_info_warn(info, "failed to get NVM "
                    "unallocated capacity from restored snapshot");
                ret = false;
        } else if (memcmp(&u128, &rest_u128, sizeof (nvme_uint128_t)) != 0) {
                warnx("TEST FAILED: NVM unallocated capacity mismatch");
                ret = false;
        } else {
                (void) printf("TEST PASSED: NVM unallocated capacity "
                    "successfully restored\n");
        }

        if (!nvme_ctrl_info_common_ns(info, &idns)) {
                libnvme_test_ctrl_info_warn(info, "failed to get common ns "
                    "from original snapshot");
                ret = false;
        } else if (!nvme_ctrl_info_common_ns(rest_info, &rest_idns)) {
                libnvme_test_ctrl_info_warn(info, "failed to get common ns "
                    "from restored snapshot");
                ret = false;
        } else if (memcmp(idns, rest_idns,
            sizeof (nvme_identify_nsid_t)) != 0) {
                warnx("TEST FAILED: Common Identify Namespace mismatch");
                ret = false;
        } else {
                (void) printf("TEST PASSED: common identify namespace "
                    "successfully restored\n");
        }

        return (ret);
}

static bool
info_roundtrip_lba(nvme_ctrl_info_t *info, nvme_ctrl_info_t *rest_info)
{
        bool ret = true;
        const uint32_t nlbas = nvme_ctrl_info_nformats(info);

        for (uint32_t i = 0; i < nlbas; i++) {
                const nvme_nvm_lba_fmt_t *fmt, *rest_fmt;

                if (!nvme_ctrl_info_format(info, i, &fmt)) {
                        /*
                         * Some devices like the Kioxia KCD6XLUL3T84 have holes
                         * in their LBA space. Skip such instances.
                         */
                        if (nvme_ctrl_info_err(info) == NVME_INFO_ERR_BAD_FMT) {
                                continue;
                        }

                        libnvme_test_ctrl_info_warn(info, "failed to get "
                            "LBA format %u from original snapshot", i);
                        ret = false;
                        continue;
                }

                if (!nvme_ctrl_info_format(rest_info, i, &rest_fmt)) {
                        libnvme_test_ctrl_info_warn(info, "failed to get "
                            "LBA format %u from restored snapshot", i);
                        ret = false;
                        continue;
                }

                (void) printf("TEST PASSED: successfully got LBA format %u\n",
                    i);
                if (nvme_nvm_lba_fmt_id(fmt) != i) {
                        warnx("TEST FAILED: format %u from original snapshot "
                            "has wrong format id: %u\n", i,
                            nvme_nvm_lba_fmt_id(fmt));
                        ret = false;
                }

                if (nvme_nvm_lba_fmt_id(rest_fmt) != i) {
                        warnx("TEST FAILED: format %u from restored snapshot "
                            "has wrong format id: %u\n", i,
                            nvme_nvm_lba_fmt_id(rest_fmt));
                        ret = false;
                }

                if (nvme_nvm_lba_fmt_meta_size(fmt) !=
                    nvme_nvm_lba_fmt_meta_size(rest_fmt)) {
                        warnx("TEST FAILED: LBA %u metadata size mismatch: "
                            "was %u, now %u", i,
                            nvme_nvm_lba_fmt_meta_size(fmt),
                            nvme_nvm_lba_fmt_meta_size(rest_fmt));
                        ret = false;
                } else {
                        (void) printf("TEST PASSED: LBA %u metadata "
                            "successfully restored\n", i);
                }

                if (nvme_nvm_lba_fmt_data_size(fmt) !=
                    nvme_nvm_lba_fmt_data_size(rest_fmt)) {
                        warnx("TEST FAILED: LBA %u data size mismatch: "
                            "was %" PRIu64 ", now %" PRIu64, i,
                            nvme_nvm_lba_fmt_data_size(fmt),
                            nvme_nvm_lba_fmt_data_size(rest_fmt));
                        ret = false;
                } else {
                        (void) printf("TEST PASSED: LBA %u data size "
                            "successfully restored\n", i);
                }

                if (nvme_nvm_lba_fmt_rel_perf(fmt) !=
                    nvme_nvm_lba_fmt_rel_perf(rest_fmt)) {
                        warnx("TEST FAILED: LBA %u relative perf mismatch: "
                            "was %u, now %u", i,
                            nvme_nvm_lba_fmt_rel_perf(fmt),
                            nvme_nvm_lba_fmt_rel_perf(rest_fmt));
                        ret = false;
                } else {
                        (void) printf("TEST PASSED: LBA %u relative perf "
                            "successfully restored\n", i);
                }
        }

        return (ret);
}

int
main(void)
{
        int ret = EXIT_SUCCESS;
        nvme_t *nvme;
        nvme_ctrl_t *ctrl;
        nvme_ctrl_info_t *info, *rest_info;
        nvlist_t *nvl;
        const nvme_identify_ctrl_t *ctrlid, *rest_ctrlid;
        const nvme_version_t *vers, *rest_vers;

        libnvme_test_init(&nvme, &ctrl);
        if (!nvme_ctrl_info_snap(ctrl, &info)) {
                libnvme_test_ctrl_fatal(ctrl, "failed to take a snapshot");
        }

        if (!nvme_ctrl_info_persist(info, &nvl)) {
                libnvme_test_ctrl_info_fatal(info, "failed to persist the "
                    "controller snapshot");
        }

        if (!nvme_ctrl_info_restore(nvme, nvl, &rest_info)) {
                libnvme_test_hdl_fatal(nvme, "failed to restore controller "
                    "snapshot");
        }

        if (nvme_ctrl_info_vendor(info) != nvme_ctrl_info_vendor(rest_info)) {
                warnx("TEST FAILED: vendor mismatch: orig 0x%x, restored: 0x%x",
                    nvme_ctrl_info_vendor(info),
                    nvme_ctrl_info_vendor(rest_info));
                ret = EXIT_FAILURE;
        } else {
                (void) printf("TEST PASSED: successfully matched vendor id\n");
        }

        ctrlid = nvme_ctrl_info_identify(info);
        rest_ctrlid = nvme_ctrl_info_identify(rest_info);
        if (memcmp(ctrlid, rest_ctrlid, sizeof (nvme_identify_ctrl_t)) != 0) {
                warnx("TEST FAILED: Identify info mismatched after restore");
                ret = EXIT_FAILURE;
        } else {
                (void) printf("TEST PASSED: identify controller successfully "
                    "restored\n");
        }

        vers = nvme_ctrl_info_version(info);
        rest_vers = nvme_ctrl_info_version(rest_info);
        if (vers->v_major != rest_vers->v_major) {
                warnx("TEST FAILED: mismatched major version: was %u, found %u",
                    vers->v_major, rest_vers->v_major);
                ret = EXIT_FAILURE;
        } else {
                (void) printf("TEST PASSED: major version successfully "
                    "restored\n");
        }

        if (vers->v_minor != rest_vers->v_minor) {
                warnx("TEST FAILED: mismatched minor version: was %u, found %u",
                    vers->v_minor, rest_vers->v_minor);
                ret = EXIT_FAILURE;
        } else {
                (void) printf("TEST PASSED: minor version successfully "
                    "restored\n");
        }

        if (strcmp(nvme_ctrl_info_model(info),
            nvme_ctrl_info_model(rest_info)) != 0) {
                warnx("TEST FAILED: model string mismatch");
                ret = EXIT_FAILURE;
        } else {
                (void) printf("TEST PASSED: model successfully restored\n");
        }

        if (strcmp(nvme_ctrl_info_serial(info),
            nvme_ctrl_info_serial(rest_info)) != 0) {
                warnx("TEST FAILED: serial string mismatch");
                ret = EXIT_FAILURE;
        } else {
                (void) printf("TEST PASSED: serial successfully restored\n");
        }

        if (strcmp(nvme_ctrl_info_fwrev(info),
            nvme_ctrl_info_fwrev(rest_info)) != 0) {
                warnx("TEST FAILED: fwrev string mismatch");
                ret = EXIT_FAILURE;
        } else {
                (void) printf("TEST PASSED: fwrev successfully restored\n");
        }

        if (nvme_ctrl_info_nns(info) != nvme_ctrl_info_nns(rest_info)) {
                warnx("TEST FAILED: number of namespaces mismatch: was %u, "
                    "now %u", nvme_ctrl_info_nns(info),
                    nvme_ctrl_info_nns(rest_info));
                ret = EXIT_FAILURE;
        } else {
                (void) printf("TEST PASSED: number of namespaces successfully "
                    "restored\n");
        }

        if (nvme_ctrl_info_type(info) != nvme_ctrl_info_type(rest_info)) {
                warnx("TEST FAILED: controller type mismatch: was %u, "
                    "now %u", nvme_ctrl_info_type(info),
                    nvme_ctrl_info_type(rest_info));
                ret = EXIT_FAILURE;
        } else {
                (void) printf("TEST PASSED: controller type successfully "
                    "restored\n");
        }

        if (nvme_ctrl_info_transport(info) !=
            nvme_ctrl_info_transport(rest_info)) {
                warnx("TEST FAILED: controller transport mismatch: was %u, "
                    "now %u", nvme_ctrl_info_transport(info),
                    nvme_ctrl_info_transport(rest_info));
                ret = EXIT_FAILURE;
        } else {
                (void) printf("TEST PASSED: controller transport successfully "
                    "restored\n");
        }

        if (nvme_ctrl_info_transport(info) == NVME_CTRL_TRANSPORT_PCI &&
            !info_roundtrip_pci(info, rest_info)) {
                ret = EXIT_FAILURE;
        }

        if (ctrlid->id_oacs.oa_nsmgmt != 0 && !info_roundtrip_ns(info,
            rest_info)) {
                ret = EXIT_FAILURE;
        }

        if (nvme_ctrl_info_nformats(info) !=
            nvme_ctrl_info_nformats(rest_info)) {
                warnx("TEST FAILED: number of LBA formats mismatch: was %u, "
                    "now %u", nvme_ctrl_info_nformats(info),
                    nvme_ctrl_info_nformats(rest_info));
                ret = EXIT_FAILURE;
        } else {
                (void) printf("TEST PASSED: number of LBA formats successfully "
                    "restored\n");
        }

        if (nvme_ctrl_info_nformats(info) > 0 && !info_roundtrip_lba(info,
            rest_info)) {
                ret = EXIT_FAILURE;
        }

        nvme_ctrl_info_free(rest_info);
        nvme_ctrl_info_free(info);
        nvme_ctrl_fini(ctrl);
        nvme_fini(nvme);

        if (ret == EXIT_SUCCESS) {
                (void) printf("All tests exited successfully\n");
        }

        return (ret);
}