|  | /* | 
|  | * QEMU NVM Express End-to-End Data Protection support | 
|  | * | 
|  | * Copyright (c) 2021 Samsung Electronics Co., Ltd. | 
|  | * | 
|  | * Authors: | 
|  | *   Klaus Jensen           <k.jensen@samsung.com> | 
|  | *   Gollu Appalanaidu      <anaidu.gollu@samsung.com> | 
|  | */ | 
|  |  | 
|  | #include "qemu/osdep.h" | 
|  | #include "qapi/error.h" | 
|  | #include "sysemu/block-backend.h" | 
|  |  | 
|  | #include "nvme.h" | 
|  | #include "dif.h" | 
|  | #include "trace.h" | 
|  |  | 
|  | uint16_t nvme_check_prinfo(NvmeNamespace *ns, uint8_t prinfo, uint64_t slba, | 
|  | uint64_t reftag) | 
|  | { | 
|  | uint64_t mask = ns->pif ? 0xffffffffffff : 0xffffffff; | 
|  |  | 
|  | if ((NVME_ID_NS_DPS_TYPE(ns->id_ns.dps) == NVME_ID_NS_DPS_TYPE_1) && | 
|  | (prinfo & NVME_PRINFO_PRCHK_REF) && (slba & mask) != reftag) { | 
|  | return NVME_INVALID_PROT_INFO | NVME_DNR; | 
|  | } | 
|  |  | 
|  | if ((NVME_ID_NS_DPS_TYPE(ns->id_ns.dps) == NVME_ID_NS_DPS_TYPE_3) && | 
|  | (prinfo & NVME_PRINFO_PRCHK_REF)) { | 
|  | return NVME_INVALID_PROT_INFO; | 
|  | } | 
|  |  | 
|  | return NVME_SUCCESS; | 
|  | } | 
|  |  | 
|  | /* from Linux kernel (crypto/crct10dif_common.c) */ | 
|  | static uint16_t crc16_t10dif(uint16_t crc, const unsigned char *buffer, | 
|  | size_t len) | 
|  | { | 
|  | unsigned int i; | 
|  |  | 
|  | for (i = 0; i < len; i++) { | 
|  | crc = (crc << 8) ^ crc16_t10dif_table[((crc >> 8) ^ buffer[i]) & 0xff]; | 
|  | } | 
|  |  | 
|  | return crc; | 
|  | } | 
|  |  | 
|  | /* from Linux kernel (lib/crc64.c) */ | 
|  | static uint64_t crc64_nvme(uint64_t crc, const unsigned char *buffer, | 
|  | size_t len) | 
|  | { | 
|  | size_t i; | 
|  |  | 
|  | for (i = 0; i < len; i++) { | 
|  | crc = (crc >> 8) ^ crc64_nvme_table[(crc & 0xff) ^ buffer[i]]; | 
|  | } | 
|  |  | 
|  | return crc ^ (uint64_t)~0; | 
|  | } | 
|  |  | 
|  | static void nvme_dif_pract_generate_dif_crc16(NvmeNamespace *ns, uint8_t *buf, | 
|  | size_t len, uint8_t *mbuf, | 
|  | size_t mlen, uint16_t apptag, | 
|  | uint64_t *reftag) | 
|  | { | 
|  | uint8_t *end = buf + len; | 
|  | int16_t pil = 0; | 
|  |  | 
|  | if (!(ns->id_ns.dps & NVME_ID_NS_DPS_FIRST_EIGHT)) { | 
|  | pil = ns->lbaf.ms - nvme_pi_tuple_size(ns); | 
|  | } | 
|  |  | 
|  | trace_pci_nvme_dif_pract_generate_dif_crc16(len, ns->lbasz, | 
|  | ns->lbasz + pil, apptag, | 
|  | *reftag); | 
|  |  | 
|  | for (; buf < end; buf += ns->lbasz, mbuf += ns->lbaf.ms) { | 
|  | NvmeDifTuple *dif = (NvmeDifTuple *)(mbuf + pil); | 
|  | uint16_t crc = crc16_t10dif(0x0, buf, ns->lbasz); | 
|  |  | 
|  | if (pil) { | 
|  | crc = crc16_t10dif(crc, mbuf, pil); | 
|  | } | 
|  |  | 
|  | dif->g16.guard = cpu_to_be16(crc); | 
|  | dif->g16.apptag = cpu_to_be16(apptag); | 
|  | dif->g16.reftag = cpu_to_be32(*reftag); | 
|  |  | 
|  | if (NVME_ID_NS_DPS_TYPE(ns->id_ns.dps) != NVME_ID_NS_DPS_TYPE_3) { | 
|  | (*reftag)++; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | static void nvme_dif_pract_generate_dif_crc64(NvmeNamespace *ns, uint8_t *buf, | 
|  | size_t len, uint8_t *mbuf, | 
|  | size_t mlen, uint16_t apptag, | 
|  | uint64_t *reftag) | 
|  | { | 
|  | uint8_t *end = buf + len; | 
|  | int16_t pil = 0; | 
|  |  | 
|  | if (!(ns->id_ns.dps & NVME_ID_NS_DPS_FIRST_EIGHT)) { | 
|  | pil = ns->lbaf.ms - 16; | 
|  | } | 
|  |  | 
|  | trace_pci_nvme_dif_pract_generate_dif_crc64(len, ns->lbasz, | 
|  | ns->lbasz + pil, apptag, | 
|  | *reftag); | 
|  |  | 
|  | for (; buf < end; buf += ns->lbasz, mbuf += ns->lbaf.ms) { | 
|  | NvmeDifTuple *dif = (NvmeDifTuple *)(mbuf + pil); | 
|  | uint64_t crc = crc64_nvme(~0ULL, buf, ns->lbasz); | 
|  |  | 
|  | if (pil) { | 
|  | crc = crc64_nvme(~crc, mbuf, pil); | 
|  | } | 
|  |  | 
|  | dif->g64.guard = cpu_to_be64(crc); | 
|  | dif->g64.apptag = cpu_to_be16(apptag); | 
|  |  | 
|  | dif->g64.sr[0] = *reftag >> 40; | 
|  | dif->g64.sr[1] = *reftag >> 32; | 
|  | dif->g64.sr[2] = *reftag >> 24; | 
|  | dif->g64.sr[3] = *reftag >> 16; | 
|  | dif->g64.sr[4] = *reftag >> 8; | 
|  | dif->g64.sr[5] = *reftag; | 
|  |  | 
|  | if (NVME_ID_NS_DPS_TYPE(ns->id_ns.dps) != NVME_ID_NS_DPS_TYPE_3) { | 
|  | (*reftag)++; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | void nvme_dif_pract_generate_dif(NvmeNamespace *ns, uint8_t *buf, size_t len, | 
|  | uint8_t *mbuf, size_t mlen, uint16_t apptag, | 
|  | uint64_t *reftag) | 
|  | { | 
|  | switch (ns->pif) { | 
|  | case NVME_PI_GUARD_16: | 
|  | return nvme_dif_pract_generate_dif_crc16(ns, buf, len, mbuf, mlen, | 
|  | apptag, reftag); | 
|  | case NVME_PI_GUARD_64: | 
|  | return nvme_dif_pract_generate_dif_crc64(ns, buf, len, mbuf, mlen, | 
|  | apptag, reftag); | 
|  | } | 
|  |  | 
|  | abort(); | 
|  | } | 
|  |  | 
|  | static uint16_t nvme_dif_prchk_crc16(NvmeNamespace *ns, NvmeDifTuple *dif, | 
|  | uint8_t *buf, uint8_t *mbuf, size_t pil, | 
|  | uint8_t prinfo, uint16_t apptag, | 
|  | uint16_t appmask, uint64_t reftag) | 
|  | { | 
|  | switch (NVME_ID_NS_DPS_TYPE(ns->id_ns.dps)) { | 
|  | case NVME_ID_NS_DPS_TYPE_3: | 
|  | if (be32_to_cpu(dif->g16.reftag) != 0xffffffff) { | 
|  | break; | 
|  | } | 
|  |  | 
|  | /* fallthrough */ | 
|  | case NVME_ID_NS_DPS_TYPE_1: | 
|  | case NVME_ID_NS_DPS_TYPE_2: | 
|  | if (be16_to_cpu(dif->g16.apptag) != 0xffff) { | 
|  | break; | 
|  | } | 
|  |  | 
|  | trace_pci_nvme_dif_prchk_disabled_crc16(be16_to_cpu(dif->g16.apptag), | 
|  | be32_to_cpu(dif->g16.reftag)); | 
|  |  | 
|  | return NVME_SUCCESS; | 
|  | } | 
|  |  | 
|  | if (prinfo & NVME_PRINFO_PRCHK_GUARD) { | 
|  | uint16_t crc = crc16_t10dif(0x0, buf, ns->lbasz); | 
|  |  | 
|  | if (pil) { | 
|  | crc = crc16_t10dif(crc, mbuf, pil); | 
|  | } | 
|  |  | 
|  | trace_pci_nvme_dif_prchk_guard_crc16(be16_to_cpu(dif->g16.guard), crc); | 
|  |  | 
|  | if (be16_to_cpu(dif->g16.guard) != crc) { | 
|  | return NVME_E2E_GUARD_ERROR; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (prinfo & NVME_PRINFO_PRCHK_APP) { | 
|  | trace_pci_nvme_dif_prchk_apptag(be16_to_cpu(dif->g16.apptag), apptag, | 
|  | appmask); | 
|  |  | 
|  | if ((be16_to_cpu(dif->g16.apptag) & appmask) != (apptag & appmask)) { | 
|  | return NVME_E2E_APP_ERROR; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (prinfo & NVME_PRINFO_PRCHK_REF) { | 
|  | trace_pci_nvme_dif_prchk_reftag_crc16(be32_to_cpu(dif->g16.reftag), | 
|  | reftag); | 
|  |  | 
|  | if (be32_to_cpu(dif->g16.reftag) != reftag) { | 
|  | return NVME_E2E_REF_ERROR; | 
|  | } | 
|  | } | 
|  |  | 
|  | return NVME_SUCCESS; | 
|  | } | 
|  |  | 
|  | static uint16_t nvme_dif_prchk_crc64(NvmeNamespace *ns, NvmeDifTuple *dif, | 
|  | uint8_t *buf, uint8_t *mbuf, size_t pil, | 
|  | uint8_t prinfo, uint16_t apptag, | 
|  | uint16_t appmask, uint64_t reftag) | 
|  | { | 
|  | uint64_t r = 0; | 
|  |  | 
|  | r |= (uint64_t)dif->g64.sr[0] << 40; | 
|  | r |= (uint64_t)dif->g64.sr[1] << 32; | 
|  | r |= (uint64_t)dif->g64.sr[2] << 24; | 
|  | r |= (uint64_t)dif->g64.sr[3] << 16; | 
|  | r |= (uint64_t)dif->g64.sr[4] << 8; | 
|  | r |= (uint64_t)dif->g64.sr[5]; | 
|  |  | 
|  | switch (NVME_ID_NS_DPS_TYPE(ns->id_ns.dps)) { | 
|  | case NVME_ID_NS_DPS_TYPE_3: | 
|  | if (r != 0xffffffffffff) { | 
|  | break; | 
|  | } | 
|  |  | 
|  | /* fallthrough */ | 
|  | case NVME_ID_NS_DPS_TYPE_1: | 
|  | case NVME_ID_NS_DPS_TYPE_2: | 
|  | if (be16_to_cpu(dif->g64.apptag) != 0xffff) { | 
|  | break; | 
|  | } | 
|  |  | 
|  | trace_pci_nvme_dif_prchk_disabled_crc64(be16_to_cpu(dif->g16.apptag), | 
|  | r); | 
|  |  | 
|  | return NVME_SUCCESS; | 
|  | } | 
|  |  | 
|  | if (prinfo & NVME_PRINFO_PRCHK_GUARD) { | 
|  | uint64_t crc = crc64_nvme(~0ULL, buf, ns->lbasz); | 
|  |  | 
|  | if (pil) { | 
|  | crc = crc64_nvme(~crc, mbuf, pil); | 
|  | } | 
|  |  | 
|  | trace_pci_nvme_dif_prchk_guard_crc64(be64_to_cpu(dif->g64.guard), crc); | 
|  |  | 
|  | if (be64_to_cpu(dif->g64.guard) != crc) { | 
|  | return NVME_E2E_GUARD_ERROR; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (prinfo & NVME_PRINFO_PRCHK_APP) { | 
|  | trace_pci_nvme_dif_prchk_apptag(be16_to_cpu(dif->g64.apptag), apptag, | 
|  | appmask); | 
|  |  | 
|  | if ((be16_to_cpu(dif->g64.apptag) & appmask) != (apptag & appmask)) { | 
|  | return NVME_E2E_APP_ERROR; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (prinfo & NVME_PRINFO_PRCHK_REF) { | 
|  | trace_pci_nvme_dif_prchk_reftag_crc64(r, reftag); | 
|  |  | 
|  | if (r != reftag) { | 
|  | return NVME_E2E_REF_ERROR; | 
|  | } | 
|  | } | 
|  |  | 
|  | return NVME_SUCCESS; | 
|  | } | 
|  |  | 
|  | static uint16_t nvme_dif_prchk(NvmeNamespace *ns, NvmeDifTuple *dif, | 
|  | uint8_t *buf, uint8_t *mbuf, size_t pil, | 
|  | uint8_t prinfo, uint16_t apptag, | 
|  | uint16_t appmask, uint64_t reftag) | 
|  | { | 
|  | switch (ns->pif) { | 
|  | case NVME_PI_GUARD_16: | 
|  | return nvme_dif_prchk_crc16(ns, dif, buf, mbuf, pil, prinfo, apptag, | 
|  | appmask, reftag); | 
|  | case NVME_PI_GUARD_64: | 
|  | return nvme_dif_prchk_crc64(ns, dif, buf, mbuf, pil, prinfo, apptag, | 
|  | appmask, reftag); | 
|  | } | 
|  |  | 
|  | abort(); | 
|  | } | 
|  |  | 
|  | uint16_t nvme_dif_check(NvmeNamespace *ns, uint8_t *buf, size_t len, | 
|  | uint8_t *mbuf, size_t mlen, uint8_t prinfo, | 
|  | uint64_t slba, uint16_t apptag, | 
|  | uint16_t appmask, uint64_t *reftag) | 
|  | { | 
|  | uint8_t *bufp, *end = buf + len; | 
|  | int16_t pil = 0; | 
|  | uint16_t status; | 
|  |  | 
|  | status = nvme_check_prinfo(ns, prinfo, slba, *reftag); | 
|  | if (status) { | 
|  | return status; | 
|  | } | 
|  |  | 
|  | if (!(ns->id_ns.dps & NVME_ID_NS_DPS_FIRST_EIGHT)) { | 
|  | pil = ns->lbaf.ms - nvme_pi_tuple_size(ns); | 
|  | } | 
|  |  | 
|  | trace_pci_nvme_dif_check(prinfo, ns->lbasz + pil); | 
|  |  | 
|  | for (bufp = buf; bufp < end; bufp += ns->lbasz, mbuf += ns->lbaf.ms) { | 
|  | NvmeDifTuple *dif = (NvmeDifTuple *)(mbuf + pil); | 
|  | status = nvme_dif_prchk(ns, dif, bufp, mbuf, pil, prinfo, apptag, | 
|  | appmask, *reftag); | 
|  | if (status) { | 
|  | /* | 
|  | * The first block of a 'raw' image is always allocated, so we | 
|  | * cannot reliably know if the block is all zeroes or not. For | 
|  | * CRC16 this works fine because the T10 CRC16 is 0x0 for all | 
|  | * zeroes, but the Rocksoft CRC64 is not. Thus, if a guard error is | 
|  | * detected for the first block, check if it is zeroed and manually | 
|  | * set the protection information to all ones to disable protection | 
|  | * information checking. | 
|  | */ | 
|  | if (status == NVME_E2E_GUARD_ERROR && slba == 0x0 && bufp == buf) { | 
|  | g_autofree uint8_t *zeroes = g_malloc0(ns->lbasz); | 
|  |  | 
|  | if (memcmp(bufp, zeroes, ns->lbasz) == 0) { | 
|  | memset(mbuf + pil, 0xff, nvme_pi_tuple_size(ns)); | 
|  | } | 
|  | } else { | 
|  | return status; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (NVME_ID_NS_DPS_TYPE(ns->id_ns.dps) != NVME_ID_NS_DPS_TYPE_3) { | 
|  | (*reftag)++; | 
|  | } | 
|  | } | 
|  |  | 
|  | return NVME_SUCCESS; | 
|  | } | 
|  |  | 
|  | uint16_t nvme_dif_mangle_mdata(NvmeNamespace *ns, uint8_t *mbuf, size_t mlen, | 
|  | uint64_t slba) | 
|  | { | 
|  | BlockBackend *blk = ns->blkconf.blk; | 
|  | BlockDriverState *bs = blk_bs(blk); | 
|  |  | 
|  | int64_t moffset = 0, offset = nvme_l2b(ns, slba); | 
|  | uint8_t *mbufp, *end; | 
|  | bool zeroed; | 
|  | int16_t pil = 0; | 
|  | int64_t bytes = (mlen / ns->lbaf.ms) << ns->lbaf.ds; | 
|  | int64_t pnum = 0; | 
|  |  | 
|  | Error *err = NULL; | 
|  |  | 
|  |  | 
|  | if (!(ns->id_ns.dps & NVME_ID_NS_DPS_FIRST_EIGHT)) { | 
|  | pil = ns->lbaf.ms - nvme_pi_tuple_size(ns); | 
|  | } | 
|  |  | 
|  | do { | 
|  | int ret; | 
|  |  | 
|  | bytes -= pnum; | 
|  |  | 
|  | ret = bdrv_block_status(bs, offset, bytes, &pnum, NULL, NULL); | 
|  | if (ret < 0) { | 
|  | error_setg_errno(&err, -ret, "unable to get block status"); | 
|  | error_report_err(err); | 
|  |  | 
|  | return NVME_INTERNAL_DEV_ERROR; | 
|  | } | 
|  |  | 
|  | zeroed = !!(ret & BDRV_BLOCK_ZERO); | 
|  |  | 
|  | trace_pci_nvme_block_status(offset, bytes, pnum, ret, zeroed); | 
|  |  | 
|  | if (zeroed) { | 
|  | mbufp = mbuf + moffset; | 
|  | mlen = (pnum >> ns->lbaf.ds) * ns->lbaf.ms; | 
|  | end = mbufp + mlen; | 
|  |  | 
|  | for (; mbufp < end; mbufp += ns->lbaf.ms) { | 
|  | memset(mbufp + pil, 0xff, nvme_pi_tuple_size(ns)); | 
|  | } | 
|  | } | 
|  |  | 
|  | moffset += (pnum >> ns->lbaf.ds) * ns->lbaf.ms; | 
|  | offset += pnum; | 
|  | } while (pnum != bytes); | 
|  |  | 
|  | return NVME_SUCCESS; | 
|  | } | 
|  |  | 
|  | static void nvme_dif_rw_cb(void *opaque, int ret) | 
|  | { | 
|  | NvmeBounceContext *ctx = opaque; | 
|  | NvmeRequest *req = ctx->req; | 
|  | NvmeNamespace *ns = req->ns; | 
|  | BlockBackend *blk = ns->blkconf.blk; | 
|  |  | 
|  | trace_pci_nvme_dif_rw_cb(nvme_cid(req), blk_name(blk)); | 
|  |  | 
|  | qemu_iovec_destroy(&ctx->data.iov); | 
|  | g_free(ctx->data.bounce); | 
|  |  | 
|  | qemu_iovec_destroy(&ctx->mdata.iov); | 
|  | g_free(ctx->mdata.bounce); | 
|  |  | 
|  | g_free(ctx); | 
|  |  | 
|  | nvme_rw_complete_cb(req, ret); | 
|  | } | 
|  |  | 
|  | static void nvme_dif_rw_check_cb(void *opaque, int ret) | 
|  | { | 
|  | NvmeBounceContext *ctx = opaque; | 
|  | NvmeRequest *req = ctx->req; | 
|  | NvmeNamespace *ns = req->ns; | 
|  | NvmeCtrl *n = nvme_ctrl(req); | 
|  | NvmeRwCmd *rw = (NvmeRwCmd *)&req->cmd; | 
|  | uint64_t slba = le64_to_cpu(rw->slba); | 
|  | uint8_t prinfo = NVME_RW_PRINFO(le16_to_cpu(rw->control)); | 
|  | uint16_t apptag = le16_to_cpu(rw->apptag); | 
|  | uint16_t appmask = le16_to_cpu(rw->appmask); | 
|  | uint64_t reftag = le32_to_cpu(rw->reftag); | 
|  | uint64_t cdw3 = le32_to_cpu(rw->cdw3); | 
|  | uint16_t status; | 
|  |  | 
|  | reftag |= cdw3 << 32; | 
|  |  | 
|  | trace_pci_nvme_dif_rw_check_cb(nvme_cid(req), prinfo, apptag, appmask, | 
|  | reftag); | 
|  |  | 
|  | if (ret) { | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | status = nvme_dif_mangle_mdata(ns, ctx->mdata.bounce, ctx->mdata.iov.size, | 
|  | slba); | 
|  | if (status) { | 
|  | req->status = status; | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | status = nvme_dif_check(ns, ctx->data.bounce, ctx->data.iov.size, | 
|  | ctx->mdata.bounce, ctx->mdata.iov.size, prinfo, | 
|  | slba, apptag, appmask, &reftag); | 
|  | if (status) { | 
|  | req->status = status; | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | status = nvme_bounce_data(n, ctx->data.bounce, ctx->data.iov.size, | 
|  | NVME_TX_DIRECTION_FROM_DEVICE, req); | 
|  | if (status) { | 
|  | req->status = status; | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | if (prinfo & NVME_PRINFO_PRACT && ns->lbaf.ms == nvme_pi_tuple_size(ns)) { | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | status = nvme_bounce_mdata(n, ctx->mdata.bounce, ctx->mdata.iov.size, | 
|  | NVME_TX_DIRECTION_FROM_DEVICE, req); | 
|  | if (status) { | 
|  | req->status = status; | 
|  | } | 
|  |  | 
|  | out: | 
|  | nvme_dif_rw_cb(ctx, ret); | 
|  | } | 
|  |  | 
|  | static void nvme_dif_rw_mdata_in_cb(void *opaque, int ret) | 
|  | { | 
|  | NvmeBounceContext *ctx = opaque; | 
|  | NvmeRequest *req = ctx->req; | 
|  | NvmeNamespace *ns = req->ns; | 
|  | NvmeRwCmd *rw = (NvmeRwCmd *)&req->cmd; | 
|  | uint64_t slba = le64_to_cpu(rw->slba); | 
|  | uint32_t nlb = le16_to_cpu(rw->nlb) + 1; | 
|  | size_t mlen = nvme_m2b(ns, nlb); | 
|  | uint64_t offset = nvme_moff(ns, slba); | 
|  | BlockBackend *blk = ns->blkconf.blk; | 
|  |  | 
|  | trace_pci_nvme_dif_rw_mdata_in_cb(nvme_cid(req), blk_name(blk)); | 
|  |  | 
|  | if (ret) { | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | ctx->mdata.bounce = g_malloc(mlen); | 
|  |  | 
|  | qemu_iovec_reset(&ctx->mdata.iov); | 
|  | qemu_iovec_add(&ctx->mdata.iov, ctx->mdata.bounce, mlen); | 
|  |  | 
|  | req->aiocb = blk_aio_preadv(blk, offset, &ctx->mdata.iov, 0, | 
|  | nvme_dif_rw_check_cb, ctx); | 
|  | return; | 
|  |  | 
|  | out: | 
|  | nvme_dif_rw_cb(ctx, ret); | 
|  | } | 
|  |  | 
|  | static void nvme_dif_rw_mdata_out_cb(void *opaque, int ret) | 
|  | { | 
|  | NvmeBounceContext *ctx = opaque; | 
|  | NvmeRequest *req = ctx->req; | 
|  | NvmeNamespace *ns = req->ns; | 
|  | NvmeRwCmd *rw = (NvmeRwCmd *)&req->cmd; | 
|  | uint64_t slba = le64_to_cpu(rw->slba); | 
|  | uint64_t offset = nvme_moff(ns, slba); | 
|  | BlockBackend *blk = ns->blkconf.blk; | 
|  |  | 
|  | trace_pci_nvme_dif_rw_mdata_out_cb(nvme_cid(req), blk_name(blk)); | 
|  |  | 
|  | if (ret) { | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | req->aiocb = blk_aio_pwritev(blk, offset, &ctx->mdata.iov, 0, | 
|  | nvme_dif_rw_cb, ctx); | 
|  | return; | 
|  |  | 
|  | out: | 
|  | nvme_dif_rw_cb(ctx, ret); | 
|  | } | 
|  |  | 
|  | uint16_t nvme_dif_rw(NvmeCtrl *n, NvmeRequest *req) | 
|  | { | 
|  | NvmeRwCmd *rw = (NvmeRwCmd *)&req->cmd; | 
|  | NvmeNamespace *ns = req->ns; | 
|  | BlockBackend *blk = ns->blkconf.blk; | 
|  | bool wrz = rw->opcode == NVME_CMD_WRITE_ZEROES; | 
|  | uint32_t nlb = le16_to_cpu(rw->nlb) + 1; | 
|  | uint64_t slba = le64_to_cpu(rw->slba); | 
|  | size_t len = nvme_l2b(ns, nlb); | 
|  | size_t mlen = nvme_m2b(ns, nlb); | 
|  | size_t mapped_len = len; | 
|  | int64_t offset = nvme_l2b(ns, slba); | 
|  | uint8_t prinfo = NVME_RW_PRINFO(le16_to_cpu(rw->control)); | 
|  | uint16_t apptag = le16_to_cpu(rw->apptag); | 
|  | uint16_t appmask = le16_to_cpu(rw->appmask); | 
|  | uint64_t reftag = le32_to_cpu(rw->reftag); | 
|  | uint64_t cdw3 = le32_to_cpu(rw->cdw3); | 
|  | bool pract = !!(prinfo & NVME_PRINFO_PRACT); | 
|  | NvmeBounceContext *ctx; | 
|  | uint16_t status; | 
|  |  | 
|  | reftag |= cdw3 << 32; | 
|  |  | 
|  | trace_pci_nvme_dif_rw(pract, prinfo); | 
|  |  | 
|  | ctx = g_new0(NvmeBounceContext, 1); | 
|  | ctx->req = req; | 
|  |  | 
|  | if (wrz) { | 
|  | BdrvRequestFlags flags = BDRV_REQ_MAY_UNMAP; | 
|  |  | 
|  | if (prinfo & NVME_PRINFO_PRCHK_MASK) { | 
|  | status = NVME_INVALID_PROT_INFO | NVME_DNR; | 
|  | goto err; | 
|  | } | 
|  |  | 
|  | if (pract) { | 
|  | uint8_t *mbuf, *end; | 
|  | int16_t pil = ns->lbaf.ms - nvme_pi_tuple_size(ns); | 
|  |  | 
|  | status = nvme_check_prinfo(ns, prinfo, slba, reftag); | 
|  | if (status) { | 
|  | goto err; | 
|  | } | 
|  |  | 
|  | flags = 0; | 
|  |  | 
|  | ctx->mdata.bounce = g_malloc0(mlen); | 
|  |  | 
|  | qemu_iovec_init(&ctx->mdata.iov, 1); | 
|  | qemu_iovec_add(&ctx->mdata.iov, ctx->mdata.bounce, mlen); | 
|  |  | 
|  | mbuf = ctx->mdata.bounce; | 
|  | end = mbuf + mlen; | 
|  |  | 
|  | if (ns->id_ns.dps & NVME_ID_NS_DPS_FIRST_EIGHT) { | 
|  | pil = 0; | 
|  | } | 
|  |  | 
|  | for (; mbuf < end; mbuf += ns->lbaf.ms) { | 
|  | NvmeDifTuple *dif = (NvmeDifTuple *)(mbuf + pil); | 
|  |  | 
|  | switch (ns->pif) { | 
|  | case NVME_PI_GUARD_16: | 
|  | dif->g16.apptag = cpu_to_be16(apptag); | 
|  | dif->g16.reftag = cpu_to_be32(reftag); | 
|  |  | 
|  | break; | 
|  |  | 
|  | case NVME_PI_GUARD_64: | 
|  | dif->g64.guard = cpu_to_be64(0x6482d367eb22b64e); | 
|  | dif->g64.apptag = cpu_to_be16(apptag); | 
|  |  | 
|  | dif->g64.sr[0] = reftag >> 40; | 
|  | dif->g64.sr[1] = reftag >> 32; | 
|  | dif->g64.sr[2] = reftag >> 24; | 
|  | dif->g64.sr[3] = reftag >> 16; | 
|  | dif->g64.sr[4] = reftag >> 8; | 
|  | dif->g64.sr[5] = reftag; | 
|  |  | 
|  | break; | 
|  |  | 
|  | default: | 
|  | abort(); | 
|  | } | 
|  |  | 
|  | switch (NVME_ID_NS_DPS_TYPE(ns->id_ns.dps)) { | 
|  | case NVME_ID_NS_DPS_TYPE_1: | 
|  | case NVME_ID_NS_DPS_TYPE_2: | 
|  | reftag++; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | req->aiocb = blk_aio_pwrite_zeroes(blk, offset, len, flags, | 
|  | nvme_dif_rw_mdata_out_cb, ctx); | 
|  | return NVME_NO_COMPLETE; | 
|  | } | 
|  |  | 
|  | if (nvme_ns_ext(ns) && !(pract && ns->lbaf.ms == nvme_pi_tuple_size(ns))) { | 
|  | mapped_len += mlen; | 
|  | } | 
|  |  | 
|  | status = nvme_map_dptr(n, &req->sg, mapped_len, &req->cmd); | 
|  | if (status) { | 
|  | goto err; | 
|  | } | 
|  |  | 
|  | ctx->data.bounce = g_malloc(len); | 
|  |  | 
|  | qemu_iovec_init(&ctx->data.iov, 1); | 
|  | qemu_iovec_add(&ctx->data.iov, ctx->data.bounce, len); | 
|  |  | 
|  | if (req->cmd.opcode == NVME_CMD_READ) { | 
|  | block_acct_start(blk_get_stats(blk), &req->acct, ctx->data.iov.size, | 
|  | BLOCK_ACCT_READ); | 
|  |  | 
|  | req->aiocb = blk_aio_preadv(ns->blkconf.blk, offset, &ctx->data.iov, 0, | 
|  | nvme_dif_rw_mdata_in_cb, ctx); | 
|  | return NVME_NO_COMPLETE; | 
|  | } | 
|  |  | 
|  | status = nvme_bounce_data(n, ctx->data.bounce, ctx->data.iov.size, | 
|  | NVME_TX_DIRECTION_TO_DEVICE, req); | 
|  | if (status) { | 
|  | goto err; | 
|  | } | 
|  |  | 
|  | ctx->mdata.bounce = g_malloc(mlen); | 
|  |  | 
|  | qemu_iovec_init(&ctx->mdata.iov, 1); | 
|  | qemu_iovec_add(&ctx->mdata.iov, ctx->mdata.bounce, mlen); | 
|  |  | 
|  | if (!(pract && ns->lbaf.ms == nvme_pi_tuple_size(ns))) { | 
|  | status = nvme_bounce_mdata(n, ctx->mdata.bounce, ctx->mdata.iov.size, | 
|  | NVME_TX_DIRECTION_TO_DEVICE, req); | 
|  | if (status) { | 
|  | goto err; | 
|  | } | 
|  | } | 
|  |  | 
|  | status = nvme_check_prinfo(ns, prinfo, slba, reftag); | 
|  | if (status) { | 
|  | goto err; | 
|  | } | 
|  |  | 
|  | if (pract) { | 
|  | /* splice generated protection information into the buffer */ | 
|  | nvme_dif_pract_generate_dif(ns, ctx->data.bounce, ctx->data.iov.size, | 
|  | ctx->mdata.bounce, ctx->mdata.iov.size, | 
|  | apptag, &reftag); | 
|  | } else { | 
|  | status = nvme_dif_check(ns, ctx->data.bounce, ctx->data.iov.size, | 
|  | ctx->mdata.bounce, ctx->mdata.iov.size, prinfo, | 
|  | slba, apptag, appmask, &reftag); | 
|  | if (status) { | 
|  | goto err; | 
|  | } | 
|  | } | 
|  |  | 
|  | block_acct_start(blk_get_stats(blk), &req->acct, ctx->data.iov.size, | 
|  | BLOCK_ACCT_WRITE); | 
|  |  | 
|  | req->aiocb = blk_aio_pwritev(ns->blkconf.blk, offset, &ctx->data.iov, 0, | 
|  | nvme_dif_rw_mdata_out_cb, ctx); | 
|  |  | 
|  | return NVME_NO_COMPLETE; | 
|  |  | 
|  | err: | 
|  | qemu_iovec_destroy(&ctx->data.iov); | 
|  | g_free(ctx->data.bounce); | 
|  |  | 
|  | qemu_iovec_destroy(&ctx->mdata.iov); | 
|  | g_free(ctx->mdata.bounce); | 
|  |  | 
|  | g_free(ctx); | 
|  |  | 
|  | return status; | 
|  | } |