ebd1568fc7
Adds support for the controller atomic parameters: AWUN and AWUPF. Atomic Compare and Write Unit (ACWU) is not currently supported. Writes that adhere to the ACWU and AWUPF parameters are guaranteed to be atomic. New NVMe QEMU Parameters (See NVMe Specification for details): atomic.dn (default off) - Set the value of Disable Normal. atomic.awun=UINT16 (default: 0) atomic.awupf=UINT16 (default: 0) By default (Disable Normal set to zero), the maximum atomic write size is set to the AWUN value. If Disable Normal is set, the maximum atomic write size is set to AWUPF. Signed-off-by: Alan Adamson <alan.adamson@oracle.com> Reviewed-by: Klaus Jensen <k.jensen@samsung.com> Signed-off-by: Klaus Jensen <k.jensen@samsung.com>
9067 lines
258 KiB
C
9067 lines
258 KiB
C
/*
|
|
* QEMU NVM Express Controller
|
|
*
|
|
* Copyright (c) 2012, Intel Corporation
|
|
*
|
|
* Written by Keith Busch <keith.busch@intel.com>
|
|
*
|
|
* This code is licensed under the GNU GPL v2 or later.
|
|
*/
|
|
|
|
/**
|
|
* Reference Specs: http://www.nvmexpress.org, 1.4, 1.3, 1.2, 1.1, 1.0e
|
|
*
|
|
* https://nvmexpress.org/developers/nvme-specification/
|
|
*
|
|
*
|
|
* Notes on coding style
|
|
* ---------------------
|
|
* While QEMU coding style prefers lowercase hexadecimals in constants, the
|
|
* NVMe subsystem use this format from the NVMe specifications in the comments
|
|
* (i.e. 'h' suffix instead of '0x' prefix).
|
|
*
|
|
* Usage
|
|
* -----
|
|
* See docs/system/nvme.rst for extensive documentation.
|
|
*
|
|
* Add options:
|
|
* -drive file=<file>,if=none,id=<drive_id>
|
|
* -device nvme-subsys,id=<subsys_id>,nqn=<nqn_id>
|
|
* -device nvme,serial=<serial>,id=<bus_name>, \
|
|
* cmb_size_mb=<cmb_size_mb[optional]>, \
|
|
* [pmrdev=<mem_backend_file_id>,] \
|
|
* max_ioqpairs=<N[optional]>, \
|
|
* aerl=<N[optional]>,aer_max_queued=<N[optional]>, \
|
|
* mdts=<N[optional]>,vsl=<N[optional]>, \
|
|
* zoned.zasl=<N[optional]>, \
|
|
* zoned.auto_transition=<on|off[optional]>, \
|
|
* sriov_max_vfs=<N[optional]> \
|
|
* sriov_vq_flexible=<N[optional]> \
|
|
* sriov_vi_flexible=<N[optional]> \
|
|
* sriov_max_vi_per_vf=<N[optional]> \
|
|
* sriov_max_vq_per_vf=<N[optional]> \
|
|
* atomic.dn=<on|off[optional]>, \
|
|
* atomic.awun<N[optional]>, \
|
|
* atomic.awupf<N[optional]>, \
|
|
* subsys=<subsys_id>
|
|
* -device nvme-ns,drive=<drive_id>,bus=<bus_name>,nsid=<nsid>,\
|
|
* zoned=<true|false[optional]>, \
|
|
* subsys=<subsys_id>,shared=<true|false[optional]>, \
|
|
* detached=<true|false[optional]>, \
|
|
* zoned.zone_size=<N[optional]>, \
|
|
* zoned.zone_capacity=<N[optional]>, \
|
|
* zoned.descr_ext_size=<N[optional]>, \
|
|
* zoned.max_active=<N[optional]>, \
|
|
* zoned.max_open=<N[optional]>, \
|
|
* zoned.cross_read=<true|false[optional]>
|
|
*
|
|
* Note cmb_size_mb denotes size of CMB in MB. CMB is assumed to be at
|
|
* offset 0 in BAR2 and supports only WDS, RDS and SQS for now. By default, the
|
|
* device will use the "v1.4 CMB scheme" - use the `legacy-cmb` parameter to
|
|
* always enable the CMBLOC and CMBSZ registers (v1.3 behavior).
|
|
*
|
|
* Enabling pmr emulation can be achieved by pointing to memory-backend-file.
|
|
* For example:
|
|
* -object memory-backend-file,id=<mem_id>,share=on,mem-path=<file_path>, \
|
|
* size=<size> .... -device nvme,...,pmrdev=<mem_id>
|
|
*
|
|
* The PMR will use BAR 4/5 exclusively.
|
|
*
|
|
* To place controller(s) and namespace(s) to a subsystem, then provide
|
|
* nvme-subsys device as above.
|
|
*
|
|
* nvme subsystem device parameters
|
|
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
* - `nqn`
|
|
* This parameter provides the `<nqn_id>` part of the string
|
|
* `nqn.2019-08.org.qemu:<nqn_id>` which will be reported in the SUBNQN field
|
|
* of subsystem controllers. Note that `<nqn_id>` should be unique per
|
|
* subsystem, but this is not enforced by QEMU. If not specified, it will
|
|
* default to the value of the `id` parameter (`<subsys_id>`).
|
|
*
|
|
* nvme device parameters
|
|
* ~~~~~~~~~~~~~~~~~~~~~~
|
|
* - `subsys`
|
|
* Specifying this parameter attaches the controller to the subsystem and
|
|
* the SUBNQN field in the controller will report the NQN of the subsystem
|
|
* device. This also enables multi controller capability represented in
|
|
* Identify Controller data structure in CMIC (Controller Multi-path I/O and
|
|
* Namespace Sharing Capabilities).
|
|
*
|
|
* - `aerl`
|
|
* The Asynchronous Event Request Limit (AERL). Indicates the maximum number
|
|
* of concurrently outstanding Asynchronous Event Request commands support
|
|
* by the controller. This is a 0's based value.
|
|
*
|
|
* - `aer_max_queued`
|
|
* This is the maximum number of events that the device will enqueue for
|
|
* completion when there are no outstanding AERs. When the maximum number of
|
|
* enqueued events are reached, subsequent events will be dropped.
|
|
*
|
|
* - `mdts`
|
|
* Indicates the maximum data transfer size for a command that transfers data
|
|
* between host-accessible memory and the controller. The value is specified
|
|
* as a power of two (2^n) and is in units of the minimum memory page size
|
|
* (CAP.MPSMIN). The default value is 7 (i.e. 512 KiB).
|
|
*
|
|
* - `vsl`
|
|
* Indicates the maximum data size limit for the Verify command. Like `mdts`,
|
|
* this value is specified as a power of two (2^n) and is in units of the
|
|
* minimum memory page size (CAP.MPSMIN). The default value is 7 (i.e. 512
|
|
* KiB).
|
|
*
|
|
* - `zoned.zasl`
|
|
* Indicates the maximum data transfer size for the Zone Append command. Like
|
|
* `mdts`, the value is specified as a power of two (2^n) and is in units of
|
|
* the minimum memory page size (CAP.MPSMIN). The default value is 0 (i.e.
|
|
* defaulting to the value of `mdts`).
|
|
*
|
|
* - `zoned.auto_transition`
|
|
* Indicates if zones in zone state implicitly opened can be automatically
|
|
* transitioned to zone state closed for resource management purposes.
|
|
* Defaults to 'on'.
|
|
*
|
|
* - `sriov_max_vfs`
|
|
* Indicates the maximum number of PCIe virtual functions supported
|
|
* by the controller. The default value is 0. Specifying a non-zero value
|
|
* enables reporting of both SR-IOV and ARI capabilities by the NVMe device.
|
|
* Virtual function controllers will not report SR-IOV capability.
|
|
*
|
|
* NOTE: Single Root I/O Virtualization support is experimental.
|
|
* All the related parameters may be subject to change.
|
|
*
|
|
* - `sriov_vq_flexible`
|
|
* Indicates the total number of flexible queue resources assignable to all
|
|
* the secondary controllers. Implicitly sets the number of primary
|
|
* controller's private resources to `(max_ioqpairs - sriov_vq_flexible)`.
|
|
*
|
|
* - `sriov_vi_flexible`
|
|
* Indicates the total number of flexible interrupt resources assignable to
|
|
* all the secondary controllers. Implicitly sets the number of primary
|
|
* controller's private resources to `(msix_qsize - sriov_vi_flexible)`.
|
|
*
|
|
* - `sriov_max_vi_per_vf`
|
|
* Indicates the maximum number of virtual interrupt resources assignable
|
|
* to a secondary controller. The default 0 resolves to
|
|
* `(sriov_vi_flexible / sriov_max_vfs)`.
|
|
*
|
|
* - `sriov_max_vq_per_vf`
|
|
* Indicates the maximum number of virtual queue resources assignable to
|
|
* a secondary controller. The default 0 resolves to
|
|
* `(sriov_vq_flexible / sriov_max_vfs)`.
|
|
*
|
|
* nvme namespace device parameters
|
|
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
* - `shared`
|
|
* When the parent nvme device (as defined explicitly by the 'bus' parameter
|
|
* or implicitly by the most recently defined NvmeBus) is linked to an
|
|
* nvme-subsys device, the namespace will be attached to all controllers in
|
|
* the subsystem. If set to 'off' (the default), the namespace will remain a
|
|
* private namespace and may only be attached to a single controller at a
|
|
* time.
|
|
*
|
|
* - `detached`
|
|
* This parameter is only valid together with the `subsys` parameter. If left
|
|
* at the default value (`false/off`), the namespace will be attached to all
|
|
* controllers in the NVMe subsystem at boot-up. If set to `true/on`, the
|
|
* namespace will be available in the subsystem but not attached to any
|
|
* controllers.
|
|
*
|
|
* Setting `zoned` to true selects Zoned Command Set at the namespace.
|
|
* In this case, the following namespace properties are available to configure
|
|
* zoned operation:
|
|
* zoned.zone_size=<zone size in bytes, default: 128MiB>
|
|
* The number may be followed by K, M, G as in kilo-, mega- or giga-.
|
|
*
|
|
* zoned.zone_capacity=<zone capacity in bytes, default: zone size>
|
|
* The value 0 (default) forces zone capacity to be the same as zone
|
|
* size. The value of this property may not exceed zone size.
|
|
*
|
|
* zoned.descr_ext_size=<zone descriptor extension size, default 0>
|
|
* This value needs to be specified in 64B units. If it is zero,
|
|
* namespace(s) will not support zone descriptor extensions.
|
|
*
|
|
* zoned.max_active=<Maximum Active Resources (zones), default: 0>
|
|
* The default value means there is no limit to the number of
|
|
* concurrently active zones.
|
|
*
|
|
* zoned.max_open=<Maximum Open Resources (zones), default: 0>
|
|
* The default value means there is no limit to the number of
|
|
* concurrently open zones.
|
|
*
|
|
* zoned.cross_read=<enable RAZB, default: false>
|
|
* Setting this property to true enables Read Across Zone Boundaries.
|
|
*/
|
|
|
|
#include "qemu/osdep.h"
|
|
#include "qemu/cutils.h"
|
|
#include "qemu/error-report.h"
|
|
#include "qemu/log.h"
|
|
#include "qemu/units.h"
|
|
#include "qemu/range.h"
|
|
#include "qapi/error.h"
|
|
#include "qapi/visitor.h"
|
|
#include "sysemu/sysemu.h"
|
|
#include "sysemu/block-backend.h"
|
|
#include "sysemu/hostmem.h"
|
|
#include "hw/pci/msix.h"
|
|
#include "hw/pci/pcie_sriov.h"
|
|
#include "sysemu/spdm-socket.h"
|
|
#include "migration/vmstate.h"
|
|
|
|
#include "nvme.h"
|
|
#include "dif.h"
|
|
#include "trace.h"
|
|
|
|
#define NVME_MAX_IOQPAIRS 0xffff
|
|
#define NVME_DB_SIZE 4
|
|
#define NVME_SPEC_VER 0x00010400
|
|
#define NVME_CMB_BIR 2
|
|
#define NVME_PMR_BIR 4
|
|
#define NVME_TEMPERATURE 0x143
|
|
#define NVME_TEMPERATURE_WARNING 0x157
|
|
#define NVME_TEMPERATURE_CRITICAL 0x175
|
|
#define NVME_NUM_FW_SLOTS 1
|
|
#define NVME_DEFAULT_MAX_ZA_SIZE (128 * KiB)
|
|
#define NVME_VF_RES_GRANULARITY 1
|
|
#define NVME_VF_OFFSET 0x1
|
|
#define NVME_VF_STRIDE 1
|
|
|
|
#define NVME_GUEST_ERR(trace, fmt, ...) \
|
|
do { \
|
|
(trace_##trace)(__VA_ARGS__); \
|
|
qemu_log_mask(LOG_GUEST_ERROR, #trace \
|
|
" in %s: " fmt "\n", __func__, ## __VA_ARGS__); \
|
|
} while (0)
|
|
|
|
static const bool nvme_feature_support[NVME_FID_MAX] = {
|
|
[NVME_ARBITRATION] = true,
|
|
[NVME_POWER_MANAGEMENT] = true,
|
|
[NVME_TEMPERATURE_THRESHOLD] = true,
|
|
[NVME_ERROR_RECOVERY] = true,
|
|
[NVME_VOLATILE_WRITE_CACHE] = true,
|
|
[NVME_NUMBER_OF_QUEUES] = true,
|
|
[NVME_INTERRUPT_COALESCING] = true,
|
|
[NVME_INTERRUPT_VECTOR_CONF] = true,
|
|
[NVME_WRITE_ATOMICITY] = true,
|
|
[NVME_ASYNCHRONOUS_EVENT_CONF] = true,
|
|
[NVME_TIMESTAMP] = true,
|
|
[NVME_HOST_BEHAVIOR_SUPPORT] = true,
|
|
[NVME_COMMAND_SET_PROFILE] = true,
|
|
[NVME_FDP_MODE] = true,
|
|
[NVME_FDP_EVENTS] = true,
|
|
};
|
|
|
|
static const uint32_t nvme_feature_cap[NVME_FID_MAX] = {
|
|
[NVME_TEMPERATURE_THRESHOLD] = NVME_FEAT_CAP_CHANGE,
|
|
[NVME_ERROR_RECOVERY] = NVME_FEAT_CAP_CHANGE | NVME_FEAT_CAP_NS,
|
|
[NVME_VOLATILE_WRITE_CACHE] = NVME_FEAT_CAP_CHANGE,
|
|
[NVME_NUMBER_OF_QUEUES] = NVME_FEAT_CAP_CHANGE,
|
|
[NVME_WRITE_ATOMICITY] = NVME_FEAT_CAP_CHANGE,
|
|
[NVME_ASYNCHRONOUS_EVENT_CONF] = NVME_FEAT_CAP_CHANGE,
|
|
[NVME_TIMESTAMP] = NVME_FEAT_CAP_CHANGE,
|
|
[NVME_HOST_BEHAVIOR_SUPPORT] = NVME_FEAT_CAP_CHANGE,
|
|
[NVME_COMMAND_SET_PROFILE] = NVME_FEAT_CAP_CHANGE,
|
|
[NVME_FDP_MODE] = NVME_FEAT_CAP_CHANGE,
|
|
[NVME_FDP_EVENTS] = NVME_FEAT_CAP_CHANGE | NVME_FEAT_CAP_NS,
|
|
};
|
|
|
|
static const uint32_t nvme_cse_acs[256] = {
|
|
[NVME_ADM_CMD_DELETE_SQ] = NVME_CMD_EFF_CSUPP,
|
|
[NVME_ADM_CMD_CREATE_SQ] = NVME_CMD_EFF_CSUPP,
|
|
[NVME_ADM_CMD_GET_LOG_PAGE] = NVME_CMD_EFF_CSUPP,
|
|
[NVME_ADM_CMD_DELETE_CQ] = NVME_CMD_EFF_CSUPP,
|
|
[NVME_ADM_CMD_CREATE_CQ] = NVME_CMD_EFF_CSUPP,
|
|
[NVME_ADM_CMD_IDENTIFY] = NVME_CMD_EFF_CSUPP,
|
|
[NVME_ADM_CMD_ABORT] = NVME_CMD_EFF_CSUPP,
|
|
[NVME_ADM_CMD_SET_FEATURES] = NVME_CMD_EFF_CSUPP,
|
|
[NVME_ADM_CMD_GET_FEATURES] = NVME_CMD_EFF_CSUPP,
|
|
[NVME_ADM_CMD_ASYNC_EV_REQ] = NVME_CMD_EFF_CSUPP,
|
|
[NVME_ADM_CMD_NS_ATTACHMENT] = NVME_CMD_EFF_CSUPP | NVME_CMD_EFF_NIC,
|
|
[NVME_ADM_CMD_VIRT_MNGMT] = NVME_CMD_EFF_CSUPP,
|
|
[NVME_ADM_CMD_DBBUF_CONFIG] = NVME_CMD_EFF_CSUPP,
|
|
[NVME_ADM_CMD_FORMAT_NVM] = NVME_CMD_EFF_CSUPP | NVME_CMD_EFF_LBCC,
|
|
[NVME_ADM_CMD_DIRECTIVE_RECV] = NVME_CMD_EFF_CSUPP,
|
|
[NVME_ADM_CMD_DIRECTIVE_SEND] = NVME_CMD_EFF_CSUPP,
|
|
};
|
|
|
|
static const uint32_t nvme_cse_iocs_none[256];
|
|
|
|
static const uint32_t nvme_cse_iocs_nvm[256] = {
|
|
[NVME_CMD_FLUSH] = NVME_CMD_EFF_CSUPP | NVME_CMD_EFF_LBCC,
|
|
[NVME_CMD_WRITE_ZEROES] = NVME_CMD_EFF_CSUPP | NVME_CMD_EFF_LBCC,
|
|
[NVME_CMD_WRITE] = NVME_CMD_EFF_CSUPP | NVME_CMD_EFF_LBCC,
|
|
[NVME_CMD_READ] = NVME_CMD_EFF_CSUPP,
|
|
[NVME_CMD_DSM] = NVME_CMD_EFF_CSUPP | NVME_CMD_EFF_LBCC,
|
|
[NVME_CMD_VERIFY] = NVME_CMD_EFF_CSUPP,
|
|
[NVME_CMD_COPY] = NVME_CMD_EFF_CSUPP | NVME_CMD_EFF_LBCC,
|
|
[NVME_CMD_COMPARE] = NVME_CMD_EFF_CSUPP,
|
|
[NVME_CMD_IO_MGMT_RECV] = NVME_CMD_EFF_CSUPP,
|
|
[NVME_CMD_IO_MGMT_SEND] = NVME_CMD_EFF_CSUPP | NVME_CMD_EFF_LBCC,
|
|
};
|
|
|
|
static const uint32_t nvme_cse_iocs_zoned[256] = {
|
|
[NVME_CMD_FLUSH] = NVME_CMD_EFF_CSUPP | NVME_CMD_EFF_LBCC,
|
|
[NVME_CMD_WRITE_ZEROES] = NVME_CMD_EFF_CSUPP | NVME_CMD_EFF_LBCC,
|
|
[NVME_CMD_WRITE] = NVME_CMD_EFF_CSUPP | NVME_CMD_EFF_LBCC,
|
|
[NVME_CMD_READ] = NVME_CMD_EFF_CSUPP,
|
|
[NVME_CMD_DSM] = NVME_CMD_EFF_CSUPP | NVME_CMD_EFF_LBCC,
|
|
[NVME_CMD_VERIFY] = NVME_CMD_EFF_CSUPP,
|
|
[NVME_CMD_COPY] = NVME_CMD_EFF_CSUPP | NVME_CMD_EFF_LBCC,
|
|
[NVME_CMD_COMPARE] = NVME_CMD_EFF_CSUPP,
|
|
[NVME_CMD_ZONE_APPEND] = NVME_CMD_EFF_CSUPP | NVME_CMD_EFF_LBCC,
|
|
[NVME_CMD_ZONE_MGMT_SEND] = NVME_CMD_EFF_CSUPP | NVME_CMD_EFF_LBCC,
|
|
[NVME_CMD_ZONE_MGMT_RECV] = NVME_CMD_EFF_CSUPP,
|
|
};
|
|
|
|
static void nvme_process_sq(void *opaque);
|
|
static void nvme_ctrl_reset(NvmeCtrl *n, NvmeResetType rst);
|
|
static inline uint64_t nvme_get_timestamp(const NvmeCtrl *n);
|
|
|
|
static uint16_t nvme_sqid(NvmeRequest *req)
|
|
{
|
|
return le16_to_cpu(req->sq->sqid);
|
|
}
|
|
|
|
static inline uint16_t nvme_make_pid(NvmeNamespace *ns, uint16_t rg,
|
|
uint16_t ph)
|
|
{
|
|
uint16_t rgif = ns->endgrp->fdp.rgif;
|
|
|
|
if (!rgif) {
|
|
return ph;
|
|
}
|
|
|
|
return (rg << (16 - rgif)) | ph;
|
|
}
|
|
|
|
static inline bool nvme_ph_valid(NvmeNamespace *ns, uint16_t ph)
|
|
{
|
|
return ph < ns->fdp.nphs;
|
|
}
|
|
|
|
static inline bool nvme_rg_valid(NvmeEnduranceGroup *endgrp, uint16_t rg)
|
|
{
|
|
return rg < endgrp->fdp.nrg;
|
|
}
|
|
|
|
static inline uint16_t nvme_pid2ph(NvmeNamespace *ns, uint16_t pid)
|
|
{
|
|
uint16_t rgif = ns->endgrp->fdp.rgif;
|
|
|
|
if (!rgif) {
|
|
return pid;
|
|
}
|
|
|
|
return pid & ((1 << (15 - rgif)) - 1);
|
|
}
|
|
|
|
static inline uint16_t nvme_pid2rg(NvmeNamespace *ns, uint16_t pid)
|
|
{
|
|
uint16_t rgif = ns->endgrp->fdp.rgif;
|
|
|
|
if (!rgif) {
|
|
return 0;
|
|
}
|
|
|
|
return pid >> (16 - rgif);
|
|
}
|
|
|
|
static inline bool nvme_parse_pid(NvmeNamespace *ns, uint16_t pid,
|
|
uint16_t *ph, uint16_t *rg)
|
|
{
|
|
*rg = nvme_pid2rg(ns, pid);
|
|
*ph = nvme_pid2ph(ns, pid);
|
|
|
|
return nvme_ph_valid(ns, *ph) && nvme_rg_valid(ns->endgrp, *rg);
|
|
}
|
|
|
|
static void nvme_assign_zone_state(NvmeNamespace *ns, NvmeZone *zone,
|
|
NvmeZoneState state)
|
|
{
|
|
if (QTAILQ_IN_USE(zone, entry)) {
|
|
switch (nvme_get_zone_state(zone)) {
|
|
case NVME_ZONE_STATE_EXPLICITLY_OPEN:
|
|
QTAILQ_REMOVE(&ns->exp_open_zones, zone, entry);
|
|
break;
|
|
case NVME_ZONE_STATE_IMPLICITLY_OPEN:
|
|
QTAILQ_REMOVE(&ns->imp_open_zones, zone, entry);
|
|
break;
|
|
case NVME_ZONE_STATE_CLOSED:
|
|
QTAILQ_REMOVE(&ns->closed_zones, zone, entry);
|
|
break;
|
|
case NVME_ZONE_STATE_FULL:
|
|
QTAILQ_REMOVE(&ns->full_zones, zone, entry);
|
|
default:
|
|
;
|
|
}
|
|
}
|
|
|
|
nvme_set_zone_state(zone, state);
|
|
|
|
switch (state) {
|
|
case NVME_ZONE_STATE_EXPLICITLY_OPEN:
|
|
QTAILQ_INSERT_TAIL(&ns->exp_open_zones, zone, entry);
|
|
break;
|
|
case NVME_ZONE_STATE_IMPLICITLY_OPEN:
|
|
QTAILQ_INSERT_TAIL(&ns->imp_open_zones, zone, entry);
|
|
break;
|
|
case NVME_ZONE_STATE_CLOSED:
|
|
QTAILQ_INSERT_TAIL(&ns->closed_zones, zone, entry);
|
|
break;
|
|
case NVME_ZONE_STATE_FULL:
|
|
QTAILQ_INSERT_TAIL(&ns->full_zones, zone, entry);
|
|
case NVME_ZONE_STATE_READ_ONLY:
|
|
break;
|
|
default:
|
|
zone->d.za = 0;
|
|
}
|
|
}
|
|
|
|
static uint16_t nvme_zns_check_resources(NvmeNamespace *ns, uint32_t act,
|
|
uint32_t opn, uint32_t zrwa)
|
|
{
|
|
if (ns->params.max_active_zones != 0 &&
|
|
ns->nr_active_zones + act > ns->params.max_active_zones) {
|
|
trace_pci_nvme_err_insuff_active_res(ns->params.max_active_zones);
|
|
return NVME_ZONE_TOO_MANY_ACTIVE | NVME_DNR;
|
|
}
|
|
|
|
if (ns->params.max_open_zones != 0 &&
|
|
ns->nr_open_zones + opn > ns->params.max_open_zones) {
|
|
trace_pci_nvme_err_insuff_open_res(ns->params.max_open_zones);
|
|
return NVME_ZONE_TOO_MANY_OPEN | NVME_DNR;
|
|
}
|
|
|
|
if (zrwa > ns->zns.numzrwa) {
|
|
return NVME_NOZRWA | NVME_DNR;
|
|
}
|
|
|
|
return NVME_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* Check if we can open a zone without exceeding open/active limits.
|
|
* AOR stands for "Active and Open Resources" (see TP 4053 section 2.5).
|
|
*/
|
|
static uint16_t nvme_aor_check(NvmeNamespace *ns, uint32_t act, uint32_t opn)
|
|
{
|
|
return nvme_zns_check_resources(ns, act, opn, 0);
|
|
}
|
|
|
|
static NvmeFdpEvent *nvme_fdp_alloc_event(NvmeCtrl *n, NvmeFdpEventBuffer *ebuf)
|
|
{
|
|
NvmeFdpEvent *ret = NULL;
|
|
bool is_full = ebuf->next == ebuf->start && ebuf->nelems;
|
|
|
|
ret = &ebuf->events[ebuf->next++];
|
|
if (unlikely(ebuf->next == NVME_FDP_MAX_EVENTS)) {
|
|
ebuf->next = 0;
|
|
}
|
|
if (is_full) {
|
|
ebuf->start = ebuf->next;
|
|
} else {
|
|
ebuf->nelems++;
|
|
}
|
|
|
|
memset(ret, 0, sizeof(NvmeFdpEvent));
|
|
ret->timestamp = nvme_get_timestamp(n);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static inline int log_event(NvmeRuHandle *ruh, uint8_t event_type)
|
|
{
|
|
return (ruh->event_filter >> nvme_fdp_evf_shifts[event_type]) & 0x1;
|
|
}
|
|
|
|
static bool nvme_update_ruh(NvmeCtrl *n, NvmeNamespace *ns, uint16_t pid)
|
|
{
|
|
NvmeEnduranceGroup *endgrp = ns->endgrp;
|
|
NvmeRuHandle *ruh;
|
|
NvmeReclaimUnit *ru;
|
|
NvmeFdpEvent *e = NULL;
|
|
uint16_t ph, rg, ruhid;
|
|
|
|
if (!nvme_parse_pid(ns, pid, &ph, &rg)) {
|
|
return false;
|
|
}
|
|
|
|
ruhid = ns->fdp.phs[ph];
|
|
|
|
ruh = &endgrp->fdp.ruhs[ruhid];
|
|
ru = &ruh->rus[rg];
|
|
|
|
if (ru->ruamw) {
|
|
if (log_event(ruh, FDP_EVT_RU_NOT_FULLY_WRITTEN)) {
|
|
e = nvme_fdp_alloc_event(n, &endgrp->fdp.host_events);
|
|
e->type = FDP_EVT_RU_NOT_FULLY_WRITTEN;
|
|
e->flags = FDPEF_PIV | FDPEF_NSIDV | FDPEF_LV;
|
|
e->pid = cpu_to_le16(pid);
|
|
e->nsid = cpu_to_le32(ns->params.nsid);
|
|
e->rgid = cpu_to_le16(rg);
|
|
e->ruhid = cpu_to_le16(ruhid);
|
|
}
|
|
|
|
/* log (eventual) GC overhead of prematurely swapping the RU */
|
|
nvme_fdp_stat_inc(&endgrp->fdp.mbmw, nvme_l2b(ns, ru->ruamw));
|
|
}
|
|
|
|
ru->ruamw = ruh->ruamw;
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool nvme_addr_is_cmb(NvmeCtrl *n, hwaddr addr)
|
|
{
|
|
hwaddr hi, lo;
|
|
|
|
if (!n->cmb.cmse) {
|
|
return false;
|
|
}
|
|
|
|
lo = n->params.legacy_cmb ? n->cmb.mem.addr : n->cmb.cba;
|
|
hi = lo + int128_get64(n->cmb.mem.size);
|
|
|
|
return addr >= lo && addr < hi;
|
|
}
|
|
|
|
static inline void *nvme_addr_to_cmb(NvmeCtrl *n, hwaddr addr)
|
|
{
|
|
hwaddr base = n->params.legacy_cmb ? n->cmb.mem.addr : n->cmb.cba;
|
|
return &n->cmb.buf[addr - base];
|
|
}
|
|
|
|
static bool nvme_addr_is_pmr(NvmeCtrl *n, hwaddr addr)
|
|
{
|
|
hwaddr hi;
|
|
|
|
if (!n->pmr.cmse) {
|
|
return false;
|
|
}
|
|
|
|
hi = n->pmr.cba + int128_get64(n->pmr.dev->mr.size);
|
|
|
|
return addr >= n->pmr.cba && addr < hi;
|
|
}
|
|
|
|
static inline void *nvme_addr_to_pmr(NvmeCtrl *n, hwaddr addr)
|
|
{
|
|
return memory_region_get_ram_ptr(&n->pmr.dev->mr) + (addr - n->pmr.cba);
|
|
}
|
|
|
|
static inline bool nvme_addr_is_iomem(NvmeCtrl *n, hwaddr addr)
|
|
{
|
|
hwaddr hi, lo;
|
|
|
|
/*
|
|
* The purpose of this check is to guard against invalid "local" access to
|
|
* the iomem (i.e. controller registers). Thus, we check against the range
|
|
* covered by the 'bar0' MemoryRegion since that is currently composed of
|
|
* two subregions (the NVMe "MBAR" and the MSI-X table/pba). Note, however,
|
|
* that if the device model is ever changed to allow the CMB to be located
|
|
* in BAR0 as well, then this must be changed.
|
|
*/
|
|
lo = n->bar0.addr;
|
|
hi = lo + int128_get64(n->bar0.size);
|
|
|
|
return addr >= lo && addr < hi;
|
|
}
|
|
|
|
static int nvme_addr_read(NvmeCtrl *n, hwaddr addr, void *buf, int size)
|
|
{
|
|
hwaddr hi = addr + size - 1;
|
|
if (hi < addr) {
|
|
return 1;
|
|
}
|
|
|
|
if (n->bar.cmbsz && nvme_addr_is_cmb(n, addr) && nvme_addr_is_cmb(n, hi)) {
|
|
memcpy(buf, nvme_addr_to_cmb(n, addr), size);
|
|
return 0;
|
|
}
|
|
|
|
if (nvme_addr_is_pmr(n, addr) && nvme_addr_is_pmr(n, hi)) {
|
|
memcpy(buf, nvme_addr_to_pmr(n, addr), size);
|
|
return 0;
|
|
}
|
|
|
|
return pci_dma_read(PCI_DEVICE(n), addr, buf, size);
|
|
}
|
|
|
|
static int nvme_addr_write(NvmeCtrl *n, hwaddr addr, const void *buf, int size)
|
|
{
|
|
hwaddr hi = addr + size - 1;
|
|
if (hi < addr) {
|
|
return 1;
|
|
}
|
|
|
|
if (n->bar.cmbsz && nvme_addr_is_cmb(n, addr) && nvme_addr_is_cmb(n, hi)) {
|
|
memcpy(nvme_addr_to_cmb(n, addr), buf, size);
|
|
return 0;
|
|
}
|
|
|
|
if (nvme_addr_is_pmr(n, addr) && nvme_addr_is_pmr(n, hi)) {
|
|
memcpy(nvme_addr_to_pmr(n, addr), buf, size);
|
|
return 0;
|
|
}
|
|
|
|
return pci_dma_write(PCI_DEVICE(n), addr, buf, size);
|
|
}
|
|
|
|
static bool nvme_nsid_valid(NvmeCtrl *n, uint32_t nsid)
|
|
{
|
|
return nsid &&
|
|
(nsid == NVME_NSID_BROADCAST || nsid <= NVME_MAX_NAMESPACES);
|
|
}
|
|
|
|
static int nvme_check_sqid(NvmeCtrl *n, uint16_t sqid)
|
|
{
|
|
return sqid < n->conf_ioqpairs + 1 && n->sq[sqid] != NULL ? 0 : -1;
|
|
}
|
|
|
|
static int nvme_check_cqid(NvmeCtrl *n, uint16_t cqid)
|
|
{
|
|
return cqid < n->conf_ioqpairs + 1 && n->cq[cqid] != NULL ? 0 : -1;
|
|
}
|
|
|
|
static void nvme_inc_cq_tail(NvmeCQueue *cq)
|
|
{
|
|
cq->tail++;
|
|
if (cq->tail >= cq->size) {
|
|
cq->tail = 0;
|
|
cq->phase = !cq->phase;
|
|
}
|
|
}
|
|
|
|
static void nvme_inc_sq_head(NvmeSQueue *sq)
|
|
{
|
|
sq->head = (sq->head + 1) % sq->size;
|
|
}
|
|
|
|
static uint8_t nvme_cq_full(NvmeCQueue *cq)
|
|
{
|
|
return (cq->tail + 1) % cq->size == cq->head;
|
|
}
|
|
|
|
static uint8_t nvme_sq_empty(NvmeSQueue *sq)
|
|
{
|
|
return sq->head == sq->tail;
|
|
}
|
|
|
|
static void nvme_irq_check(NvmeCtrl *n)
|
|
{
|
|
PCIDevice *pci = PCI_DEVICE(n);
|
|
uint32_t intms = ldl_le_p(&n->bar.intms);
|
|
|
|
if (msix_enabled(pci)) {
|
|
return;
|
|
}
|
|
if (~intms & n->irq_status) {
|
|
pci_irq_assert(pci);
|
|
} else {
|
|
pci_irq_deassert(pci);
|
|
}
|
|
}
|
|
|
|
static void nvme_irq_assert(NvmeCtrl *n, NvmeCQueue *cq)
|
|
{
|
|
PCIDevice *pci = PCI_DEVICE(n);
|
|
|
|
if (cq->irq_enabled) {
|
|
if (msix_enabled(pci)) {
|
|
trace_pci_nvme_irq_msix(cq->vector);
|
|
msix_notify(pci, cq->vector);
|
|
} else {
|
|
trace_pci_nvme_irq_pin();
|
|
assert(cq->vector < 32);
|
|
n->irq_status |= 1 << cq->vector;
|
|
nvme_irq_check(n);
|
|
}
|
|
} else {
|
|
trace_pci_nvme_irq_masked();
|
|
}
|
|
}
|
|
|
|
static void nvme_irq_deassert(NvmeCtrl *n, NvmeCQueue *cq)
|
|
{
|
|
if (cq->irq_enabled) {
|
|
if (msix_enabled(PCI_DEVICE(n))) {
|
|
return;
|
|
} else {
|
|
assert(cq->vector < 32);
|
|
if (!n->cq_pending) {
|
|
n->irq_status &= ~(1 << cq->vector);
|
|
}
|
|
nvme_irq_check(n);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void nvme_req_clear(NvmeRequest *req)
|
|
{
|
|
req->ns = NULL;
|
|
req->opaque = NULL;
|
|
req->aiocb = NULL;
|
|
memset(&req->cqe, 0x0, sizeof(req->cqe));
|
|
req->status = NVME_SUCCESS;
|
|
}
|
|
|
|
static inline void nvme_sg_init(NvmeCtrl *n, NvmeSg *sg, bool dma)
|
|
{
|
|
if (dma) {
|
|
pci_dma_sglist_init(&sg->qsg, PCI_DEVICE(n), 0);
|
|
sg->flags = NVME_SG_DMA;
|
|
} else {
|
|
qemu_iovec_init(&sg->iov, 0);
|
|
}
|
|
|
|
sg->flags |= NVME_SG_ALLOC;
|
|
}
|
|
|
|
static inline void nvme_sg_unmap(NvmeSg *sg)
|
|
{
|
|
if (!(sg->flags & NVME_SG_ALLOC)) {
|
|
return;
|
|
}
|
|
|
|
if (sg->flags & NVME_SG_DMA) {
|
|
qemu_sglist_destroy(&sg->qsg);
|
|
} else {
|
|
qemu_iovec_destroy(&sg->iov);
|
|
}
|
|
|
|
memset(sg, 0x0, sizeof(*sg));
|
|
}
|
|
|
|
/*
|
|
* When metadata is transferred as extended LBAs, the DPTR mapped into `sg`
|
|
* holds both data and metadata. This function splits the data and metadata
|
|
* into two separate QSG/IOVs.
|
|
*/
|
|
static void nvme_sg_split(NvmeSg *sg, NvmeNamespace *ns, NvmeSg *data,
|
|
NvmeSg *mdata)
|
|
{
|
|
NvmeSg *dst = data;
|
|
uint32_t trans_len, count = ns->lbasz;
|
|
uint64_t offset = 0;
|
|
bool dma = sg->flags & NVME_SG_DMA;
|
|
size_t sge_len;
|
|
size_t sg_len = dma ? sg->qsg.size : sg->iov.size;
|
|
int sg_idx = 0;
|
|
|
|
assert(sg->flags & NVME_SG_ALLOC);
|
|
|
|
while (sg_len) {
|
|
sge_len = dma ? sg->qsg.sg[sg_idx].len : sg->iov.iov[sg_idx].iov_len;
|
|
|
|
trans_len = MIN(sg_len, count);
|
|
trans_len = MIN(trans_len, sge_len - offset);
|
|
|
|
if (dst) {
|
|
if (dma) {
|
|
qemu_sglist_add(&dst->qsg, sg->qsg.sg[sg_idx].base + offset,
|
|
trans_len);
|
|
} else {
|
|
qemu_iovec_add(&dst->iov,
|
|
sg->iov.iov[sg_idx].iov_base + offset,
|
|
trans_len);
|
|
}
|
|
}
|
|
|
|
sg_len -= trans_len;
|
|
count -= trans_len;
|
|
offset += trans_len;
|
|
|
|
if (count == 0) {
|
|
dst = (dst == data) ? mdata : data;
|
|
count = (dst == data) ? ns->lbasz : ns->lbaf.ms;
|
|
}
|
|
|
|
if (sge_len == offset) {
|
|
offset = 0;
|
|
sg_idx++;
|
|
}
|
|
}
|
|
}
|
|
|
|
static uint16_t nvme_map_addr_cmb(NvmeCtrl *n, QEMUIOVector *iov, hwaddr addr,
|
|
size_t len)
|
|
{
|
|
if (!len) {
|
|
return NVME_SUCCESS;
|
|
}
|
|
|
|
trace_pci_nvme_map_addr_cmb(addr, len);
|
|
|
|
if (!nvme_addr_is_cmb(n, addr) || !nvme_addr_is_cmb(n, addr + len - 1)) {
|
|
return NVME_DATA_TRAS_ERROR;
|
|
}
|
|
|
|
qemu_iovec_add(iov, nvme_addr_to_cmb(n, addr), len);
|
|
|
|
return NVME_SUCCESS;
|
|
}
|
|
|
|
static uint16_t nvme_map_addr_pmr(NvmeCtrl *n, QEMUIOVector *iov, hwaddr addr,
|
|
size_t len)
|
|
{
|
|
if (!len) {
|
|
return NVME_SUCCESS;
|
|
}
|
|
|
|
if (!nvme_addr_is_pmr(n, addr) || !nvme_addr_is_pmr(n, addr + len - 1)) {
|
|
return NVME_DATA_TRAS_ERROR;
|
|
}
|
|
|
|
qemu_iovec_add(iov, nvme_addr_to_pmr(n, addr), len);
|
|
|
|
return NVME_SUCCESS;
|
|
}
|
|
|
|
static uint16_t nvme_map_addr(NvmeCtrl *n, NvmeSg *sg, hwaddr addr, size_t len)
|
|
{
|
|
bool cmb = false, pmr = false;
|
|
|
|
if (!len) {
|
|
return NVME_SUCCESS;
|
|
}
|
|
|
|
trace_pci_nvme_map_addr(addr, len);
|
|
|
|
if (nvme_addr_is_iomem(n, addr)) {
|
|
return NVME_DATA_TRAS_ERROR;
|
|
}
|
|
|
|
if (nvme_addr_is_cmb(n, addr)) {
|
|
cmb = true;
|
|
} else if (nvme_addr_is_pmr(n, addr)) {
|
|
pmr = true;
|
|
}
|
|
|
|
if (cmb || pmr) {
|
|
if (sg->flags & NVME_SG_DMA) {
|
|
return NVME_INVALID_USE_OF_CMB | NVME_DNR;
|
|
}
|
|
|
|
if (sg->iov.niov + 1 > IOV_MAX) {
|
|
goto max_mappings_exceeded;
|
|
}
|
|
|
|
if (cmb) {
|
|
return nvme_map_addr_cmb(n, &sg->iov, addr, len);
|
|
} else {
|
|
return nvme_map_addr_pmr(n, &sg->iov, addr, len);
|
|
}
|
|
}
|
|
|
|
if (!(sg->flags & NVME_SG_DMA)) {
|
|
return NVME_INVALID_USE_OF_CMB | NVME_DNR;
|
|
}
|
|
|
|
if (sg->qsg.nsg + 1 > IOV_MAX) {
|
|
goto max_mappings_exceeded;
|
|
}
|
|
|
|
qemu_sglist_add(&sg->qsg, addr, len);
|
|
|
|
return NVME_SUCCESS;
|
|
|
|
max_mappings_exceeded:
|
|
NVME_GUEST_ERR(pci_nvme_ub_too_many_mappings,
|
|
"number of mappings exceed 1024");
|
|
return NVME_INTERNAL_DEV_ERROR | NVME_DNR;
|
|
}
|
|
|
|
static inline bool nvme_addr_is_dma(NvmeCtrl *n, hwaddr addr)
|
|
{
|
|
return !(nvme_addr_is_cmb(n, addr) || nvme_addr_is_pmr(n, addr));
|
|
}
|
|
|
|
static uint16_t nvme_map_prp(NvmeCtrl *n, NvmeSg *sg, uint64_t prp1,
|
|
uint64_t prp2, uint32_t len)
|
|
{
|
|
hwaddr trans_len = n->page_size - (prp1 % n->page_size);
|
|
trans_len = MIN(len, trans_len);
|
|
int num_prps = (len >> n->page_bits) + 1;
|
|
uint16_t status;
|
|
int ret;
|
|
|
|
trace_pci_nvme_map_prp(trans_len, len, prp1, prp2, num_prps);
|
|
|
|
nvme_sg_init(n, sg, nvme_addr_is_dma(n, prp1));
|
|
|
|
status = nvme_map_addr(n, sg, prp1, trans_len);
|
|
if (status) {
|
|
goto unmap;
|
|
}
|
|
|
|
len -= trans_len;
|
|
if (len) {
|
|
if (len > n->page_size) {
|
|
g_autofree uint64_t *prp_list = g_new(uint64_t, n->max_prp_ents);
|
|
uint32_t nents, prp_trans;
|
|
int i = 0;
|
|
|
|
/*
|
|
* The first PRP list entry, pointed to by PRP2 may contain offset.
|
|
* Hence, we need to calculate the number of entries in based on
|
|
* that offset.
|
|
*/
|
|
nents = (n->page_size - (prp2 & (n->page_size - 1))) >> 3;
|
|
prp_trans = MIN(n->max_prp_ents, nents) * sizeof(uint64_t);
|
|
ret = nvme_addr_read(n, prp2, (void *)prp_list, prp_trans);
|
|
if (ret) {
|
|
trace_pci_nvme_err_addr_read(prp2);
|
|
status = NVME_DATA_TRAS_ERROR;
|
|
goto unmap;
|
|
}
|
|
while (len != 0) {
|
|
uint64_t prp_ent = le64_to_cpu(prp_list[i]);
|
|
|
|
if (i == nents - 1 && len > n->page_size) {
|
|
if (unlikely(prp_ent & (n->page_size - 1))) {
|
|
trace_pci_nvme_err_invalid_prplist_ent(prp_ent);
|
|
status = NVME_INVALID_PRP_OFFSET | NVME_DNR;
|
|
goto unmap;
|
|
}
|
|
|
|
i = 0;
|
|
nents = (len + n->page_size - 1) >> n->page_bits;
|
|
nents = MIN(nents, n->max_prp_ents);
|
|
prp_trans = nents * sizeof(uint64_t);
|
|
ret = nvme_addr_read(n, prp_ent, (void *)prp_list,
|
|
prp_trans);
|
|
if (ret) {
|
|
trace_pci_nvme_err_addr_read(prp_ent);
|
|
status = NVME_DATA_TRAS_ERROR;
|
|
goto unmap;
|
|
}
|
|
prp_ent = le64_to_cpu(prp_list[i]);
|
|
}
|
|
|
|
if (unlikely(prp_ent & (n->page_size - 1))) {
|
|
trace_pci_nvme_err_invalid_prplist_ent(prp_ent);
|
|
status = NVME_INVALID_PRP_OFFSET | NVME_DNR;
|
|
goto unmap;
|
|
}
|
|
|
|
trans_len = MIN(len, n->page_size);
|
|
status = nvme_map_addr(n, sg, prp_ent, trans_len);
|
|
if (status) {
|
|
goto unmap;
|
|
}
|
|
|
|
len -= trans_len;
|
|
i++;
|
|
}
|
|
} else {
|
|
if (unlikely(prp2 & (n->page_size - 1))) {
|
|
trace_pci_nvme_err_invalid_prp2_align(prp2);
|
|
status = NVME_INVALID_PRP_OFFSET | NVME_DNR;
|
|
goto unmap;
|
|
}
|
|
status = nvme_map_addr(n, sg, prp2, len);
|
|
if (status) {
|
|
goto unmap;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NVME_SUCCESS;
|
|
|
|
unmap:
|
|
nvme_sg_unmap(sg);
|
|
return status;
|
|
}
|
|
|
|
/*
|
|
* Map 'nsgld' data descriptors from 'segment'. The function will subtract the
|
|
* number of bytes mapped in len.
|
|
*/
|
|
static uint16_t nvme_map_sgl_data(NvmeCtrl *n, NvmeSg *sg,
|
|
NvmeSglDescriptor *segment, uint64_t nsgld,
|
|
size_t *len, NvmeCmd *cmd)
|
|
{
|
|
dma_addr_t addr, trans_len;
|
|
uint32_t dlen;
|
|
uint16_t status;
|
|
|
|
for (int i = 0; i < nsgld; i++) {
|
|
uint8_t type = NVME_SGL_TYPE(segment[i].type);
|
|
|
|
switch (type) {
|
|
case NVME_SGL_DESCR_TYPE_DATA_BLOCK:
|
|
break;
|
|
case NVME_SGL_DESCR_TYPE_SEGMENT:
|
|
case NVME_SGL_DESCR_TYPE_LAST_SEGMENT:
|
|
return NVME_INVALID_NUM_SGL_DESCRS | NVME_DNR;
|
|
default:
|
|
return NVME_SGL_DESCR_TYPE_INVALID | NVME_DNR;
|
|
}
|
|
|
|
dlen = le32_to_cpu(segment[i].len);
|
|
|
|
if (!dlen) {
|
|
continue;
|
|
}
|
|
|
|
if (*len == 0) {
|
|
/*
|
|
* All data has been mapped, but the SGL contains additional
|
|
* segments and/or descriptors. The controller might accept
|
|
* ignoring the rest of the SGL.
|
|
*/
|
|
uint32_t sgls = le32_to_cpu(n->id_ctrl.sgls);
|
|
if (sgls & NVME_CTRL_SGLS_EXCESS_LENGTH) {
|
|
break;
|
|
}
|
|
|
|
trace_pci_nvme_err_invalid_sgl_excess_length(dlen);
|
|
return NVME_DATA_SGL_LEN_INVALID | NVME_DNR;
|
|
}
|
|
|
|
trans_len = MIN(*len, dlen);
|
|
|
|
addr = le64_to_cpu(segment[i].addr);
|
|
|
|
if (UINT64_MAX - addr < dlen) {
|
|
return NVME_DATA_SGL_LEN_INVALID | NVME_DNR;
|
|
}
|
|
|
|
status = nvme_map_addr(n, sg, addr, trans_len);
|
|
if (status) {
|
|
return status;
|
|
}
|
|
|
|
*len -= trans_len;
|
|
}
|
|
|
|
return NVME_SUCCESS;
|
|
}
|
|
|
|
static uint16_t nvme_map_sgl(NvmeCtrl *n, NvmeSg *sg, NvmeSglDescriptor sgl,
|
|
size_t len, NvmeCmd *cmd)
|
|
{
|
|
/*
|
|
* Read the segment in chunks of 256 descriptors (one 4k page) to avoid
|
|
* dynamically allocating a potentially huge SGL. The spec allows the SGL
|
|
* to be larger (as in number of bytes required to describe the SGL
|
|
* descriptors and segment chain) than the command transfer size, so it is
|
|
* not bounded by MDTS.
|
|
*/
|
|
#define SEG_CHUNK_SIZE 256
|
|
|
|
NvmeSglDescriptor segment[SEG_CHUNK_SIZE], *sgld, *last_sgld;
|
|
uint64_t nsgld;
|
|
uint32_t seg_len;
|
|
uint16_t status;
|
|
hwaddr addr;
|
|
int ret;
|
|
|
|
sgld = &sgl;
|
|
addr = le64_to_cpu(sgl.addr);
|
|
|
|
trace_pci_nvme_map_sgl(NVME_SGL_TYPE(sgl.type), len);
|
|
|
|
nvme_sg_init(n, sg, nvme_addr_is_dma(n, addr));
|
|
|
|
/*
|
|
* If the entire transfer can be described with a single data block it can
|
|
* be mapped directly.
|
|
*/
|
|
if (NVME_SGL_TYPE(sgl.type) == NVME_SGL_DESCR_TYPE_DATA_BLOCK) {
|
|
status = nvme_map_sgl_data(n, sg, sgld, 1, &len, cmd);
|
|
if (status) {
|
|
goto unmap;
|
|
}
|
|
|
|
goto out;
|
|
}
|
|
|
|
for (;;) {
|
|
switch (NVME_SGL_TYPE(sgld->type)) {
|
|
case NVME_SGL_DESCR_TYPE_SEGMENT:
|
|
case NVME_SGL_DESCR_TYPE_LAST_SEGMENT:
|
|
break;
|
|
default:
|
|
return NVME_INVALID_SGL_SEG_DESCR | NVME_DNR;
|
|
}
|
|
|
|
seg_len = le32_to_cpu(sgld->len);
|
|
|
|
/* check the length of the (Last) Segment descriptor */
|
|
if (!seg_len || seg_len & 0xf) {
|
|
return NVME_INVALID_SGL_SEG_DESCR | NVME_DNR;
|
|
}
|
|
|
|
if (UINT64_MAX - addr < seg_len) {
|
|
return NVME_DATA_SGL_LEN_INVALID | NVME_DNR;
|
|
}
|
|
|
|
nsgld = seg_len / sizeof(NvmeSglDescriptor);
|
|
|
|
while (nsgld > SEG_CHUNK_SIZE) {
|
|
if (nvme_addr_read(n, addr, segment, sizeof(segment))) {
|
|
trace_pci_nvme_err_addr_read(addr);
|
|
status = NVME_DATA_TRAS_ERROR;
|
|
goto unmap;
|
|
}
|
|
|
|
status = nvme_map_sgl_data(n, sg, segment, SEG_CHUNK_SIZE,
|
|
&len, cmd);
|
|
if (status) {
|
|
goto unmap;
|
|
}
|
|
|
|
nsgld -= SEG_CHUNK_SIZE;
|
|
addr += SEG_CHUNK_SIZE * sizeof(NvmeSglDescriptor);
|
|
}
|
|
|
|
ret = nvme_addr_read(n, addr, segment, nsgld *
|
|
sizeof(NvmeSglDescriptor));
|
|
if (ret) {
|
|
trace_pci_nvme_err_addr_read(addr);
|
|
status = NVME_DATA_TRAS_ERROR;
|
|
goto unmap;
|
|
}
|
|
|
|
last_sgld = &segment[nsgld - 1];
|
|
|
|
/*
|
|
* If the segment ends with a Data Block, then we are done.
|
|
*/
|
|
if (NVME_SGL_TYPE(last_sgld->type) == NVME_SGL_DESCR_TYPE_DATA_BLOCK) {
|
|
status = nvme_map_sgl_data(n, sg, segment, nsgld, &len, cmd);
|
|
if (status) {
|
|
goto unmap;
|
|
}
|
|
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* If the last descriptor was not a Data Block, then the current
|
|
* segment must not be a Last Segment.
|
|
*/
|
|
if (NVME_SGL_TYPE(sgld->type) == NVME_SGL_DESCR_TYPE_LAST_SEGMENT) {
|
|
status = NVME_INVALID_SGL_SEG_DESCR | NVME_DNR;
|
|
goto unmap;
|
|
}
|
|
|
|
sgld = last_sgld;
|
|
addr = le64_to_cpu(sgld->addr);
|
|
|
|
/*
|
|
* Do not map the last descriptor; it will be a Segment or Last Segment
|
|
* descriptor and is handled by the next iteration.
|
|
*/
|
|
status = nvme_map_sgl_data(n, sg, segment, nsgld - 1, &len, cmd);
|
|
if (status) {
|
|
goto unmap;
|
|
}
|
|
}
|
|
|
|
out:
|
|
/* if there is any residual left in len, the SGL was too short */
|
|
if (len) {
|
|
status = NVME_DATA_SGL_LEN_INVALID | NVME_DNR;
|
|
goto unmap;
|
|
}
|
|
|
|
return NVME_SUCCESS;
|
|
|
|
unmap:
|
|
nvme_sg_unmap(sg);
|
|
return status;
|
|
}
|
|
|
|
uint16_t nvme_map_dptr(NvmeCtrl *n, NvmeSg *sg, size_t len,
|
|
NvmeCmd *cmd)
|
|
{
|
|
uint64_t prp1, prp2;
|
|
|
|
switch (NVME_CMD_FLAGS_PSDT(cmd->flags)) {
|
|
case NVME_PSDT_PRP:
|
|
prp1 = le64_to_cpu(cmd->dptr.prp1);
|
|
prp2 = le64_to_cpu(cmd->dptr.prp2);
|
|
|
|
return nvme_map_prp(n, sg, prp1, prp2, len);
|
|
case NVME_PSDT_SGL_MPTR_CONTIGUOUS:
|
|
case NVME_PSDT_SGL_MPTR_SGL:
|
|
return nvme_map_sgl(n, sg, cmd->dptr.sgl, len, cmd);
|
|
default:
|
|
return NVME_INVALID_FIELD;
|
|
}
|
|
}
|
|
|
|
static uint16_t nvme_map_mptr(NvmeCtrl *n, NvmeSg *sg, size_t len,
|
|
NvmeCmd *cmd)
|
|
{
|
|
int psdt = NVME_CMD_FLAGS_PSDT(cmd->flags);
|
|
hwaddr mptr = le64_to_cpu(cmd->mptr);
|
|
uint16_t status;
|
|
|
|
if (psdt == NVME_PSDT_SGL_MPTR_SGL) {
|
|
NvmeSglDescriptor sgl;
|
|
|
|
if (nvme_addr_read(n, mptr, &sgl, sizeof(sgl))) {
|
|
return NVME_DATA_TRAS_ERROR;
|
|
}
|
|
|
|
status = nvme_map_sgl(n, sg, sgl, len, cmd);
|
|
if (status && (status & 0x7ff) == NVME_DATA_SGL_LEN_INVALID) {
|
|
status = NVME_MD_SGL_LEN_INVALID | NVME_DNR;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
nvme_sg_init(n, sg, nvme_addr_is_dma(n, mptr));
|
|
status = nvme_map_addr(n, sg, mptr, len);
|
|
if (status) {
|
|
nvme_sg_unmap(sg);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
static uint16_t nvme_map_data(NvmeCtrl *n, uint32_t nlb, NvmeRequest *req)
|
|
{
|
|
NvmeNamespace *ns = req->ns;
|
|
NvmeRwCmd *rw = (NvmeRwCmd *)&req->cmd;
|
|
bool pi = !!NVME_ID_NS_DPS_TYPE(ns->id_ns.dps);
|
|
bool pract = !!(le16_to_cpu(rw->control) & NVME_RW_PRINFO_PRACT);
|
|
size_t len = nvme_l2b(ns, nlb);
|
|
uint16_t status;
|
|
|
|
if (nvme_ns_ext(ns) &&
|
|
!(pi && pract && ns->lbaf.ms == nvme_pi_tuple_size(ns))) {
|
|
NvmeSg sg;
|
|
|
|
len += nvme_m2b(ns, nlb);
|
|
|
|
status = nvme_map_dptr(n, &sg, len, &req->cmd);
|
|
if (status) {
|
|
return status;
|
|
}
|
|
|
|
nvme_sg_init(n, &req->sg, sg.flags & NVME_SG_DMA);
|
|
nvme_sg_split(&sg, ns, &req->sg, NULL);
|
|
nvme_sg_unmap(&sg);
|
|
|
|
return NVME_SUCCESS;
|
|
}
|
|
|
|
return nvme_map_dptr(n, &req->sg, len, &req->cmd);
|
|
}
|
|
|
|
static uint16_t nvme_map_mdata(NvmeCtrl *n, uint32_t nlb, NvmeRequest *req)
|
|
{
|
|
NvmeNamespace *ns = req->ns;
|
|
size_t len = nvme_m2b(ns, nlb);
|
|
uint16_t status;
|
|
|
|
if (nvme_ns_ext(ns)) {
|
|
NvmeSg sg;
|
|
|
|
len += nvme_l2b(ns, nlb);
|
|
|
|
status = nvme_map_dptr(n, &sg, len, &req->cmd);
|
|
if (status) {
|
|
return status;
|
|
}
|
|
|
|
nvme_sg_init(n, &req->sg, sg.flags & NVME_SG_DMA);
|
|
nvme_sg_split(&sg, ns, NULL, &req->sg);
|
|
nvme_sg_unmap(&sg);
|
|
|
|
return NVME_SUCCESS;
|
|
}
|
|
|
|
return nvme_map_mptr(n, &req->sg, len, &req->cmd);
|
|
}
|
|
|
|
static uint16_t nvme_tx_interleaved(NvmeCtrl *n, NvmeSg *sg, uint8_t *ptr,
|
|
uint32_t len, uint32_t bytes,
|
|
int32_t skip_bytes, int64_t offset,
|
|
NvmeTxDirection dir)
|
|
{
|
|
hwaddr addr;
|
|
uint32_t trans_len, count = bytes;
|
|
bool dma = sg->flags & NVME_SG_DMA;
|
|
int64_t sge_len;
|
|
int sg_idx = 0;
|
|
int ret;
|
|
|
|
assert(sg->flags & NVME_SG_ALLOC);
|
|
|
|
while (len) {
|
|
sge_len = dma ? sg->qsg.sg[sg_idx].len : sg->iov.iov[sg_idx].iov_len;
|
|
|
|
if (sge_len - offset < 0) {
|
|
offset -= sge_len;
|
|
sg_idx++;
|
|
continue;
|
|
}
|
|
|
|
if (sge_len == offset) {
|
|
offset = 0;
|
|
sg_idx++;
|
|
continue;
|
|
}
|
|
|
|
trans_len = MIN(len, count);
|
|
trans_len = MIN(trans_len, sge_len - offset);
|
|
|
|
if (dma) {
|
|
addr = sg->qsg.sg[sg_idx].base + offset;
|
|
} else {
|
|
addr = (hwaddr)(uintptr_t)sg->iov.iov[sg_idx].iov_base + offset;
|
|
}
|
|
|
|
if (dir == NVME_TX_DIRECTION_TO_DEVICE) {
|
|
ret = nvme_addr_read(n, addr, ptr, trans_len);
|
|
} else {
|
|
ret = nvme_addr_write(n, addr, ptr, trans_len);
|
|
}
|
|
|
|
if (ret) {
|
|
return NVME_DATA_TRAS_ERROR;
|
|
}
|
|
|
|
ptr += trans_len;
|
|
len -= trans_len;
|
|
count -= trans_len;
|
|
offset += trans_len;
|
|
|
|
if (count == 0) {
|
|
count = bytes;
|
|
offset += skip_bytes;
|
|
}
|
|
}
|
|
|
|
return NVME_SUCCESS;
|
|
}
|
|
|
|
static uint16_t nvme_tx(NvmeCtrl *n, NvmeSg *sg, void *ptr, uint32_t len,
|
|
NvmeTxDirection dir)
|
|
{
|
|
assert(sg->flags & NVME_SG_ALLOC);
|
|
|
|
if (sg->flags & NVME_SG_DMA) {
|
|
const MemTxAttrs attrs = MEMTXATTRS_UNSPECIFIED;
|
|
dma_addr_t residual;
|
|
|
|
if (dir == NVME_TX_DIRECTION_TO_DEVICE) {
|
|
dma_buf_write(ptr, len, &residual, &sg->qsg, attrs);
|
|
} else {
|
|
dma_buf_read(ptr, len, &residual, &sg->qsg, attrs);
|
|
}
|
|
|
|
if (unlikely(residual)) {
|
|
trace_pci_nvme_err_invalid_dma();
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
} else {
|
|
size_t bytes;
|
|
|
|
if (dir == NVME_TX_DIRECTION_TO_DEVICE) {
|
|
bytes = qemu_iovec_to_buf(&sg->iov, 0, ptr, len);
|
|
} else {
|
|
bytes = qemu_iovec_from_buf(&sg->iov, 0, ptr, len);
|
|
}
|
|
|
|
if (unlikely(bytes != len)) {
|
|
trace_pci_nvme_err_invalid_dma();
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
}
|
|
|
|
return NVME_SUCCESS;
|
|
}
|
|
|
|
static inline uint16_t nvme_c2h(NvmeCtrl *n, void *ptr, uint32_t len,
|
|
NvmeRequest *req)
|
|
{
|
|
uint16_t status;
|
|
|
|
status = nvme_map_dptr(n, &req->sg, len, &req->cmd);
|
|
if (status) {
|
|
return status;
|
|
}
|
|
|
|
return nvme_tx(n, &req->sg, ptr, len, NVME_TX_DIRECTION_FROM_DEVICE);
|
|
}
|
|
|
|
static inline uint16_t nvme_h2c(NvmeCtrl *n, void *ptr, uint32_t len,
|
|
NvmeRequest *req)
|
|
{
|
|
uint16_t status;
|
|
|
|
status = nvme_map_dptr(n, &req->sg, len, &req->cmd);
|
|
if (status) {
|
|
return status;
|
|
}
|
|
|
|
return nvme_tx(n, &req->sg, ptr, len, NVME_TX_DIRECTION_TO_DEVICE);
|
|
}
|
|
|
|
uint16_t nvme_bounce_data(NvmeCtrl *n, void *ptr, uint32_t len,
|
|
NvmeTxDirection dir, NvmeRequest *req)
|
|
{
|
|
NvmeNamespace *ns = req->ns;
|
|
NvmeRwCmd *rw = (NvmeRwCmd *)&req->cmd;
|
|
bool pi = !!NVME_ID_NS_DPS_TYPE(ns->id_ns.dps);
|
|
bool pract = !!(le16_to_cpu(rw->control) & NVME_RW_PRINFO_PRACT);
|
|
|
|
if (nvme_ns_ext(ns) &&
|
|
!(pi && pract && ns->lbaf.ms == nvme_pi_tuple_size(ns))) {
|
|
return nvme_tx_interleaved(n, &req->sg, ptr, len, ns->lbasz,
|
|
ns->lbaf.ms, 0, dir);
|
|
}
|
|
|
|
return nvme_tx(n, &req->sg, ptr, len, dir);
|
|
}
|
|
|
|
uint16_t nvme_bounce_mdata(NvmeCtrl *n, void *ptr, uint32_t len,
|
|
NvmeTxDirection dir, NvmeRequest *req)
|
|
{
|
|
NvmeNamespace *ns = req->ns;
|
|
uint16_t status;
|
|
|
|
if (nvme_ns_ext(ns)) {
|
|
return nvme_tx_interleaved(n, &req->sg, ptr, len, ns->lbaf.ms,
|
|
ns->lbasz, ns->lbasz, dir);
|
|
}
|
|
|
|
nvme_sg_unmap(&req->sg);
|
|
|
|
status = nvme_map_mptr(n, &req->sg, len, &req->cmd);
|
|
if (status) {
|
|
return status;
|
|
}
|
|
|
|
return nvme_tx(n, &req->sg, ptr, len, dir);
|
|
}
|
|
|
|
static inline void nvme_blk_read(BlockBackend *blk, int64_t offset,
|
|
uint32_t align, BlockCompletionFunc *cb,
|
|
NvmeRequest *req)
|
|
{
|
|
assert(req->sg.flags & NVME_SG_ALLOC);
|
|
|
|
if (req->sg.flags & NVME_SG_DMA) {
|
|
req->aiocb = dma_blk_read(blk, &req->sg.qsg, offset, align, cb, req);
|
|
} else {
|
|
req->aiocb = blk_aio_preadv(blk, offset, &req->sg.iov, 0, cb, req);
|
|
}
|
|
}
|
|
|
|
static inline void nvme_blk_write(BlockBackend *blk, int64_t offset,
|
|
uint32_t align, BlockCompletionFunc *cb,
|
|
NvmeRequest *req)
|
|
{
|
|
assert(req->sg.flags & NVME_SG_ALLOC);
|
|
|
|
if (req->sg.flags & NVME_SG_DMA) {
|
|
req->aiocb = dma_blk_write(blk, &req->sg.qsg, offset, align, cb, req);
|
|
} else {
|
|
req->aiocb = blk_aio_pwritev(blk, offset, &req->sg.iov, 0, cb, req);
|
|
}
|
|
}
|
|
|
|
static void nvme_update_cq_eventidx(const NvmeCQueue *cq)
|
|
{
|
|
trace_pci_nvme_update_cq_eventidx(cq->cqid, cq->head);
|
|
|
|
stl_le_pci_dma(PCI_DEVICE(cq->ctrl), cq->ei_addr, cq->head,
|
|
MEMTXATTRS_UNSPECIFIED);
|
|
}
|
|
|
|
static void nvme_update_cq_head(NvmeCQueue *cq)
|
|
{
|
|
ldl_le_pci_dma(PCI_DEVICE(cq->ctrl), cq->db_addr, &cq->head,
|
|
MEMTXATTRS_UNSPECIFIED);
|
|
|
|
trace_pci_nvme_update_cq_head(cq->cqid, cq->head);
|
|
}
|
|
|
|
static void nvme_post_cqes(void *opaque)
|
|
{
|
|
NvmeCQueue *cq = opaque;
|
|
NvmeCtrl *n = cq->ctrl;
|
|
NvmeRequest *req, *next;
|
|
bool pending = cq->head != cq->tail;
|
|
int ret;
|
|
|
|
QTAILQ_FOREACH_SAFE(req, &cq->req_list, entry, next) {
|
|
NvmeSQueue *sq;
|
|
hwaddr addr;
|
|
|
|
if (n->dbbuf_enabled) {
|
|
nvme_update_cq_eventidx(cq);
|
|
nvme_update_cq_head(cq);
|
|
}
|
|
|
|
if (nvme_cq_full(cq)) {
|
|
break;
|
|
}
|
|
|
|
sq = req->sq;
|
|
req->cqe.status = cpu_to_le16((req->status << 1) | cq->phase);
|
|
req->cqe.sq_id = cpu_to_le16(sq->sqid);
|
|
req->cqe.sq_head = cpu_to_le16(sq->head);
|
|
addr = cq->dma_addr + (cq->tail << NVME_CQES);
|
|
ret = pci_dma_write(PCI_DEVICE(n), addr, (void *)&req->cqe,
|
|
sizeof(req->cqe));
|
|
if (ret) {
|
|
trace_pci_nvme_err_addr_write(addr);
|
|
trace_pci_nvme_err_cfs();
|
|
stl_le_p(&n->bar.csts, NVME_CSTS_FAILED);
|
|
break;
|
|
}
|
|
QTAILQ_REMOVE(&cq->req_list, req, entry);
|
|
nvme_inc_cq_tail(cq);
|
|
nvme_sg_unmap(&req->sg);
|
|
QTAILQ_INSERT_TAIL(&sq->req_list, req, entry);
|
|
}
|
|
if (cq->tail != cq->head) {
|
|
if (cq->irq_enabled && !pending) {
|
|
n->cq_pending++;
|
|
}
|
|
|
|
nvme_irq_assert(n, cq);
|
|
}
|
|
}
|
|
|
|
static void nvme_enqueue_req_completion(NvmeCQueue *cq, NvmeRequest *req)
|
|
{
|
|
assert(cq->cqid == req->sq->cqid);
|
|
trace_pci_nvme_enqueue_req_completion(nvme_cid(req), cq->cqid,
|
|
le32_to_cpu(req->cqe.result),
|
|
le32_to_cpu(req->cqe.dw1),
|
|
req->status);
|
|
|
|
if (req->status) {
|
|
trace_pci_nvme_err_req_status(nvme_cid(req), nvme_nsid(req->ns),
|
|
req->status, req->cmd.opcode);
|
|
}
|
|
|
|
QTAILQ_REMOVE(&req->sq->out_req_list, req, entry);
|
|
QTAILQ_INSERT_TAIL(&cq->req_list, req, entry);
|
|
|
|
qemu_bh_schedule(cq->bh);
|
|
}
|
|
|
|
static void nvme_process_aers(void *opaque)
|
|
{
|
|
NvmeCtrl *n = opaque;
|
|
NvmeAsyncEvent *event, *next;
|
|
|
|
trace_pci_nvme_process_aers(n->aer_queued);
|
|
|
|
QTAILQ_FOREACH_SAFE(event, &n->aer_queue, entry, next) {
|
|
NvmeRequest *req;
|
|
NvmeAerResult *result;
|
|
|
|
/* can't post cqe if there is nothing to complete */
|
|
if (!n->outstanding_aers) {
|
|
trace_pci_nvme_no_outstanding_aers();
|
|
break;
|
|
}
|
|
|
|
/* ignore if masked (cqe posted, but event not cleared) */
|
|
if (n->aer_mask & (1 << event->result.event_type)) {
|
|
trace_pci_nvme_aer_masked(event->result.event_type, n->aer_mask);
|
|
continue;
|
|
}
|
|
|
|
QTAILQ_REMOVE(&n->aer_queue, event, entry);
|
|
n->aer_queued--;
|
|
|
|
n->aer_mask |= 1 << event->result.event_type;
|
|
n->outstanding_aers--;
|
|
|
|
req = n->aer_reqs[n->outstanding_aers];
|
|
|
|
result = (NvmeAerResult *) &req->cqe.result;
|
|
result->event_type = event->result.event_type;
|
|
result->event_info = event->result.event_info;
|
|
result->log_page = event->result.log_page;
|
|
g_free(event);
|
|
|
|
trace_pci_nvme_aer_post_cqe(result->event_type, result->event_info,
|
|
result->log_page);
|
|
|
|
nvme_enqueue_req_completion(&n->admin_cq, req);
|
|
}
|
|
}
|
|
|
|
static void nvme_enqueue_event(NvmeCtrl *n, uint8_t event_type,
|
|
uint8_t event_info, uint8_t log_page)
|
|
{
|
|
NvmeAsyncEvent *event;
|
|
|
|
trace_pci_nvme_enqueue_event(event_type, event_info, log_page);
|
|
|
|
if (n->aer_queued == n->params.aer_max_queued) {
|
|
trace_pci_nvme_enqueue_event_noqueue(n->aer_queued);
|
|
return;
|
|
}
|
|
|
|
event = g_new(NvmeAsyncEvent, 1);
|
|
event->result = (NvmeAerResult) {
|
|
.event_type = event_type,
|
|
.event_info = event_info,
|
|
.log_page = log_page,
|
|
};
|
|
|
|
QTAILQ_INSERT_TAIL(&n->aer_queue, event, entry);
|
|
n->aer_queued++;
|
|
|
|
nvme_process_aers(n);
|
|
}
|
|
|
|
static void nvme_smart_event(NvmeCtrl *n, uint8_t event)
|
|
{
|
|
uint8_t aer_info;
|
|
|
|
/* Ref SPEC <Asynchronous Event Information 0x2013 SMART / Health Status> */
|
|
if (!(NVME_AEC_SMART(n->features.async_config) & event)) {
|
|
return;
|
|
}
|
|
|
|
switch (event) {
|
|
case NVME_SMART_SPARE:
|
|
aer_info = NVME_AER_INFO_SMART_SPARE_THRESH;
|
|
break;
|
|
case NVME_SMART_TEMPERATURE:
|
|
aer_info = NVME_AER_INFO_SMART_TEMP_THRESH;
|
|
break;
|
|
case NVME_SMART_RELIABILITY:
|
|
case NVME_SMART_MEDIA_READ_ONLY:
|
|
case NVME_SMART_FAILED_VOLATILE_MEDIA:
|
|
case NVME_SMART_PMR_UNRELIABLE:
|
|
aer_info = NVME_AER_INFO_SMART_RELIABILITY;
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
nvme_enqueue_event(n, NVME_AER_TYPE_SMART, aer_info, NVME_LOG_SMART_INFO);
|
|
}
|
|
|
|
static void nvme_clear_events(NvmeCtrl *n, uint8_t event_type)
|
|
{
|
|
NvmeAsyncEvent *event, *next;
|
|
|
|
n->aer_mask &= ~(1 << event_type);
|
|
|
|
QTAILQ_FOREACH_SAFE(event, &n->aer_queue, entry, next) {
|
|
if (event->result.event_type == event_type) {
|
|
QTAILQ_REMOVE(&n->aer_queue, event, entry);
|
|
n->aer_queued--;
|
|
g_free(event);
|
|
}
|
|
}
|
|
}
|
|
|
|
static inline uint16_t nvme_check_mdts(NvmeCtrl *n, size_t len)
|
|
{
|
|
uint8_t mdts = n->params.mdts;
|
|
|
|
if (mdts && len > n->page_size << mdts) {
|
|
trace_pci_nvme_err_mdts(len);
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
|
|
return NVME_SUCCESS;
|
|
}
|
|
|
|
static inline uint16_t nvme_check_bounds(NvmeNamespace *ns, uint64_t slba,
|
|
uint32_t nlb)
|
|
{
|
|
uint64_t nsze = le64_to_cpu(ns->id_ns.nsze);
|
|
|
|
if (unlikely(UINT64_MAX - slba < nlb || slba + nlb > nsze)) {
|
|
trace_pci_nvme_err_invalid_lba_range(slba, nlb, nsze);
|
|
return NVME_LBA_RANGE | NVME_DNR;
|
|
}
|
|
|
|
return NVME_SUCCESS;
|
|
}
|
|
|
|
static int nvme_block_status_all(NvmeNamespace *ns, uint64_t slba,
|
|
uint32_t nlb, int flags)
|
|
{
|
|
BlockDriverState *bs = blk_bs(ns->blkconf.blk);
|
|
|
|
int64_t pnum = 0, bytes = nvme_l2b(ns, nlb);
|
|
int64_t offset = nvme_l2b(ns, slba);
|
|
int ret;
|
|
|
|
/*
|
|
* `pnum` holds the number of bytes after offset that shares the same
|
|
* allocation status as the byte at offset. If `pnum` is different from
|
|
* `bytes`, we should check the allocation status of the next range and
|
|
* continue this until all bytes have been checked.
|
|
*/
|
|
do {
|
|
bytes -= pnum;
|
|
|
|
ret = bdrv_block_status(bs, offset, bytes, &pnum, NULL, NULL);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
|
|
trace_pci_nvme_block_status(offset, bytes, pnum, ret,
|
|
!!(ret & BDRV_BLOCK_ZERO));
|
|
|
|
if (!(ret & flags)) {
|
|
return 1;
|
|
}
|
|
|
|
offset += pnum;
|
|
} while (pnum != bytes);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static uint16_t nvme_check_dulbe(NvmeNamespace *ns, uint64_t slba,
|
|
uint32_t nlb)
|
|
{
|
|
int ret;
|
|
Error *err = NULL;
|
|
|
|
ret = nvme_block_status_all(ns, slba, nlb, BDRV_BLOCK_DATA);
|
|
if (ret) {
|
|
if (ret < 0) {
|
|
error_setg_errno(&err, -ret, "unable to get block status");
|
|
error_report_err(err);
|
|
|
|
return NVME_INTERNAL_DEV_ERROR;
|
|
}
|
|
|
|
return NVME_DULB;
|
|
}
|
|
|
|
return NVME_SUCCESS;
|
|
}
|
|
|
|
static void nvme_aio_err(NvmeRequest *req, int ret)
|
|
{
|
|
uint16_t status = NVME_SUCCESS;
|
|
Error *local_err = NULL;
|
|
|
|
switch (req->cmd.opcode) {
|
|
case NVME_CMD_READ:
|
|
status = NVME_UNRECOVERED_READ;
|
|
break;
|
|
case NVME_CMD_FLUSH:
|
|
case NVME_CMD_WRITE:
|
|
case NVME_CMD_WRITE_ZEROES:
|
|
case NVME_CMD_ZONE_APPEND:
|
|
case NVME_CMD_COPY:
|
|
status = NVME_WRITE_FAULT;
|
|
break;
|
|
default:
|
|
status = NVME_INTERNAL_DEV_ERROR;
|
|
break;
|
|
}
|
|
|
|
if (ret == -ECANCELED) {
|
|
status = NVME_CMD_ABORT_REQ;
|
|
}
|
|
|
|
trace_pci_nvme_err_aio(nvme_cid(req), strerror(-ret), status);
|
|
|
|
error_setg_errno(&local_err, -ret, "aio failed");
|
|
error_report_err(local_err);
|
|
|
|
/*
|
|
* Set the command status code to the first encountered error but allow a
|
|
* subsequent Internal Device Error to trump it.
|
|
*/
|
|
if (req->status && status != NVME_INTERNAL_DEV_ERROR) {
|
|
return;
|
|
}
|
|
|
|
req->status = status;
|
|
}
|
|
|
|
static inline uint32_t nvme_zone_idx(NvmeNamespace *ns, uint64_t slba)
|
|
{
|
|
return ns->zone_size_log2 > 0 ? slba >> ns->zone_size_log2 :
|
|
slba / ns->zone_size;
|
|
}
|
|
|
|
static inline NvmeZone *nvme_get_zone_by_slba(NvmeNamespace *ns, uint64_t slba)
|
|
{
|
|
uint32_t zone_idx = nvme_zone_idx(ns, slba);
|
|
|
|
if (zone_idx >= ns->num_zones) {
|
|
return NULL;
|
|
}
|
|
|
|
return &ns->zone_array[zone_idx];
|
|
}
|
|
|
|
static uint16_t nvme_check_zone_state_for_write(NvmeZone *zone)
|
|
{
|
|
uint64_t zslba = zone->d.zslba;
|
|
|
|
switch (nvme_get_zone_state(zone)) {
|
|
case NVME_ZONE_STATE_EMPTY:
|
|
case NVME_ZONE_STATE_IMPLICITLY_OPEN:
|
|
case NVME_ZONE_STATE_EXPLICITLY_OPEN:
|
|
case NVME_ZONE_STATE_CLOSED:
|
|
return NVME_SUCCESS;
|
|
case NVME_ZONE_STATE_FULL:
|
|
trace_pci_nvme_err_zone_is_full(zslba);
|
|
return NVME_ZONE_FULL;
|
|
case NVME_ZONE_STATE_OFFLINE:
|
|
trace_pci_nvme_err_zone_is_offline(zslba);
|
|
return NVME_ZONE_OFFLINE;
|
|
case NVME_ZONE_STATE_READ_ONLY:
|
|
trace_pci_nvme_err_zone_is_read_only(zslba);
|
|
return NVME_ZONE_READ_ONLY;
|
|
default:
|
|
g_assert_not_reached();
|
|
}
|
|
|
|
return NVME_INTERNAL_DEV_ERROR;
|
|
}
|
|
|
|
static uint16_t nvme_check_zone_write(NvmeNamespace *ns, NvmeZone *zone,
|
|
uint64_t slba, uint32_t nlb)
|
|
{
|
|
uint64_t zcap = nvme_zone_wr_boundary(zone);
|
|
uint16_t status;
|
|
|
|
status = nvme_check_zone_state_for_write(zone);
|
|
if (status) {
|
|
return status;
|
|
}
|
|
|
|
if (zone->d.za & NVME_ZA_ZRWA_VALID) {
|
|
uint64_t ezrwa = zone->w_ptr + 2 * ns->zns.zrwas;
|
|
|
|
if (slba < zone->w_ptr || slba + nlb > ezrwa) {
|
|
trace_pci_nvme_err_zone_invalid_write(slba, zone->w_ptr);
|
|
return NVME_ZONE_INVALID_WRITE;
|
|
}
|
|
} else {
|
|
if (unlikely(slba != zone->w_ptr)) {
|
|
trace_pci_nvme_err_write_not_at_wp(slba, zone->d.zslba,
|
|
zone->w_ptr);
|
|
return NVME_ZONE_INVALID_WRITE;
|
|
}
|
|
}
|
|
|
|
if (unlikely((slba + nlb) > zcap)) {
|
|
trace_pci_nvme_err_zone_boundary(slba, nlb, zcap);
|
|
return NVME_ZONE_BOUNDARY_ERROR;
|
|
}
|
|
|
|
return NVME_SUCCESS;
|
|
}
|
|
|
|
static uint16_t nvme_check_zone_state_for_read(NvmeZone *zone)
|
|
{
|
|
switch (nvme_get_zone_state(zone)) {
|
|
case NVME_ZONE_STATE_EMPTY:
|
|
case NVME_ZONE_STATE_IMPLICITLY_OPEN:
|
|
case NVME_ZONE_STATE_EXPLICITLY_OPEN:
|
|
case NVME_ZONE_STATE_FULL:
|
|
case NVME_ZONE_STATE_CLOSED:
|
|
case NVME_ZONE_STATE_READ_ONLY:
|
|
return NVME_SUCCESS;
|
|
case NVME_ZONE_STATE_OFFLINE:
|
|
trace_pci_nvme_err_zone_is_offline(zone->d.zslba);
|
|
return NVME_ZONE_OFFLINE;
|
|
default:
|
|
g_assert_not_reached();
|
|
}
|
|
|
|
return NVME_INTERNAL_DEV_ERROR;
|
|
}
|
|
|
|
static uint16_t nvme_check_zone_read(NvmeNamespace *ns, uint64_t slba,
|
|
uint32_t nlb)
|
|
{
|
|
NvmeZone *zone;
|
|
uint64_t bndry, end;
|
|
uint16_t status;
|
|
|
|
zone = nvme_get_zone_by_slba(ns, slba);
|
|
assert(zone);
|
|
|
|
bndry = nvme_zone_rd_boundary(ns, zone);
|
|
end = slba + nlb;
|
|
|
|
status = nvme_check_zone_state_for_read(zone);
|
|
if (status) {
|
|
;
|
|
} else if (unlikely(end > bndry)) {
|
|
if (!ns->params.cross_zone_read) {
|
|
status = NVME_ZONE_BOUNDARY_ERROR;
|
|
} else {
|
|
/*
|
|
* Read across zone boundary - check that all subsequent
|
|
* zones that are being read have an appropriate state.
|
|
*/
|
|
do {
|
|
zone++;
|
|
status = nvme_check_zone_state_for_read(zone);
|
|
if (status) {
|
|
break;
|
|
}
|
|
} while (end > nvme_zone_rd_boundary(ns, zone));
|
|
}
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
static uint16_t nvme_zrm_finish(NvmeNamespace *ns, NvmeZone *zone)
|
|
{
|
|
switch (nvme_get_zone_state(zone)) {
|
|
case NVME_ZONE_STATE_FULL:
|
|
return NVME_SUCCESS;
|
|
|
|
case NVME_ZONE_STATE_IMPLICITLY_OPEN:
|
|
case NVME_ZONE_STATE_EXPLICITLY_OPEN:
|
|
nvme_aor_dec_open(ns);
|
|
/* fallthrough */
|
|
case NVME_ZONE_STATE_CLOSED:
|
|
nvme_aor_dec_active(ns);
|
|
|
|
if (zone->d.za & NVME_ZA_ZRWA_VALID) {
|
|
zone->d.za &= ~NVME_ZA_ZRWA_VALID;
|
|
if (ns->params.numzrwa) {
|
|
ns->zns.numzrwa++;
|
|
}
|
|
}
|
|
|
|
/* fallthrough */
|
|
case NVME_ZONE_STATE_EMPTY:
|
|
nvme_assign_zone_state(ns, zone, NVME_ZONE_STATE_FULL);
|
|
return NVME_SUCCESS;
|
|
|
|
default:
|
|
return NVME_ZONE_INVAL_TRANSITION;
|
|
}
|
|
}
|
|
|
|
static uint16_t nvme_zrm_close(NvmeNamespace *ns, NvmeZone *zone)
|
|
{
|
|
switch (nvme_get_zone_state(zone)) {
|
|
case NVME_ZONE_STATE_EXPLICITLY_OPEN:
|
|
case NVME_ZONE_STATE_IMPLICITLY_OPEN:
|
|
nvme_aor_dec_open(ns);
|
|
nvme_assign_zone_state(ns, zone, NVME_ZONE_STATE_CLOSED);
|
|
/* fall through */
|
|
case NVME_ZONE_STATE_CLOSED:
|
|
return NVME_SUCCESS;
|
|
|
|
default:
|
|
return NVME_ZONE_INVAL_TRANSITION;
|
|
}
|
|
}
|
|
|
|
static uint16_t nvme_zrm_reset(NvmeNamespace *ns, NvmeZone *zone)
|
|
{
|
|
switch (nvme_get_zone_state(zone)) {
|
|
case NVME_ZONE_STATE_EXPLICITLY_OPEN:
|
|
case NVME_ZONE_STATE_IMPLICITLY_OPEN:
|
|
nvme_aor_dec_open(ns);
|
|
/* fallthrough */
|
|
case NVME_ZONE_STATE_CLOSED:
|
|
nvme_aor_dec_active(ns);
|
|
|
|
if (zone->d.za & NVME_ZA_ZRWA_VALID) {
|
|
if (ns->params.numzrwa) {
|
|
ns->zns.numzrwa++;
|
|
}
|
|
}
|
|
|
|
/* fallthrough */
|
|
case NVME_ZONE_STATE_FULL:
|
|
zone->w_ptr = zone->d.zslba;
|
|
zone->d.wp = zone->w_ptr;
|
|
nvme_assign_zone_state(ns, zone, NVME_ZONE_STATE_EMPTY);
|
|
/* fallthrough */
|
|
case NVME_ZONE_STATE_EMPTY:
|
|
return NVME_SUCCESS;
|
|
|
|
default:
|
|
return NVME_ZONE_INVAL_TRANSITION;
|
|
}
|
|
}
|
|
|
|
static void nvme_zrm_auto_transition_zone(NvmeNamespace *ns)
|
|
{
|
|
NvmeZone *zone;
|
|
|
|
if (ns->params.max_open_zones &&
|
|
ns->nr_open_zones == ns->params.max_open_zones) {
|
|
zone = QTAILQ_FIRST(&ns->imp_open_zones);
|
|
if (zone) {
|
|
/*
|
|
* Automatically close this implicitly open zone.
|
|
*/
|
|
QTAILQ_REMOVE(&ns->imp_open_zones, zone, entry);
|
|
nvme_zrm_close(ns, zone);
|
|
}
|
|
}
|
|
}
|
|
|
|
enum {
|
|
NVME_ZRM_AUTO = 1 << 0,
|
|
NVME_ZRM_ZRWA = 1 << 1,
|
|
};
|
|
|
|
static uint16_t nvme_zrm_open_flags(NvmeCtrl *n, NvmeNamespace *ns,
|
|
NvmeZone *zone, int flags)
|
|
{
|
|
int act = 0;
|
|
uint16_t status;
|
|
|
|
switch (nvme_get_zone_state(zone)) {
|
|
case NVME_ZONE_STATE_EMPTY:
|
|
act = 1;
|
|
|
|
/* fallthrough */
|
|
|
|
case NVME_ZONE_STATE_CLOSED:
|
|
if (n->params.auto_transition_zones) {
|
|
nvme_zrm_auto_transition_zone(ns);
|
|
}
|
|
status = nvme_zns_check_resources(ns, act, 1,
|
|
(flags & NVME_ZRM_ZRWA) ? 1 : 0);
|
|
if (status) {
|
|
return status;
|
|
}
|
|
|
|
if (act) {
|
|
nvme_aor_inc_active(ns);
|
|
}
|
|
|
|
nvme_aor_inc_open(ns);
|
|
|
|
if (flags & NVME_ZRM_AUTO) {
|
|
nvme_assign_zone_state(ns, zone, NVME_ZONE_STATE_IMPLICITLY_OPEN);
|
|
return NVME_SUCCESS;
|
|
}
|
|
|
|
/* fallthrough */
|
|
|
|
case NVME_ZONE_STATE_IMPLICITLY_OPEN:
|
|
if (flags & NVME_ZRM_AUTO) {
|
|
return NVME_SUCCESS;
|
|
}
|
|
|
|
nvme_assign_zone_state(ns, zone, NVME_ZONE_STATE_EXPLICITLY_OPEN);
|
|
|
|
/* fallthrough */
|
|
|
|
case NVME_ZONE_STATE_EXPLICITLY_OPEN:
|
|
if (flags & NVME_ZRM_ZRWA) {
|
|
ns->zns.numzrwa--;
|
|
|
|
zone->d.za |= NVME_ZA_ZRWA_VALID;
|
|
}
|
|
|
|
return NVME_SUCCESS;
|
|
|
|
default:
|
|
return NVME_ZONE_INVAL_TRANSITION;
|
|
}
|
|
}
|
|
|
|
static inline uint16_t nvme_zrm_auto(NvmeCtrl *n, NvmeNamespace *ns,
|
|
NvmeZone *zone)
|
|
{
|
|
return nvme_zrm_open_flags(n, ns, zone, NVME_ZRM_AUTO);
|
|
}
|
|
|
|
static void nvme_advance_zone_wp(NvmeNamespace *ns, NvmeZone *zone,
|
|
uint32_t nlb)
|
|
{
|
|
zone->d.wp += nlb;
|
|
|
|
if (zone->d.wp == nvme_zone_wr_boundary(zone)) {
|
|
nvme_zrm_finish(ns, zone);
|
|
}
|
|
}
|
|
|
|
static void nvme_zoned_zrwa_implicit_flush(NvmeNamespace *ns, NvmeZone *zone,
|
|
uint32_t nlbc)
|
|
{
|
|
uint16_t nzrwafgs = DIV_ROUND_UP(nlbc, ns->zns.zrwafg);
|
|
|
|
nlbc = nzrwafgs * ns->zns.zrwafg;
|
|
|
|
trace_pci_nvme_zoned_zrwa_implicit_flush(zone->d.zslba, nlbc);
|
|
|
|
zone->w_ptr += nlbc;
|
|
|
|
nvme_advance_zone_wp(ns, zone, nlbc);
|
|
}
|
|
|
|
static void nvme_finalize_zoned_write(NvmeNamespace *ns, NvmeRequest *req)
|
|
{
|
|
NvmeRwCmd *rw = (NvmeRwCmd *)&req->cmd;
|
|
NvmeZone *zone;
|
|
uint64_t slba;
|
|
uint32_t nlb;
|
|
|
|
slba = le64_to_cpu(rw->slba);
|
|
nlb = le16_to_cpu(rw->nlb) + 1;
|
|
zone = nvme_get_zone_by_slba(ns, slba);
|
|
assert(zone);
|
|
|
|
if (zone->d.za & NVME_ZA_ZRWA_VALID) {
|
|
uint64_t ezrwa = zone->w_ptr + ns->zns.zrwas - 1;
|
|
uint64_t elba = slba + nlb - 1;
|
|
|
|
if (elba > ezrwa) {
|
|
nvme_zoned_zrwa_implicit_flush(ns, zone, elba - ezrwa);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
nvme_advance_zone_wp(ns, zone, nlb);
|
|
}
|
|
|
|
static inline bool nvme_is_write(NvmeRequest *req)
|
|
{
|
|
NvmeRwCmd *rw = (NvmeRwCmd *)&req->cmd;
|
|
|
|
return rw->opcode == NVME_CMD_WRITE ||
|
|
rw->opcode == NVME_CMD_ZONE_APPEND ||
|
|
rw->opcode == NVME_CMD_WRITE_ZEROES;
|
|
}
|
|
|
|
static void nvme_misc_cb(void *opaque, int ret)
|
|
{
|
|
NvmeRequest *req = opaque;
|
|
|
|
trace_pci_nvme_misc_cb(nvme_cid(req));
|
|
|
|
if (ret) {
|
|
nvme_aio_err(req, ret);
|
|
}
|
|
|
|
nvme_enqueue_req_completion(nvme_cq(req), req);
|
|
}
|
|
|
|
void nvme_rw_complete_cb(void *opaque, int ret)
|
|
{
|
|
NvmeRequest *req = opaque;
|
|
NvmeNamespace *ns = req->ns;
|
|
BlockBackend *blk = ns->blkconf.blk;
|
|
BlockAcctCookie *acct = &req->acct;
|
|
BlockAcctStats *stats = blk_get_stats(blk);
|
|
|
|
trace_pci_nvme_rw_complete_cb(nvme_cid(req), blk_name(blk));
|
|
|
|
if (ret) {
|
|
block_acct_failed(stats, acct);
|
|
nvme_aio_err(req, ret);
|
|
} else {
|
|
block_acct_done(stats, acct);
|
|
}
|
|
|
|
if (ns->params.zoned && nvme_is_write(req)) {
|
|
nvme_finalize_zoned_write(ns, req);
|
|
}
|
|
|
|
nvme_enqueue_req_completion(nvme_cq(req), req);
|
|
}
|
|
|
|
static void nvme_rw_cb(void *opaque, int ret)
|
|
{
|
|
NvmeRequest *req = opaque;
|
|
NvmeNamespace *ns = req->ns;
|
|
|
|
BlockBackend *blk = ns->blkconf.blk;
|
|
|
|
trace_pci_nvme_rw_cb(nvme_cid(req), blk_name(blk));
|
|
|
|
if (ret) {
|
|
goto out;
|
|
}
|
|
|
|
if (ns->lbaf.ms) {
|
|
NvmeRwCmd *rw = (NvmeRwCmd *)&req->cmd;
|
|
uint64_t slba = le64_to_cpu(rw->slba);
|
|
uint32_t nlb = (uint32_t)le16_to_cpu(rw->nlb) + 1;
|
|
uint64_t offset = nvme_moff(ns, slba);
|
|
|
|
if (req->cmd.opcode == NVME_CMD_WRITE_ZEROES) {
|
|
size_t mlen = nvme_m2b(ns, nlb);
|
|
|
|
req->aiocb = blk_aio_pwrite_zeroes(blk, offset, mlen,
|
|
BDRV_REQ_MAY_UNMAP,
|
|
nvme_rw_complete_cb, req);
|
|
return;
|
|
}
|
|
|
|
if (nvme_ns_ext(ns) || req->cmd.mptr) {
|
|
uint16_t status;
|
|
|
|
nvme_sg_unmap(&req->sg);
|
|
status = nvme_map_mdata(nvme_ctrl(req), nlb, req);
|
|
if (status) {
|
|
ret = -EFAULT;
|
|
goto out;
|
|
}
|
|
|
|
if (req->cmd.opcode == NVME_CMD_READ) {
|
|
return nvme_blk_read(blk, offset, 1, nvme_rw_complete_cb, req);
|
|
}
|
|
|
|
return nvme_blk_write(blk, offset, 1, nvme_rw_complete_cb, req);
|
|
}
|
|
}
|
|
|
|
out:
|
|
nvme_rw_complete_cb(req, ret);
|
|
}
|
|
|
|
static void nvme_verify_cb(void *opaque, int ret)
|
|
{
|
|
NvmeBounceContext *ctx = opaque;
|
|
NvmeRequest *req = ctx->req;
|
|
NvmeNamespace *ns = req->ns;
|
|
BlockBackend *blk = ns->blkconf.blk;
|
|
BlockAcctCookie *acct = &req->acct;
|
|
BlockAcctStats *stats = blk_get_stats(blk);
|
|
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_verify_cb(nvme_cid(req), prinfo, apptag, appmask, reftag);
|
|
|
|
if (ret) {
|
|
block_acct_failed(stats, acct);
|
|
nvme_aio_err(req, ret);
|
|
goto out;
|
|
}
|
|
|
|
block_acct_done(stats, acct);
|
|
|
|
if (NVME_ID_NS_DPS_TYPE(ns->id_ns.dps)) {
|
|
status = nvme_dif_mangle_mdata(ns, ctx->mdata.bounce,
|
|
ctx->mdata.iov.size, slba);
|
|
if (status) {
|
|
req->status = status;
|
|
goto out;
|
|
}
|
|
|
|
req->status = nvme_dif_check(ns, ctx->data.bounce, ctx->data.iov.size,
|
|
ctx->mdata.bounce, ctx->mdata.iov.size,
|
|
prinfo, slba, apptag, appmask, &reftag);
|
|
}
|
|
|
|
out:
|
|
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_enqueue_req_completion(nvme_cq(req), req);
|
|
}
|
|
|
|
|
|
static void nvme_verify_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_verify_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_verify_cb, ctx);
|
|
return;
|
|
|
|
out:
|
|
nvme_verify_cb(ctx, ret);
|
|
}
|
|
|
|
struct nvme_compare_ctx {
|
|
struct {
|
|
QEMUIOVector iov;
|
|
uint8_t *bounce;
|
|
} data;
|
|
|
|
struct {
|
|
QEMUIOVector iov;
|
|
uint8_t *bounce;
|
|
} mdata;
|
|
};
|
|
|
|
static void nvme_compare_mdata_cb(void *opaque, int ret)
|
|
{
|
|
NvmeRequest *req = opaque;
|
|
NvmeNamespace *ns = req->ns;
|
|
NvmeCtrl *n = nvme_ctrl(req);
|
|
NvmeRwCmd *rw = (NvmeRwCmd *)&req->cmd;
|
|
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);
|
|
struct nvme_compare_ctx *ctx = req->opaque;
|
|
g_autofree uint8_t *buf = NULL;
|
|
BlockBackend *blk = ns->blkconf.blk;
|
|
BlockAcctCookie *acct = &req->acct;
|
|
BlockAcctStats *stats = blk_get_stats(blk);
|
|
uint16_t status = NVME_SUCCESS;
|
|
|
|
reftag |= cdw3 << 32;
|
|
|
|
trace_pci_nvme_compare_mdata_cb(nvme_cid(req));
|
|
|
|
if (ret) {
|
|
block_acct_failed(stats, acct);
|
|
nvme_aio_err(req, ret);
|
|
goto out;
|
|
}
|
|
|
|
buf = g_malloc(ctx->mdata.iov.size);
|
|
|
|
status = nvme_bounce_mdata(n, buf, ctx->mdata.iov.size,
|
|
NVME_TX_DIRECTION_TO_DEVICE, req);
|
|
if (status) {
|
|
req->status = status;
|
|
goto out;
|
|
}
|
|
|
|
if (NVME_ID_NS_DPS_TYPE(ns->id_ns.dps)) {
|
|
uint64_t slba = le64_to_cpu(rw->slba);
|
|
uint8_t *bufp;
|
|
uint8_t *mbufp = ctx->mdata.bounce;
|
|
uint8_t *end = mbufp + ctx->mdata.iov.size;
|
|
int16_t pil = 0;
|
|
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* When formatted with protection information, do not compare the DIF
|
|
* tuple.
|
|
*/
|
|
if (!(ns->id_ns.dps & NVME_ID_NS_DPS_FIRST_EIGHT)) {
|
|
pil = ns->lbaf.ms - nvme_pi_tuple_size(ns);
|
|
}
|
|
|
|
for (bufp = buf; mbufp < end; bufp += ns->lbaf.ms, mbufp += ns->lbaf.ms) {
|
|
if (memcmp(bufp + pil, mbufp + pil, ns->lbaf.ms - pil)) {
|
|
req->status = NVME_CMP_FAILURE | NVME_DNR;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
goto out;
|
|
}
|
|
|
|
if (memcmp(buf, ctx->mdata.bounce, ctx->mdata.iov.size)) {
|
|
req->status = NVME_CMP_FAILURE | NVME_DNR;
|
|
goto out;
|
|
}
|
|
|
|
block_acct_done(stats, acct);
|
|
|
|
out:
|
|
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_enqueue_req_completion(nvme_cq(req), req);
|
|
}
|
|
|
|
static void nvme_compare_data_cb(void *opaque, int ret)
|
|
{
|
|
NvmeRequest *req = opaque;
|
|
NvmeCtrl *n = nvme_ctrl(req);
|
|
NvmeNamespace *ns = req->ns;
|
|
BlockBackend *blk = ns->blkconf.blk;
|
|
BlockAcctCookie *acct = &req->acct;
|
|
BlockAcctStats *stats = blk_get_stats(blk);
|
|
|
|
struct nvme_compare_ctx *ctx = req->opaque;
|
|
g_autofree uint8_t *buf = NULL;
|
|
uint16_t status;
|
|
|
|
trace_pci_nvme_compare_data_cb(nvme_cid(req));
|
|
|
|
if (ret) {
|
|
block_acct_failed(stats, acct);
|
|
nvme_aio_err(req, ret);
|
|
goto out;
|
|
}
|
|
|
|
buf = g_malloc(ctx->data.iov.size);
|
|
|
|
status = nvme_bounce_data(n, buf, ctx->data.iov.size,
|
|
NVME_TX_DIRECTION_TO_DEVICE, req);
|
|
if (status) {
|
|
req->status = status;
|
|
goto out;
|
|
}
|
|
|
|
if (memcmp(buf, ctx->data.bounce, ctx->data.iov.size)) {
|
|
req->status = NVME_CMP_FAILURE | NVME_DNR;
|
|
goto out;
|
|
}
|
|
|
|
if (ns->lbaf.ms) {
|
|
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);
|
|
|
|
ctx->mdata.bounce = g_malloc(mlen);
|
|
|
|
qemu_iovec_init(&ctx->mdata.iov, 1);
|
|
qemu_iovec_add(&ctx->mdata.iov, ctx->mdata.bounce, mlen);
|
|
|
|
req->aiocb = blk_aio_preadv(blk, offset, &ctx->mdata.iov, 0,
|
|
nvme_compare_mdata_cb, req);
|
|
return;
|
|
}
|
|
|
|
block_acct_done(stats, acct);
|
|
|
|
out:
|
|
qemu_iovec_destroy(&ctx->data.iov);
|
|
g_free(ctx->data.bounce);
|
|
g_free(ctx);
|
|
|
|
nvme_enqueue_req_completion(nvme_cq(req), req);
|
|
}
|
|
|
|
typedef struct NvmeDSMAIOCB {
|
|
BlockAIOCB common;
|
|
BlockAIOCB *aiocb;
|
|
NvmeRequest *req;
|
|
int ret;
|
|
|
|
NvmeDsmRange *range;
|
|
unsigned int nr;
|
|
unsigned int idx;
|
|
} NvmeDSMAIOCB;
|
|
|
|
static void nvme_dsm_cancel(BlockAIOCB *aiocb)
|
|
{
|
|
NvmeDSMAIOCB *iocb = container_of(aiocb, NvmeDSMAIOCB, common);
|
|
|
|
/* break nvme_dsm_cb loop */
|
|
iocb->idx = iocb->nr;
|
|
iocb->ret = -ECANCELED;
|
|
|
|
if (iocb->aiocb) {
|
|
blk_aio_cancel_async(iocb->aiocb);
|
|
iocb->aiocb = NULL;
|
|
} else {
|
|
/*
|
|
* We only reach this if nvme_dsm_cancel() has already been called or
|
|
* the command ran to completion.
|
|
*/
|
|
assert(iocb->idx == iocb->nr);
|
|
}
|
|
}
|
|
|
|
static const AIOCBInfo nvme_dsm_aiocb_info = {
|
|
.aiocb_size = sizeof(NvmeDSMAIOCB),
|
|
.cancel_async = nvme_dsm_cancel,
|
|
};
|
|
|
|
static void nvme_dsm_cb(void *opaque, int ret);
|
|
|
|
static void nvme_dsm_md_cb(void *opaque, int ret)
|
|
{
|
|
NvmeDSMAIOCB *iocb = opaque;
|
|
NvmeRequest *req = iocb->req;
|
|
NvmeNamespace *ns = req->ns;
|
|
NvmeDsmRange *range;
|
|
uint64_t slba;
|
|
uint32_t nlb;
|
|
|
|
if (ret < 0 || iocb->ret < 0 || !ns->lbaf.ms) {
|
|
goto done;
|
|
}
|
|
|
|
range = &iocb->range[iocb->idx - 1];
|
|
slba = le64_to_cpu(range->slba);
|
|
nlb = le32_to_cpu(range->nlb);
|
|
|
|
/*
|
|
* Check that all block were discarded (zeroed); otherwise we do not zero
|
|
* the metadata.
|
|
*/
|
|
|
|
ret = nvme_block_status_all(ns, slba, nlb, BDRV_BLOCK_ZERO);
|
|
if (ret) {
|
|
if (ret < 0) {
|
|
goto done;
|
|
}
|
|
|
|
nvme_dsm_cb(iocb, 0);
|
|
return;
|
|
}
|
|
|
|
iocb->aiocb = blk_aio_pwrite_zeroes(ns->blkconf.blk, nvme_moff(ns, slba),
|
|
nvme_m2b(ns, nlb), BDRV_REQ_MAY_UNMAP,
|
|
nvme_dsm_cb, iocb);
|
|
return;
|
|
|
|
done:
|
|
nvme_dsm_cb(iocb, ret);
|
|
}
|
|
|
|
static void nvme_dsm_cb(void *opaque, int ret)
|
|
{
|
|
NvmeDSMAIOCB *iocb = opaque;
|
|
NvmeRequest *req = iocb->req;
|
|
NvmeCtrl *n = nvme_ctrl(req);
|
|
NvmeNamespace *ns = req->ns;
|
|
NvmeDsmRange *range;
|
|
uint64_t slba;
|
|
uint32_t nlb;
|
|
|
|
if (iocb->ret < 0) {
|
|
goto done;
|
|
} else if (ret < 0) {
|
|
iocb->ret = ret;
|
|
goto done;
|
|
}
|
|
|
|
next:
|
|
if (iocb->idx == iocb->nr) {
|
|
goto done;
|
|
}
|
|
|
|
range = &iocb->range[iocb->idx++];
|
|
slba = le64_to_cpu(range->slba);
|
|
nlb = le32_to_cpu(range->nlb);
|
|
|
|
trace_pci_nvme_dsm_deallocate(slba, nlb);
|
|
|
|
if (nlb > n->dmrsl) {
|
|
trace_pci_nvme_dsm_single_range_limit_exceeded(nlb, n->dmrsl);
|
|
goto next;
|
|
}
|
|
|
|
if (nvme_check_bounds(ns, slba, nlb)) {
|
|
trace_pci_nvme_err_invalid_lba_range(slba, nlb,
|
|
ns->id_ns.nsze);
|
|
goto next;
|
|
}
|
|
|
|
iocb->aiocb = blk_aio_pdiscard(ns->blkconf.blk, nvme_l2b(ns, slba),
|
|
nvme_l2b(ns, nlb),
|
|
nvme_dsm_md_cb, iocb);
|
|
return;
|
|
|
|
done:
|
|
iocb->aiocb = NULL;
|
|
iocb->common.cb(iocb->common.opaque, iocb->ret);
|
|
g_free(iocb->range);
|
|
qemu_aio_unref(iocb);
|
|
}
|
|
|
|
static uint16_t nvme_dsm(NvmeCtrl *n, NvmeRequest *req)
|
|
{
|
|
NvmeNamespace *ns = req->ns;
|
|
NvmeDsmCmd *dsm = (NvmeDsmCmd *) &req->cmd;
|
|
uint32_t attr = le32_to_cpu(dsm->attributes);
|
|
uint32_t nr = (le32_to_cpu(dsm->nr) & 0xff) + 1;
|
|
uint16_t status = NVME_SUCCESS;
|
|
|
|
trace_pci_nvme_dsm(nr, attr);
|
|
|
|
if (attr & NVME_DSMGMT_AD) {
|
|
NvmeDSMAIOCB *iocb = blk_aio_get(&nvme_dsm_aiocb_info, ns->blkconf.blk,
|
|
nvme_misc_cb, req);
|
|
|
|
iocb->req = req;
|
|
iocb->ret = 0;
|
|
iocb->range = g_new(NvmeDsmRange, nr);
|
|
iocb->nr = nr;
|
|
iocb->idx = 0;
|
|
|
|
status = nvme_h2c(n, (uint8_t *)iocb->range, sizeof(NvmeDsmRange) * nr,
|
|
req);
|
|
if (status) {
|
|
g_free(iocb->range);
|
|
qemu_aio_unref(iocb);
|
|
|
|
return status;
|
|
}
|
|
|
|
req->aiocb = &iocb->common;
|
|
nvme_dsm_cb(iocb, 0);
|
|
|
|
return NVME_NO_COMPLETE;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
static uint16_t nvme_verify(NvmeCtrl *n, NvmeRequest *req)
|
|
{
|
|
NvmeRwCmd *rw = (NvmeRwCmd *)&req->cmd;
|
|
NvmeNamespace *ns = req->ns;
|
|
BlockBackend *blk = ns->blkconf.blk;
|
|
uint64_t slba = le64_to_cpu(rw->slba);
|
|
uint32_t nlb = le16_to_cpu(rw->nlb) + 1;
|
|
size_t len = nvme_l2b(ns, nlb);
|
|
size_t data_len = len;
|
|
int64_t offset = nvme_l2b(ns, slba);
|
|
uint8_t prinfo = NVME_RW_PRINFO(le16_to_cpu(rw->control));
|
|
uint32_t reftag = le32_to_cpu(rw->reftag);
|
|
NvmeBounceContext *ctx = NULL;
|
|
uint16_t status;
|
|
|
|
trace_pci_nvme_verify(nvme_cid(req), nvme_nsid(ns), slba, nlb);
|
|
|
|
if (NVME_ID_NS_DPS_TYPE(ns->id_ns.dps)) {
|
|
status = nvme_check_prinfo(ns, prinfo, slba, reftag);
|
|
if (status) {
|
|
return status;
|
|
}
|
|
|
|
if (prinfo & NVME_PRINFO_PRACT) {
|
|
return NVME_INVALID_PROT_INFO | NVME_DNR;
|
|
}
|
|
}
|
|
|
|
if (nvme_ns_ext(ns) && !(NVME_ID_CTRL_CTRATT_MEM(n->id_ctrl.ctratt))) {
|
|
data_len += nvme_m2b(ns, nlb);
|
|
}
|
|
|
|
if (data_len > (n->page_size << n->params.vsl)) {
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
|
|
status = nvme_check_bounds(ns, slba, nlb);
|
|
if (status) {
|
|
return status;
|
|
}
|
|
|
|
if (NVME_ERR_REC_DULBE(ns->features.err_rec)) {
|
|
status = nvme_check_dulbe(ns, slba, nlb);
|
|
if (status) {
|
|
return status;
|
|
}
|
|
}
|
|
|
|
ctx = g_new0(NvmeBounceContext, 1);
|
|
ctx->req = req;
|
|
|
|
ctx->data.bounce = g_malloc(len);
|
|
|
|
qemu_iovec_init(&ctx->data.iov, 1);
|
|
qemu_iovec_add(&ctx->data.iov, ctx->data.bounce, len);
|
|
|
|
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_verify_mdata_in_cb, ctx);
|
|
return NVME_NO_COMPLETE;
|
|
}
|
|
|
|
typedef struct NvmeCopyAIOCB {
|
|
BlockAIOCB common;
|
|
BlockAIOCB *aiocb;
|
|
NvmeRequest *req;
|
|
NvmeCtrl *n;
|
|
int ret;
|
|
|
|
void *ranges;
|
|
unsigned int format;
|
|
int nr;
|
|
int idx;
|
|
|
|
uint8_t *bounce;
|
|
QEMUIOVector iov;
|
|
struct {
|
|
BlockAcctCookie read;
|
|
BlockAcctCookie write;
|
|
} acct;
|
|
|
|
uint64_t reftag;
|
|
uint64_t slba;
|
|
|
|
NvmeZone *zone;
|
|
NvmeNamespace *sns;
|
|
uint32_t tcl;
|
|
} NvmeCopyAIOCB;
|
|
|
|
static void nvme_copy_cancel(BlockAIOCB *aiocb)
|
|
{
|
|
NvmeCopyAIOCB *iocb = container_of(aiocb, NvmeCopyAIOCB, common);
|
|
|
|
iocb->ret = -ECANCELED;
|
|
|
|
if (iocb->aiocb) {
|
|
blk_aio_cancel_async(iocb->aiocb);
|
|
iocb->aiocb = NULL;
|
|
}
|
|
}
|
|
|
|
static const AIOCBInfo nvme_copy_aiocb_info = {
|
|
.aiocb_size = sizeof(NvmeCopyAIOCB),
|
|
.cancel_async = nvme_copy_cancel,
|
|
};
|
|
|
|
static void nvme_copy_done(NvmeCopyAIOCB *iocb)
|
|
{
|
|
NvmeRequest *req = iocb->req;
|
|
NvmeNamespace *ns = req->ns;
|
|
BlockAcctStats *stats = blk_get_stats(ns->blkconf.blk);
|
|
|
|
if (iocb->idx != iocb->nr) {
|
|
req->cqe.result = cpu_to_le32(iocb->idx);
|
|
}
|
|
|
|
qemu_iovec_destroy(&iocb->iov);
|
|
g_free(iocb->bounce);
|
|
|
|
if (iocb->ret < 0) {
|
|
block_acct_failed(stats, &iocb->acct.read);
|
|
block_acct_failed(stats, &iocb->acct.write);
|
|
} else {
|
|
block_acct_done(stats, &iocb->acct.read);
|
|
block_acct_done(stats, &iocb->acct.write);
|
|
}
|
|
|
|
iocb->common.cb(iocb->common.opaque, iocb->ret);
|
|
qemu_aio_unref(iocb);
|
|
}
|
|
|
|
static void nvme_do_copy(NvmeCopyAIOCB *iocb);
|
|
|
|
static void nvme_copy_source_range_parse_format0_2(void *ranges,
|
|
int idx, uint64_t *slba,
|
|
uint32_t *nlb,
|
|
uint32_t *snsid,
|
|
uint16_t *apptag,
|
|
uint16_t *appmask,
|
|
uint64_t *reftag)
|
|
{
|
|
NvmeCopySourceRangeFormat0_2 *_ranges = ranges;
|
|
|
|
if (snsid) {
|
|
*snsid = le32_to_cpu(_ranges[idx].sparams);
|
|
}
|
|
|
|
if (slba) {
|
|
*slba = le64_to_cpu(_ranges[idx].slba);
|
|
}
|
|
|
|
if (nlb) {
|
|
*nlb = le16_to_cpu(_ranges[idx].nlb) + 1;
|
|
}
|
|
|
|
if (apptag) {
|
|
*apptag = le16_to_cpu(_ranges[idx].apptag);
|
|
}
|
|
|
|
if (appmask) {
|
|
*appmask = le16_to_cpu(_ranges[idx].appmask);
|
|
}
|
|
|
|
if (reftag) {
|
|
*reftag = le32_to_cpu(_ranges[idx].reftag);
|
|
}
|
|
}
|
|
|
|
static void nvme_copy_source_range_parse_format1_3(void *ranges, int idx,
|
|
uint64_t *slba,
|
|
uint32_t *nlb,
|
|
uint32_t *snsid,
|
|
uint16_t *apptag,
|
|
uint16_t *appmask,
|
|
uint64_t *reftag)
|
|
{
|
|
NvmeCopySourceRangeFormat1_3 *_ranges = ranges;
|
|
|
|
if (snsid) {
|
|
*snsid = le32_to_cpu(_ranges[idx].sparams);
|
|
}
|
|
|
|
if (slba) {
|
|
*slba = le64_to_cpu(_ranges[idx].slba);
|
|
}
|
|
|
|
if (nlb) {
|
|
*nlb = le16_to_cpu(_ranges[idx].nlb) + 1;
|
|
}
|
|
|
|
if (apptag) {
|
|
*apptag = le16_to_cpu(_ranges[idx].apptag);
|
|
}
|
|
|
|
if (appmask) {
|
|
*appmask = le16_to_cpu(_ranges[idx].appmask);
|
|
}
|
|
|
|
if (reftag) {
|
|
*reftag = 0;
|
|
|
|
*reftag |= (uint64_t)_ranges[idx].sr[4] << 40;
|
|
*reftag |= (uint64_t)_ranges[idx].sr[5] << 32;
|
|
*reftag |= (uint64_t)_ranges[idx].sr[6] << 24;
|
|
*reftag |= (uint64_t)_ranges[idx].sr[7] << 16;
|
|
*reftag |= (uint64_t)_ranges[idx].sr[8] << 8;
|
|
*reftag |= (uint64_t)_ranges[idx].sr[9];
|
|
}
|
|
}
|
|
|
|
static void nvme_copy_source_range_parse(void *ranges, int idx, uint8_t format,
|
|
uint64_t *slba, uint32_t *nlb,
|
|
uint32_t *snsid, uint16_t *apptag,
|
|
uint16_t *appmask, uint64_t *reftag)
|
|
{
|
|
switch (format) {
|
|
case NVME_COPY_FORMAT_0:
|
|
case NVME_COPY_FORMAT_2:
|
|
nvme_copy_source_range_parse_format0_2(ranges, idx, slba, nlb, snsid,
|
|
apptag, appmask, reftag);
|
|
break;
|
|
|
|
case NVME_COPY_FORMAT_1:
|
|
case NVME_COPY_FORMAT_3:
|
|
nvme_copy_source_range_parse_format1_3(ranges, idx, slba, nlb, snsid,
|
|
apptag, appmask, reftag);
|
|
break;
|
|
|
|
default:
|
|
abort();
|
|
}
|
|
}
|
|
|
|
static inline uint16_t nvme_check_copy_mcl(NvmeNamespace *ns,
|
|
NvmeCopyAIOCB *iocb, uint16_t nr)
|
|
{
|
|
uint32_t copy_len = 0;
|
|
|
|
for (int idx = 0; idx < nr; idx++) {
|
|
uint32_t nlb;
|
|
nvme_copy_source_range_parse(iocb->ranges, idx, iocb->format, NULL,
|
|
&nlb, NULL, NULL, NULL, NULL);
|
|
copy_len += nlb;
|
|
}
|
|
iocb->tcl = copy_len;
|
|
if (copy_len > ns->id_ns.mcl) {
|
|
return NVME_CMD_SIZE_LIMIT | NVME_DNR;
|
|
}
|
|
|
|
return NVME_SUCCESS;
|
|
}
|
|
|
|
static void nvme_copy_out_completed_cb(void *opaque, int ret)
|
|
{
|
|
NvmeCopyAIOCB *iocb = opaque;
|
|
NvmeRequest *req = iocb->req;
|
|
NvmeNamespace *dns = req->ns;
|
|
uint32_t nlb;
|
|
|
|
nvme_copy_source_range_parse(iocb->ranges, iocb->idx, iocb->format, NULL,
|
|
&nlb, NULL, NULL, NULL, NULL);
|
|
|
|
if (ret < 0) {
|
|
iocb->ret = ret;
|
|
goto out;
|
|
} else if (iocb->ret < 0) {
|
|
goto out;
|
|
}
|
|
|
|
if (dns->params.zoned) {
|
|
nvme_advance_zone_wp(dns, iocb->zone, nlb);
|
|
}
|
|
|
|
iocb->idx++;
|
|
iocb->slba += nlb;
|
|
out:
|
|
nvme_do_copy(iocb);
|
|
}
|
|
|
|
static void nvme_copy_out_cb(void *opaque, int ret)
|
|
{
|
|
NvmeCopyAIOCB *iocb = opaque;
|
|
NvmeRequest *req = iocb->req;
|
|
NvmeNamespace *dns = req->ns;
|
|
uint32_t nlb;
|
|
size_t mlen;
|
|
uint8_t *mbounce;
|
|
|
|
if (ret < 0 || iocb->ret < 0 || !dns->lbaf.ms) {
|
|
goto out;
|
|
}
|
|
|
|
nvme_copy_source_range_parse(iocb->ranges, iocb->idx, iocb->format, NULL,
|
|
&nlb, NULL, NULL, NULL, NULL);
|
|
|
|
mlen = nvme_m2b(dns, nlb);
|
|
mbounce = iocb->bounce + nvme_l2b(dns, nlb);
|
|
|
|
qemu_iovec_reset(&iocb->iov);
|
|
qemu_iovec_add(&iocb->iov, mbounce, mlen);
|
|
|
|
iocb->aiocb = blk_aio_pwritev(dns->blkconf.blk, nvme_moff(dns, iocb->slba),
|
|
&iocb->iov, 0, nvme_copy_out_completed_cb,
|
|
iocb);
|
|
|
|
return;
|
|
|
|
out:
|
|
nvme_copy_out_completed_cb(iocb, ret);
|
|
}
|
|
|
|
static void nvme_copy_in_completed_cb(void *opaque, int ret)
|
|
{
|
|
NvmeCopyAIOCB *iocb = opaque;
|
|
NvmeRequest *req = iocb->req;
|
|
NvmeNamespace *sns = iocb->sns;
|
|
NvmeNamespace *dns = req->ns;
|
|
NvmeCopyCmd *copy = NULL;
|
|
uint8_t *mbounce = NULL;
|
|
uint32_t nlb;
|
|
uint64_t slba;
|
|
uint16_t apptag, appmask;
|
|
uint64_t reftag;
|
|
size_t len, mlen;
|
|
uint16_t status;
|
|
|
|
if (ret < 0) {
|
|
iocb->ret = ret;
|
|
goto out;
|
|
} else if (iocb->ret < 0) {
|
|
goto out;
|
|
}
|
|
|
|
nvme_copy_source_range_parse(iocb->ranges, iocb->idx, iocb->format, &slba,
|
|
&nlb, NULL, &apptag, &appmask, &reftag);
|
|
|
|
trace_pci_nvme_copy_out(iocb->slba, nlb);
|
|
|
|
len = nvme_l2b(sns, nlb);
|
|
|
|
if (NVME_ID_NS_DPS_TYPE(sns->id_ns.dps)) {
|
|
copy = (NvmeCopyCmd *)&req->cmd;
|
|
|
|
uint16_t prinfor = ((copy->control[0] >> 4) & 0xf);
|
|
|
|
mlen = nvme_m2b(sns, nlb);
|
|
mbounce = iocb->bounce + nvme_l2b(sns, nlb);
|
|
|
|
status = nvme_dif_mangle_mdata(sns, mbounce, mlen, slba);
|
|
if (status) {
|
|
goto invalid;
|
|
}
|
|
status = nvme_dif_check(sns, iocb->bounce, len, mbounce, mlen, prinfor,
|
|
slba, apptag, appmask, &reftag);
|
|
if (status) {
|
|
goto invalid;
|
|
}
|
|
}
|
|
|
|
if (NVME_ID_NS_DPS_TYPE(dns->id_ns.dps)) {
|
|
copy = (NvmeCopyCmd *)&req->cmd;
|
|
uint16_t prinfow = ((copy->control[2] >> 2) & 0xf);
|
|
|
|
mlen = nvme_m2b(dns, nlb);
|
|
mbounce = iocb->bounce + nvme_l2b(dns, nlb);
|
|
|
|
apptag = le16_to_cpu(copy->apptag);
|
|
appmask = le16_to_cpu(copy->appmask);
|
|
|
|
if (prinfow & NVME_PRINFO_PRACT) {
|
|
status = nvme_check_prinfo(dns, prinfow, iocb->slba, iocb->reftag);
|
|
if (status) {
|
|
goto invalid;
|
|
}
|
|
|
|
nvme_dif_pract_generate_dif(dns, iocb->bounce, len, mbounce, mlen,
|
|
apptag, &iocb->reftag);
|
|
} else {
|
|
status = nvme_dif_check(dns, iocb->bounce, len, mbounce, mlen,
|
|
prinfow, iocb->slba, apptag, appmask,
|
|
&iocb->reftag);
|
|
if (status) {
|
|
goto invalid;
|
|
}
|
|
}
|
|
}
|
|
|
|
status = nvme_check_bounds(dns, iocb->slba, nlb);
|
|
if (status) {
|
|
goto invalid;
|
|
}
|
|
|
|
if (dns->params.zoned) {
|
|
status = nvme_check_zone_write(dns, iocb->zone, iocb->slba, nlb);
|
|
if (status) {
|
|
goto invalid;
|
|
}
|
|
|
|
if (!(iocb->zone->d.za & NVME_ZA_ZRWA_VALID)) {
|
|
iocb->zone->w_ptr += nlb;
|
|
}
|
|
}
|
|
|
|
qemu_iovec_reset(&iocb->iov);
|
|
qemu_iovec_add(&iocb->iov, iocb->bounce, len);
|
|
|
|
block_acct_start(blk_get_stats(dns->blkconf.blk), &iocb->acct.write, 0,
|
|
BLOCK_ACCT_WRITE);
|
|
|
|
iocb->aiocb = blk_aio_pwritev(dns->blkconf.blk, nvme_l2b(dns, iocb->slba),
|
|
&iocb->iov, 0, nvme_copy_out_cb, iocb);
|
|
|
|
return;
|
|
|
|
invalid:
|
|
req->status = status;
|
|
iocb->ret = -1;
|
|
out:
|
|
nvme_do_copy(iocb);
|
|
}
|
|
|
|
static void nvme_copy_in_cb(void *opaque, int ret)
|
|
{
|
|
NvmeCopyAIOCB *iocb = opaque;
|
|
NvmeNamespace *sns = iocb->sns;
|
|
uint64_t slba;
|
|
uint32_t nlb;
|
|
|
|
if (ret < 0 || iocb->ret < 0 || !sns->lbaf.ms) {
|
|
goto out;
|
|
}
|
|
|
|
nvme_copy_source_range_parse(iocb->ranges, iocb->idx, iocb->format, &slba,
|
|
&nlb, NULL, NULL, NULL, NULL);
|
|
|
|
qemu_iovec_reset(&iocb->iov);
|
|
qemu_iovec_add(&iocb->iov, iocb->bounce + nvme_l2b(sns, nlb),
|
|
nvme_m2b(sns, nlb));
|
|
|
|
iocb->aiocb = blk_aio_preadv(sns->blkconf.blk, nvme_moff(sns, slba),
|
|
&iocb->iov, 0, nvme_copy_in_completed_cb,
|
|
iocb);
|
|
return;
|
|
|
|
out:
|
|
nvme_copy_in_completed_cb(iocb, ret);
|
|
}
|
|
|
|
static inline bool nvme_csi_supports_copy(uint8_t csi)
|
|
{
|
|
return csi == NVME_CSI_NVM || csi == NVME_CSI_ZONED;
|
|
}
|
|
|
|
static inline bool nvme_copy_ns_format_match(NvmeNamespace *sns,
|
|
NvmeNamespace *dns)
|
|
{
|
|
return sns->lbaf.ds == dns->lbaf.ds && sns->lbaf.ms == dns->lbaf.ms;
|
|
}
|
|
|
|
static bool nvme_copy_matching_ns_format(NvmeNamespace *sns, NvmeNamespace *dns,
|
|
bool pi_enable)
|
|
{
|
|
if (!nvme_csi_supports_copy(sns->csi) ||
|
|
!nvme_csi_supports_copy(dns->csi)) {
|
|
return false;
|
|
}
|
|
|
|
if (!pi_enable && !nvme_copy_ns_format_match(sns, dns)) {
|
|
return false;
|
|
}
|
|
|
|
if (pi_enable && (!nvme_copy_ns_format_match(sns, dns) ||
|
|
sns->id_ns.dps != dns->id_ns.dps)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static inline bool nvme_copy_corresp_pi_match(NvmeNamespace *sns,
|
|
NvmeNamespace *dns)
|
|
{
|
|
return sns->lbaf.ms == 0 &&
|
|
((dns->lbaf.ms == 8 && dns->pif == 0) ||
|
|
(dns->lbaf.ms == 16 && dns->pif == 1));
|
|
}
|
|
|
|
static bool nvme_copy_corresp_pi_format(NvmeNamespace *sns, NvmeNamespace *dns,
|
|
bool sns_pi_en)
|
|
{
|
|
if (!nvme_csi_supports_copy(sns->csi) ||
|
|
!nvme_csi_supports_copy(dns->csi)) {
|
|
return false;
|
|
}
|
|
|
|
if (!sns_pi_en && !nvme_copy_corresp_pi_match(sns, dns)) {
|
|
return false;
|
|
}
|
|
|
|
if (sns_pi_en && !nvme_copy_corresp_pi_match(dns, sns)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void nvme_do_copy(NvmeCopyAIOCB *iocb)
|
|
{
|
|
NvmeRequest *req = iocb->req;
|
|
NvmeNamespace *sns;
|
|
NvmeNamespace *dns = req->ns;
|
|
NvmeCopyCmd *copy = (NvmeCopyCmd *)&req->cmd;
|
|
uint16_t prinfor = ((copy->control[0] >> 4) & 0xf);
|
|
uint16_t prinfow = ((copy->control[2] >> 2) & 0xf);
|
|
uint64_t slba;
|
|
uint32_t nlb;
|
|
size_t len;
|
|
uint16_t status;
|
|
uint32_t dnsid = le32_to_cpu(req->cmd.nsid);
|
|
uint32_t snsid = dnsid;
|
|
|
|
if (iocb->ret < 0) {
|
|
goto done;
|
|
}
|
|
|
|
if (iocb->idx == iocb->nr) {
|
|
goto done;
|
|
}
|
|
|
|
if (iocb->format == 2 || iocb->format == 3) {
|
|
nvme_copy_source_range_parse(iocb->ranges, iocb->idx, iocb->format,
|
|
&slba, &nlb, &snsid, NULL, NULL, NULL);
|
|
if (snsid != dnsid) {
|
|
if (snsid == NVME_NSID_BROADCAST ||
|
|
!nvme_nsid_valid(iocb->n, snsid)) {
|
|
status = NVME_INVALID_NSID | NVME_DNR;
|
|
goto invalid;
|
|
}
|
|
iocb->sns = nvme_ns(iocb->n, snsid);
|
|
if (unlikely(!iocb->sns)) {
|
|
status = NVME_INVALID_FIELD | NVME_DNR;
|
|
goto invalid;
|
|
}
|
|
} else {
|
|
if (((slba + nlb) > iocb->slba) &&
|
|
((slba + nlb) < (iocb->slba + iocb->tcl))) {
|
|
status = NVME_CMD_OVERLAP_IO_RANGE | NVME_DNR;
|
|
goto invalid;
|
|
}
|
|
}
|
|
} else {
|
|
nvme_copy_source_range_parse(iocb->ranges, iocb->idx, iocb->format,
|
|
&slba, &nlb, NULL, NULL, NULL, NULL);
|
|
}
|
|
|
|
sns = iocb->sns;
|
|
if ((snsid == dnsid) && NVME_ID_NS_DPS_TYPE(sns->id_ns.dps) &&
|
|
((prinfor & NVME_PRINFO_PRACT) != (prinfow & NVME_PRINFO_PRACT))) {
|
|
status = NVME_INVALID_FIELD | NVME_DNR;
|
|
goto invalid;
|
|
} else if (snsid != dnsid) {
|
|
if (!NVME_ID_NS_DPS_TYPE(sns->id_ns.dps) &&
|
|
!NVME_ID_NS_DPS_TYPE(dns->id_ns.dps)) {
|
|
if (!nvme_copy_matching_ns_format(sns, dns, false)) {
|
|
status = NVME_CMD_INCOMP_NS_OR_FMT | NVME_DNR;
|
|
goto invalid;
|
|
}
|
|
}
|
|
if (NVME_ID_NS_DPS_TYPE(sns->id_ns.dps) &&
|
|
NVME_ID_NS_DPS_TYPE(dns->id_ns.dps)) {
|
|
if ((prinfor & NVME_PRINFO_PRACT) !=
|
|
(prinfow & NVME_PRINFO_PRACT)) {
|
|
status = NVME_CMD_INCOMP_NS_OR_FMT | NVME_DNR;
|
|
goto invalid;
|
|
} else {
|
|
if (!nvme_copy_matching_ns_format(sns, dns, true)) {
|
|
status = NVME_CMD_INCOMP_NS_OR_FMT | NVME_DNR;
|
|
goto invalid;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!NVME_ID_NS_DPS_TYPE(sns->id_ns.dps) &&
|
|
NVME_ID_NS_DPS_TYPE(dns->id_ns.dps)) {
|
|
if (!(prinfow & NVME_PRINFO_PRACT)) {
|
|
status = NVME_CMD_INCOMP_NS_OR_FMT | NVME_DNR;
|
|
goto invalid;
|
|
} else {
|
|
if (!nvme_copy_corresp_pi_format(sns, dns, false)) {
|
|
status = NVME_CMD_INCOMP_NS_OR_FMT | NVME_DNR;
|
|
goto invalid;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (NVME_ID_NS_DPS_TYPE(sns->id_ns.dps) &&
|
|
!NVME_ID_NS_DPS_TYPE(dns->id_ns.dps)) {
|
|
if (!(prinfor & NVME_PRINFO_PRACT)) {
|
|
status = NVME_CMD_INCOMP_NS_OR_FMT | NVME_DNR;
|
|
goto invalid;
|
|
} else {
|
|
if (!nvme_copy_corresp_pi_format(sns, dns, true)) {
|
|
status = NVME_CMD_INCOMP_NS_OR_FMT | NVME_DNR;
|
|
goto invalid;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
len = nvme_l2b(sns, nlb);
|
|
|
|
trace_pci_nvme_copy_source_range(slba, nlb);
|
|
|
|
if (nlb > le16_to_cpu(sns->id_ns.mssrl)) {
|
|
status = NVME_CMD_SIZE_LIMIT | NVME_DNR;
|
|
goto invalid;
|
|
}
|
|
|
|
status = nvme_check_bounds(sns, slba, nlb);
|
|
if (status) {
|
|
goto invalid;
|
|
}
|
|
|
|
if (NVME_ERR_REC_DULBE(sns->features.err_rec)) {
|
|
status = nvme_check_dulbe(sns, slba, nlb);
|
|
if (status) {
|
|
goto invalid;
|
|
}
|
|
}
|
|
|
|
if (sns->params.zoned) {
|
|
status = nvme_check_zone_read(sns, slba, nlb);
|
|
if (status) {
|
|
goto invalid;
|
|
}
|
|
}
|
|
|
|
g_free(iocb->bounce);
|
|
iocb->bounce = g_malloc_n(le16_to_cpu(sns->id_ns.mssrl),
|
|
sns->lbasz + sns->lbaf.ms);
|
|
|
|
qemu_iovec_reset(&iocb->iov);
|
|
qemu_iovec_add(&iocb->iov, iocb->bounce, len);
|
|
|
|
block_acct_start(blk_get_stats(sns->blkconf.blk), &iocb->acct.read, 0,
|
|
BLOCK_ACCT_READ);
|
|
|
|
iocb->aiocb = blk_aio_preadv(sns->blkconf.blk, nvme_l2b(sns, slba),
|
|
&iocb->iov, 0, nvme_copy_in_cb, iocb);
|
|
return;
|
|
|
|
invalid:
|
|
req->status = status;
|
|
iocb->ret = -1;
|
|
done:
|
|
nvme_copy_done(iocb);
|
|
}
|
|
|
|
static uint16_t nvme_copy(NvmeCtrl *n, NvmeRequest *req)
|
|
{
|
|
NvmeNamespace *ns = req->ns;
|
|
NvmeCopyCmd *copy = (NvmeCopyCmd *)&req->cmd;
|
|
NvmeCopyAIOCB *iocb = blk_aio_get(&nvme_copy_aiocb_info, ns->blkconf.blk,
|
|
nvme_misc_cb, req);
|
|
uint16_t nr = copy->nr + 1;
|
|
uint8_t format = copy->control[0] & 0xf;
|
|
size_t len = sizeof(NvmeCopySourceRangeFormat0_2);
|
|
|
|
uint16_t status;
|
|
|
|
trace_pci_nvme_copy(nvme_cid(req), nvme_nsid(ns), nr, format);
|
|
|
|
iocb->ranges = NULL;
|
|
iocb->zone = NULL;
|
|
|
|
if (!(n->id_ctrl.ocfs & (1 << format)) ||
|
|
((format == 2 || format == 3) &&
|
|
!(n->features.hbs.cdfe & (1 << format)))) {
|
|
trace_pci_nvme_err_copy_invalid_format(format);
|
|
status = NVME_INVALID_FIELD | NVME_DNR;
|
|
goto invalid;
|
|
}
|
|
|
|
if (nr > ns->id_ns.msrc + 1) {
|
|
status = NVME_CMD_SIZE_LIMIT | NVME_DNR;
|
|
goto invalid;
|
|
}
|
|
|
|
if ((ns->pif == 0x0 && (format != 0x0 && format != 0x2)) ||
|
|
(ns->pif != 0x0 && (format != 0x1 && format != 0x3))) {
|
|
status = NVME_INVALID_FORMAT | NVME_DNR;
|
|
goto invalid;
|
|
}
|
|
|
|
if (ns->pif) {
|
|
len = sizeof(NvmeCopySourceRangeFormat1_3);
|
|
}
|
|
|
|
iocb->format = format;
|
|
iocb->ranges = g_malloc_n(nr, len);
|
|
status = nvme_h2c(n, (uint8_t *)iocb->ranges, len * nr, req);
|
|
if (status) {
|
|
goto invalid;
|
|
}
|
|
|
|
iocb->slba = le64_to_cpu(copy->sdlba);
|
|
|
|
if (ns->params.zoned) {
|
|
iocb->zone = nvme_get_zone_by_slba(ns, iocb->slba);
|
|
if (!iocb->zone) {
|
|
status = NVME_LBA_RANGE | NVME_DNR;
|
|
goto invalid;
|
|
}
|
|
|
|
status = nvme_zrm_auto(n, ns, iocb->zone);
|
|
if (status) {
|
|
goto invalid;
|
|
}
|
|
}
|
|
|
|
status = nvme_check_copy_mcl(ns, iocb, nr);
|
|
if (status) {
|
|
goto invalid;
|
|
}
|
|
|
|
iocb->req = req;
|
|
iocb->ret = 0;
|
|
iocb->nr = nr;
|
|
iocb->idx = 0;
|
|
iocb->reftag = le32_to_cpu(copy->reftag);
|
|
iocb->reftag |= (uint64_t)le32_to_cpu(copy->cdw3) << 32;
|
|
|
|
qemu_iovec_init(&iocb->iov, 1);
|
|
|
|
req->aiocb = &iocb->common;
|
|
iocb->sns = req->ns;
|
|
iocb->n = n;
|
|
iocb->bounce = NULL;
|
|
nvme_do_copy(iocb);
|
|
|
|
return NVME_NO_COMPLETE;
|
|
|
|
invalid:
|
|
g_free(iocb->ranges);
|
|
qemu_aio_unref(iocb);
|
|
return status;
|
|
}
|
|
|
|
static uint16_t nvme_compare(NvmeCtrl *n, NvmeRequest *req)
|
|
{
|
|
NvmeRwCmd *rw = (NvmeRwCmd *)&req->cmd;
|
|
NvmeNamespace *ns = req->ns;
|
|
BlockBackend *blk = ns->blkconf.blk;
|
|
uint64_t slba = le64_to_cpu(rw->slba);
|
|
uint32_t nlb = le16_to_cpu(rw->nlb) + 1;
|
|
uint8_t prinfo = NVME_RW_PRINFO(le16_to_cpu(rw->control));
|
|
size_t data_len = nvme_l2b(ns, nlb);
|
|
size_t len = data_len;
|
|
int64_t offset = nvme_l2b(ns, slba);
|
|
struct nvme_compare_ctx *ctx = NULL;
|
|
uint16_t status;
|
|
|
|
trace_pci_nvme_compare(nvme_cid(req), nvme_nsid(ns), slba, nlb);
|
|
|
|
if (NVME_ID_NS_DPS_TYPE(ns->id_ns.dps) && (prinfo & NVME_PRINFO_PRACT)) {
|
|
return NVME_INVALID_PROT_INFO | NVME_DNR;
|
|
}
|
|
|
|
if (nvme_ns_ext(ns)) {
|
|
len += nvme_m2b(ns, nlb);
|
|
}
|
|
|
|
if (NVME_ID_CTRL_CTRATT_MEM(n->id_ctrl.ctratt)) {
|
|
status = nvme_check_mdts(n, data_len);
|
|
} else {
|
|
status = nvme_check_mdts(n, len);
|
|
}
|
|
if (status) {
|
|
return status;
|
|
}
|
|
|
|
status = nvme_check_bounds(ns, slba, nlb);
|
|
if (status) {
|
|
return status;
|
|
}
|
|
|
|
if (NVME_ERR_REC_DULBE(ns->features.err_rec)) {
|
|
status = nvme_check_dulbe(ns, slba, nlb);
|
|
if (status) {
|
|
return status;
|
|
}
|
|
}
|
|
|
|
status = nvme_map_dptr(n, &req->sg, len, &req->cmd);
|
|
if (status) {
|
|
return status;
|
|
}
|
|
|
|
ctx = g_new(struct nvme_compare_ctx, 1);
|
|
ctx->data.bounce = g_malloc(data_len);
|
|
|
|
req->opaque = ctx;
|
|
|
|
qemu_iovec_init(&ctx->data.iov, 1);
|
|
qemu_iovec_add(&ctx->data.iov, ctx->data.bounce, data_len);
|
|
|
|
block_acct_start(blk_get_stats(blk), &req->acct, data_len,
|
|
BLOCK_ACCT_READ);
|
|
req->aiocb = blk_aio_preadv(blk, offset, &ctx->data.iov, 0,
|
|
nvme_compare_data_cb, req);
|
|
|
|
return NVME_NO_COMPLETE;
|
|
}
|
|
|
|
typedef struct NvmeFlushAIOCB {
|
|
BlockAIOCB common;
|
|
BlockAIOCB *aiocb;
|
|
NvmeRequest *req;
|
|
int ret;
|
|
|
|
NvmeNamespace *ns;
|
|
uint32_t nsid;
|
|
bool broadcast;
|
|
} NvmeFlushAIOCB;
|
|
|
|
static void nvme_flush_cancel(BlockAIOCB *acb)
|
|
{
|
|
NvmeFlushAIOCB *iocb = container_of(acb, NvmeFlushAIOCB, common);
|
|
|
|
iocb->ret = -ECANCELED;
|
|
|
|
if (iocb->aiocb) {
|
|
blk_aio_cancel_async(iocb->aiocb);
|
|
iocb->aiocb = NULL;
|
|
}
|
|
}
|
|
|
|
static const AIOCBInfo nvme_flush_aiocb_info = {
|
|
.aiocb_size = sizeof(NvmeFlushAIOCB),
|
|
.cancel_async = nvme_flush_cancel,
|
|
};
|
|
|
|
static void nvme_do_flush(NvmeFlushAIOCB *iocb);
|
|
|
|
static void nvme_flush_ns_cb(void *opaque, int ret)
|
|
{
|
|
NvmeFlushAIOCB *iocb = opaque;
|
|
NvmeNamespace *ns = iocb->ns;
|
|
|
|
if (ret < 0) {
|
|
iocb->ret = ret;
|
|
goto out;
|
|
} else if (iocb->ret < 0) {
|
|
goto out;
|
|
}
|
|
|
|
if (ns) {
|
|
trace_pci_nvme_flush_ns(iocb->nsid);
|
|
|
|
iocb->ns = NULL;
|
|
iocb->aiocb = blk_aio_flush(ns->blkconf.blk, nvme_flush_ns_cb, iocb);
|
|
return;
|
|
}
|
|
|
|
out:
|
|
nvme_do_flush(iocb);
|
|
}
|
|
|
|
static void nvme_do_flush(NvmeFlushAIOCB *iocb)
|
|
{
|
|
NvmeRequest *req = iocb->req;
|
|
NvmeCtrl *n = nvme_ctrl(req);
|
|
int i;
|
|
|
|
if (iocb->ret < 0) {
|
|
goto done;
|
|
}
|
|
|
|
if (iocb->broadcast) {
|
|
for (i = iocb->nsid + 1; i <= NVME_MAX_NAMESPACES; i++) {
|
|
iocb->ns = nvme_ns(n, i);
|
|
if (iocb->ns) {
|
|
iocb->nsid = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!iocb->ns) {
|
|
goto done;
|
|
}
|
|
|
|
nvme_flush_ns_cb(iocb, 0);
|
|
return;
|
|
|
|
done:
|
|
iocb->common.cb(iocb->common.opaque, iocb->ret);
|
|
qemu_aio_unref(iocb);
|
|
}
|
|
|
|
static uint16_t nvme_flush(NvmeCtrl *n, NvmeRequest *req)
|
|
{
|
|
NvmeFlushAIOCB *iocb;
|
|
uint32_t nsid = le32_to_cpu(req->cmd.nsid);
|
|
uint16_t status;
|
|
|
|
iocb = qemu_aio_get(&nvme_flush_aiocb_info, NULL, nvme_misc_cb, req);
|
|
|
|
iocb->req = req;
|
|
iocb->ret = 0;
|
|
iocb->ns = NULL;
|
|
iocb->nsid = 0;
|
|
iocb->broadcast = (nsid == NVME_NSID_BROADCAST);
|
|
|
|
if (!iocb->broadcast) {
|
|
if (!nvme_nsid_valid(n, nsid)) {
|
|
status = NVME_INVALID_NSID | NVME_DNR;
|
|
goto out;
|
|
}
|
|
|
|
iocb->ns = nvme_ns(n, nsid);
|
|
if (!iocb->ns) {
|
|
status = NVME_INVALID_FIELD | NVME_DNR;
|
|
goto out;
|
|
}
|
|
|
|
iocb->nsid = nsid;
|
|
}
|
|
|
|
req->aiocb = &iocb->common;
|
|
nvme_do_flush(iocb);
|
|
|
|
return NVME_NO_COMPLETE;
|
|
|
|
out:
|
|
qemu_aio_unref(iocb);
|
|
|
|
return status;
|
|
}
|
|
|
|
static uint16_t nvme_read(NvmeCtrl *n, NvmeRequest *req)
|
|
{
|
|
NvmeRwCmd *rw = (NvmeRwCmd *)&req->cmd;
|
|
NvmeNamespace *ns = req->ns;
|
|
uint64_t slba = le64_to_cpu(rw->slba);
|
|
uint32_t nlb = (uint32_t)le16_to_cpu(rw->nlb) + 1;
|
|
uint8_t prinfo = NVME_RW_PRINFO(le16_to_cpu(rw->control));
|
|
uint64_t data_size = nvme_l2b(ns, nlb);
|
|
uint64_t mapped_size = data_size;
|
|
uint64_t data_offset;
|
|
BlockBackend *blk = ns->blkconf.blk;
|
|
uint16_t status;
|
|
|
|
if (nvme_ns_ext(ns) && !(NVME_ID_CTRL_CTRATT_MEM(n->id_ctrl.ctratt))) {
|
|
mapped_size += nvme_m2b(ns, nlb);
|
|
|
|
if (NVME_ID_NS_DPS_TYPE(ns->id_ns.dps)) {
|
|
bool pract = prinfo & NVME_PRINFO_PRACT;
|
|
|
|
if (pract && ns->lbaf.ms == nvme_pi_tuple_size(ns)) {
|
|
mapped_size = data_size;
|
|
}
|
|
}
|
|
}
|
|
|
|
trace_pci_nvme_read(nvme_cid(req), nvme_nsid(ns), nlb, mapped_size, slba);
|
|
|
|
status = nvme_check_mdts(n, mapped_size);
|
|
if (status) {
|
|
goto invalid;
|
|
}
|
|
|
|
status = nvme_check_bounds(ns, slba, nlb);
|
|
if (status) {
|
|
goto invalid;
|
|
}
|
|
|
|
if (ns->params.zoned) {
|
|
status = nvme_check_zone_read(ns, slba, nlb);
|
|
if (status) {
|
|
trace_pci_nvme_err_zone_read_not_ok(slba, nlb, status);
|
|
goto invalid;
|
|
}
|
|
}
|
|
|
|
if (NVME_ERR_REC_DULBE(ns->features.err_rec)) {
|
|
status = nvme_check_dulbe(ns, slba, nlb);
|
|
if (status) {
|
|
goto invalid;
|
|
}
|
|
}
|
|
|
|
if (NVME_ID_NS_DPS_TYPE(ns->id_ns.dps)) {
|
|
return nvme_dif_rw(n, req);
|
|
}
|
|
|
|
status = nvme_map_data(n, nlb, req);
|
|
if (status) {
|
|
goto invalid;
|
|
}
|
|
|
|
data_offset = nvme_l2b(ns, slba);
|
|
|
|
block_acct_start(blk_get_stats(blk), &req->acct, data_size,
|
|
BLOCK_ACCT_READ);
|
|
nvme_blk_read(blk, data_offset, BDRV_SECTOR_SIZE, nvme_rw_cb, req);
|
|
return NVME_NO_COMPLETE;
|
|
|
|
invalid:
|
|
block_acct_invalid(blk_get_stats(blk), BLOCK_ACCT_READ);
|
|
return status | NVME_DNR;
|
|
}
|
|
|
|
static void nvme_do_write_fdp(NvmeCtrl *n, NvmeRequest *req, uint64_t slba,
|
|
uint32_t nlb)
|
|
{
|
|
NvmeNamespace *ns = req->ns;
|
|
NvmeRwCmd *rw = (NvmeRwCmd *)&req->cmd;
|
|
uint64_t data_size = nvme_l2b(ns, nlb);
|
|
uint32_t dw12 = le32_to_cpu(req->cmd.cdw12);
|
|
uint8_t dtype = (dw12 >> 20) & 0xf;
|
|
uint16_t pid = le16_to_cpu(rw->dspec);
|
|
uint16_t ph, rg, ruhid;
|
|
NvmeReclaimUnit *ru;
|
|
|
|
if (dtype != NVME_DIRECTIVE_DATA_PLACEMENT ||
|
|
!nvme_parse_pid(ns, pid, &ph, &rg)) {
|
|
ph = 0;
|
|
rg = 0;
|
|
}
|
|
|
|
ruhid = ns->fdp.phs[ph];
|
|
ru = &ns->endgrp->fdp.ruhs[ruhid].rus[rg];
|
|
|
|
nvme_fdp_stat_inc(&ns->endgrp->fdp.hbmw, data_size);
|
|
nvme_fdp_stat_inc(&ns->endgrp->fdp.mbmw, data_size);
|
|
|
|
while (nlb) {
|
|
if (nlb < ru->ruamw) {
|
|
ru->ruamw -= nlb;
|
|
break;
|
|
}
|
|
|
|
nlb -= ru->ruamw;
|
|
nvme_update_ruh(n, ns, pid);
|
|
}
|
|
}
|
|
|
|
static uint16_t nvme_do_write(NvmeCtrl *n, NvmeRequest *req, bool append,
|
|
bool wrz)
|
|
{
|
|
NvmeRwCmd *rw = (NvmeRwCmd *)&req->cmd;
|
|
NvmeNamespace *ns = req->ns;
|
|
uint64_t slba = le64_to_cpu(rw->slba);
|
|
uint32_t nlb = (uint32_t)le16_to_cpu(rw->nlb) + 1;
|
|
uint16_t ctrl = le16_to_cpu(rw->control);
|
|
uint8_t prinfo = NVME_RW_PRINFO(ctrl);
|
|
uint64_t data_size = nvme_l2b(ns, nlb);
|
|
uint64_t mapped_size = data_size;
|
|
uint64_t data_offset;
|
|
NvmeZone *zone;
|
|
NvmeZonedResult *res = (NvmeZonedResult *)&req->cqe;
|
|
BlockBackend *blk = ns->blkconf.blk;
|
|
uint16_t status;
|
|
|
|
if (nvme_ns_ext(ns) && !(NVME_ID_CTRL_CTRATT_MEM(n->id_ctrl.ctratt))) {
|
|
mapped_size += nvme_m2b(ns, nlb);
|
|
|
|
if (NVME_ID_NS_DPS_TYPE(ns->id_ns.dps)) {
|
|
bool pract = prinfo & NVME_PRINFO_PRACT;
|
|
|
|
if (pract && ns->lbaf.ms == nvme_pi_tuple_size(ns)) {
|
|
mapped_size -= nvme_m2b(ns, nlb);
|
|
}
|
|
}
|
|
}
|
|
|
|
trace_pci_nvme_write(nvme_cid(req), nvme_io_opc_str(rw->opcode),
|
|
nvme_nsid(ns), nlb, mapped_size, slba);
|
|
|
|
if (!wrz) {
|
|
status = nvme_check_mdts(n, mapped_size);
|
|
if (status) {
|
|
goto invalid;
|
|
}
|
|
}
|
|
|
|
status = nvme_check_bounds(ns, slba, nlb);
|
|
if (status) {
|
|
goto invalid;
|
|
}
|
|
|
|
if (ns->params.zoned) {
|
|
zone = nvme_get_zone_by_slba(ns, slba);
|
|
assert(zone);
|
|
|
|
if (append) {
|
|
bool piremap = !!(ctrl & NVME_RW_PIREMAP);
|
|
|
|
if (unlikely(zone->d.za & NVME_ZA_ZRWA_VALID)) {
|
|
return NVME_INVALID_ZONE_OP | NVME_DNR;
|
|
}
|
|
|
|
if (unlikely(slba != zone->d.zslba)) {
|
|
trace_pci_nvme_err_append_not_at_start(slba, zone->d.zslba);
|
|
status = NVME_INVALID_FIELD;
|
|
goto invalid;
|
|
}
|
|
|
|
if (n->params.zasl &&
|
|
data_size > (uint64_t)n->page_size << n->params.zasl) {
|
|
trace_pci_nvme_err_zasl(data_size);
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
|
|
slba = zone->w_ptr;
|
|
rw->slba = cpu_to_le64(slba);
|
|
res->slba = cpu_to_le64(slba);
|
|
|
|
switch (NVME_ID_NS_DPS_TYPE(ns->id_ns.dps)) {
|
|
case NVME_ID_NS_DPS_TYPE_1:
|
|
if (!piremap) {
|
|
return NVME_INVALID_PROT_INFO | NVME_DNR;
|
|
}
|
|
|
|
/* fallthrough */
|
|
|
|
case NVME_ID_NS_DPS_TYPE_2:
|
|
if (piremap) {
|
|
uint32_t reftag = le32_to_cpu(rw->reftag);
|
|
rw->reftag = cpu_to_le32(reftag + (slba - zone->d.zslba));
|
|
}
|
|
|
|
break;
|
|
|
|
case NVME_ID_NS_DPS_TYPE_3:
|
|
if (piremap) {
|
|
return NVME_INVALID_PROT_INFO | NVME_DNR;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
status = nvme_check_zone_write(ns, zone, slba, nlb);
|
|
if (status) {
|
|
goto invalid;
|
|
}
|
|
|
|
status = nvme_zrm_auto(n, ns, zone);
|
|
if (status) {
|
|
goto invalid;
|
|
}
|
|
|
|
if (!(zone->d.za & NVME_ZA_ZRWA_VALID)) {
|
|
zone->w_ptr += nlb;
|
|
}
|
|
} else if (ns->endgrp && ns->endgrp->fdp.enabled) {
|
|
nvme_do_write_fdp(n, req, slba, nlb);
|
|
}
|
|
|
|
data_offset = nvme_l2b(ns, slba);
|
|
|
|
if (NVME_ID_NS_DPS_TYPE(ns->id_ns.dps)) {
|
|
return nvme_dif_rw(n, req);
|
|
}
|
|
|
|
if (!wrz) {
|
|
status = nvme_map_data(n, nlb, req);
|
|
if (status) {
|
|
goto invalid;
|
|
}
|
|
|
|
block_acct_start(blk_get_stats(blk), &req->acct, data_size,
|
|
BLOCK_ACCT_WRITE);
|
|
nvme_blk_write(blk, data_offset, BDRV_SECTOR_SIZE, nvme_rw_cb, req);
|
|
} else {
|
|
req->aiocb = blk_aio_pwrite_zeroes(blk, data_offset, data_size,
|
|
BDRV_REQ_MAY_UNMAP, nvme_rw_cb,
|
|
req);
|
|
}
|
|
|
|
return NVME_NO_COMPLETE;
|
|
|
|
invalid:
|
|
block_acct_invalid(blk_get_stats(blk), BLOCK_ACCT_WRITE);
|
|
return status | NVME_DNR;
|
|
}
|
|
|
|
static inline uint16_t nvme_write(NvmeCtrl *n, NvmeRequest *req)
|
|
{
|
|
return nvme_do_write(n, req, false, false);
|
|
}
|
|
|
|
static inline uint16_t nvme_write_zeroes(NvmeCtrl *n, NvmeRequest *req)
|
|
{
|
|
return nvme_do_write(n, req, false, true);
|
|
}
|
|
|
|
static inline uint16_t nvme_zone_append(NvmeCtrl *n, NvmeRequest *req)
|
|
{
|
|
return nvme_do_write(n, req, true, false);
|
|
}
|
|
|
|
static uint16_t nvme_get_mgmt_zone_slba_idx(NvmeNamespace *ns, NvmeCmd *c,
|
|
uint64_t *slba, uint32_t *zone_idx)
|
|
{
|
|
uint32_t dw10 = le32_to_cpu(c->cdw10);
|
|
uint32_t dw11 = le32_to_cpu(c->cdw11);
|
|
|
|
if (!ns->params.zoned) {
|
|
trace_pci_nvme_err_invalid_opc(c->opcode);
|
|
return NVME_INVALID_OPCODE | NVME_DNR;
|
|
}
|
|
|
|
*slba = ((uint64_t)dw11) << 32 | dw10;
|
|
if (unlikely(*slba >= ns->id_ns.nsze)) {
|
|
trace_pci_nvme_err_invalid_lba_range(*slba, 0, ns->id_ns.nsze);
|
|
*slba = 0;
|
|
return NVME_LBA_RANGE | NVME_DNR;
|
|
}
|
|
|
|
*zone_idx = nvme_zone_idx(ns, *slba);
|
|
assert(*zone_idx < ns->num_zones);
|
|
|
|
return NVME_SUCCESS;
|
|
}
|
|
|
|
typedef uint16_t (*op_handler_t)(NvmeNamespace *, NvmeZone *, NvmeZoneState,
|
|
NvmeRequest *);
|
|
|
|
enum NvmeZoneProcessingMask {
|
|
NVME_PROC_CURRENT_ZONE = 0,
|
|
NVME_PROC_OPENED_ZONES = 1 << 0,
|
|
NVME_PROC_CLOSED_ZONES = 1 << 1,
|
|
NVME_PROC_READ_ONLY_ZONES = 1 << 2,
|
|
NVME_PROC_FULL_ZONES = 1 << 3,
|
|
};
|
|
|
|
static uint16_t nvme_open_zone(NvmeNamespace *ns, NvmeZone *zone,
|
|
NvmeZoneState state, NvmeRequest *req)
|
|
{
|
|
NvmeZoneSendCmd *cmd = (NvmeZoneSendCmd *)&req->cmd;
|
|
int flags = 0;
|
|
|
|
if (cmd->zsflags & NVME_ZSFLAG_ZRWA_ALLOC) {
|
|
uint16_t ozcs = le16_to_cpu(ns->id_ns_zoned->ozcs);
|
|
|
|
if (!(ozcs & NVME_ID_NS_ZONED_OZCS_ZRWASUP)) {
|
|
return NVME_INVALID_ZONE_OP | NVME_DNR;
|
|
}
|
|
|
|
if (zone->w_ptr % ns->zns.zrwafg) {
|
|
return NVME_NOZRWA | NVME_DNR;
|
|
}
|
|
|
|
flags = NVME_ZRM_ZRWA;
|
|
}
|
|
|
|
return nvme_zrm_open_flags(nvme_ctrl(req), ns, zone, flags);
|
|
}
|
|
|
|
static uint16_t nvme_close_zone(NvmeNamespace *ns, NvmeZone *zone,
|
|
NvmeZoneState state, NvmeRequest *req)
|
|
{
|
|
return nvme_zrm_close(ns, zone);
|
|
}
|
|
|
|
static uint16_t nvme_finish_zone(NvmeNamespace *ns, NvmeZone *zone,
|
|
NvmeZoneState state, NvmeRequest *req)
|
|
{
|
|
return nvme_zrm_finish(ns, zone);
|
|
}
|
|
|
|
static uint16_t nvme_offline_zone(NvmeNamespace *ns, NvmeZone *zone,
|
|
NvmeZoneState state, NvmeRequest *req)
|
|
{
|
|
switch (state) {
|
|
case NVME_ZONE_STATE_READ_ONLY:
|
|
nvme_assign_zone_state(ns, zone, NVME_ZONE_STATE_OFFLINE);
|
|
/* fall through */
|
|
case NVME_ZONE_STATE_OFFLINE:
|
|
return NVME_SUCCESS;
|
|
default:
|
|
return NVME_ZONE_INVAL_TRANSITION;
|
|
}
|
|
}
|
|
|
|
static uint16_t nvme_set_zd_ext(NvmeNamespace *ns, NvmeZone *zone)
|
|
{
|
|
uint16_t status;
|
|
uint8_t state = nvme_get_zone_state(zone);
|
|
|
|
if (state == NVME_ZONE_STATE_EMPTY) {
|
|
status = nvme_aor_check(ns, 1, 0);
|
|
if (status) {
|
|
return status;
|
|
}
|
|
nvme_aor_inc_active(ns);
|
|
zone->d.za |= NVME_ZA_ZD_EXT_VALID;
|
|
nvme_assign_zone_state(ns, zone, NVME_ZONE_STATE_CLOSED);
|
|
return NVME_SUCCESS;
|
|
}
|
|
|
|
return NVME_ZONE_INVAL_TRANSITION;
|
|
}
|
|
|
|
static uint16_t nvme_bulk_proc_zone(NvmeNamespace *ns, NvmeZone *zone,
|
|
enum NvmeZoneProcessingMask proc_mask,
|
|
op_handler_t op_hndlr, NvmeRequest *req)
|
|
{
|
|
uint16_t status = NVME_SUCCESS;
|
|
NvmeZoneState zs = nvme_get_zone_state(zone);
|
|
bool proc_zone;
|
|
|
|
switch (zs) {
|
|
case NVME_ZONE_STATE_IMPLICITLY_OPEN:
|
|
case NVME_ZONE_STATE_EXPLICITLY_OPEN:
|
|
proc_zone = proc_mask & NVME_PROC_OPENED_ZONES;
|
|
break;
|
|
case NVME_ZONE_STATE_CLOSED:
|
|
proc_zone = proc_mask & NVME_PROC_CLOSED_ZONES;
|
|
break;
|
|
case NVME_ZONE_STATE_READ_ONLY:
|
|
proc_zone = proc_mask & NVME_PROC_READ_ONLY_ZONES;
|
|
break;
|
|
case NVME_ZONE_STATE_FULL:
|
|
proc_zone = proc_mask & NVME_PROC_FULL_ZONES;
|
|
break;
|
|
default:
|
|
proc_zone = false;
|
|
}
|
|
|
|
if (proc_zone) {
|
|
status = op_hndlr(ns, zone, zs, req);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
static uint16_t nvme_do_zone_op(NvmeNamespace *ns, NvmeZone *zone,
|
|
enum NvmeZoneProcessingMask proc_mask,
|
|
op_handler_t op_hndlr, NvmeRequest *req)
|
|
{
|
|
NvmeZone *next;
|
|
uint16_t status = NVME_SUCCESS;
|
|
int i;
|
|
|
|
if (!proc_mask) {
|
|
status = op_hndlr(ns, zone, nvme_get_zone_state(zone), req);
|
|
} else {
|
|
if (proc_mask & NVME_PROC_CLOSED_ZONES) {
|
|
QTAILQ_FOREACH_SAFE(zone, &ns->closed_zones, entry, next) {
|
|
status = nvme_bulk_proc_zone(ns, zone, proc_mask, op_hndlr,
|
|
req);
|
|
if (status && status != NVME_NO_COMPLETE) {
|
|
goto out;
|
|
}
|
|
}
|
|
}
|
|
if (proc_mask & NVME_PROC_OPENED_ZONES) {
|
|
QTAILQ_FOREACH_SAFE(zone, &ns->imp_open_zones, entry, next) {
|
|
status = nvme_bulk_proc_zone(ns, zone, proc_mask, op_hndlr,
|
|
req);
|
|
if (status && status != NVME_NO_COMPLETE) {
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
QTAILQ_FOREACH_SAFE(zone, &ns->exp_open_zones, entry, next) {
|
|
status = nvme_bulk_proc_zone(ns, zone, proc_mask, op_hndlr,
|
|
req);
|
|
if (status && status != NVME_NO_COMPLETE) {
|
|
goto out;
|
|
}
|
|
}
|
|
}
|
|
if (proc_mask & NVME_PROC_FULL_ZONES) {
|
|
QTAILQ_FOREACH_SAFE(zone, &ns->full_zones, entry, next) {
|
|
status = nvme_bulk_proc_zone(ns, zone, proc_mask, op_hndlr,
|
|
req);
|
|
if (status && status != NVME_NO_COMPLETE) {
|
|
goto out;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (proc_mask & NVME_PROC_READ_ONLY_ZONES) {
|
|
for (i = 0; i < ns->num_zones; i++, zone++) {
|
|
status = nvme_bulk_proc_zone(ns, zone, proc_mask, op_hndlr,
|
|
req);
|
|
if (status && status != NVME_NO_COMPLETE) {
|
|
goto out;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
out:
|
|
return status;
|
|
}
|
|
|
|
typedef struct NvmeZoneResetAIOCB {
|
|
BlockAIOCB common;
|
|
BlockAIOCB *aiocb;
|
|
NvmeRequest *req;
|
|
int ret;
|
|
|
|
bool all;
|
|
int idx;
|
|
NvmeZone *zone;
|
|
} NvmeZoneResetAIOCB;
|
|
|
|
static void nvme_zone_reset_cancel(BlockAIOCB *aiocb)
|
|
{
|
|
NvmeZoneResetAIOCB *iocb = container_of(aiocb, NvmeZoneResetAIOCB, common);
|
|
NvmeRequest *req = iocb->req;
|
|
NvmeNamespace *ns = req->ns;
|
|
|
|
iocb->idx = ns->num_zones;
|
|
|
|
iocb->ret = -ECANCELED;
|
|
|
|
if (iocb->aiocb) {
|
|
blk_aio_cancel_async(iocb->aiocb);
|
|
iocb->aiocb = NULL;
|
|
}
|
|
}
|
|
|
|
static const AIOCBInfo nvme_zone_reset_aiocb_info = {
|
|
.aiocb_size = sizeof(NvmeZoneResetAIOCB),
|
|
.cancel_async = nvme_zone_reset_cancel,
|
|
};
|
|
|
|
static void nvme_zone_reset_cb(void *opaque, int ret);
|
|
|
|
static void nvme_zone_reset_epilogue_cb(void *opaque, int ret)
|
|
{
|
|
NvmeZoneResetAIOCB *iocb = opaque;
|
|
NvmeRequest *req = iocb->req;
|
|
NvmeNamespace *ns = req->ns;
|
|
int64_t moff;
|
|
int count;
|
|
|
|
if (ret < 0 || iocb->ret < 0 || !ns->lbaf.ms) {
|
|
goto out;
|
|
}
|
|
|
|
moff = nvme_moff(ns, iocb->zone->d.zslba);
|
|
count = nvme_m2b(ns, ns->zone_size);
|
|
|
|
iocb->aiocb = blk_aio_pwrite_zeroes(ns->blkconf.blk, moff, count,
|
|
BDRV_REQ_MAY_UNMAP,
|
|
nvme_zone_reset_cb, iocb);
|
|
return;
|
|
|
|
out:
|
|
nvme_zone_reset_cb(iocb, ret);
|
|
}
|
|
|
|
static void nvme_zone_reset_cb(void *opaque, int ret)
|
|
{
|
|
NvmeZoneResetAIOCB *iocb = opaque;
|
|
NvmeRequest *req = iocb->req;
|
|
NvmeNamespace *ns = req->ns;
|
|
|
|
if (iocb->ret < 0) {
|
|
goto done;
|
|
} else if (ret < 0) {
|
|
iocb->ret = ret;
|
|
goto done;
|
|
}
|
|
|
|
if (iocb->zone) {
|
|
nvme_zrm_reset(ns, iocb->zone);
|
|
|
|
if (!iocb->all) {
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
while (iocb->idx < ns->num_zones) {
|
|
NvmeZone *zone = &ns->zone_array[iocb->idx++];
|
|
|
|
switch (nvme_get_zone_state(zone)) {
|
|
case NVME_ZONE_STATE_EMPTY:
|
|
if (!iocb->all) {
|
|
goto done;
|
|
}
|
|
|
|
continue;
|
|
|
|
case NVME_ZONE_STATE_EXPLICITLY_OPEN:
|
|
case NVME_ZONE_STATE_IMPLICITLY_OPEN:
|
|
case NVME_ZONE_STATE_CLOSED:
|
|
case NVME_ZONE_STATE_FULL:
|
|
iocb->zone = zone;
|
|
break;
|
|
|
|
default:
|
|
continue;
|
|
}
|
|
|
|
trace_pci_nvme_zns_zone_reset(zone->d.zslba);
|
|
|
|
iocb->aiocb = blk_aio_pwrite_zeroes(ns->blkconf.blk,
|
|
nvme_l2b(ns, zone->d.zslba),
|
|
nvme_l2b(ns, ns->zone_size),
|
|
BDRV_REQ_MAY_UNMAP,
|
|
nvme_zone_reset_epilogue_cb,
|
|
iocb);
|
|
return;
|
|
}
|
|
|
|
done:
|
|
iocb->aiocb = NULL;
|
|
|
|
iocb->common.cb(iocb->common.opaque, iocb->ret);
|
|
qemu_aio_unref(iocb);
|
|
}
|
|
|
|
static uint16_t nvme_zone_mgmt_send_zrwa_flush(NvmeCtrl *n, NvmeZone *zone,
|
|
uint64_t elba, NvmeRequest *req)
|
|
{
|
|
NvmeNamespace *ns = req->ns;
|
|
uint16_t ozcs = le16_to_cpu(ns->id_ns_zoned->ozcs);
|
|
uint64_t wp = zone->d.wp;
|
|
uint32_t nlb = elba - wp + 1;
|
|
uint16_t status;
|
|
|
|
|
|
if (!(ozcs & NVME_ID_NS_ZONED_OZCS_ZRWASUP)) {
|
|
return NVME_INVALID_ZONE_OP | NVME_DNR;
|
|
}
|
|
|
|
if (!(zone->d.za & NVME_ZA_ZRWA_VALID)) {
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
|
|
if (elba < wp || elba > wp + ns->zns.zrwas) {
|
|
return NVME_ZONE_BOUNDARY_ERROR | NVME_DNR;
|
|
}
|
|
|
|
if (nlb % ns->zns.zrwafg) {
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
|
|
status = nvme_zrm_auto(n, ns, zone);
|
|
if (status) {
|
|
return status;
|
|
}
|
|
|
|
zone->w_ptr += nlb;
|
|
|
|
nvme_advance_zone_wp(ns, zone, nlb);
|
|
|
|
return NVME_SUCCESS;
|
|
}
|
|
|
|
static uint16_t nvme_zone_mgmt_send(NvmeCtrl *n, NvmeRequest *req)
|
|
{
|
|
NvmeZoneSendCmd *cmd = (NvmeZoneSendCmd *)&req->cmd;
|
|
NvmeNamespace *ns = req->ns;
|
|
NvmeZone *zone;
|
|
NvmeZoneResetAIOCB *iocb;
|
|
uint8_t *zd_ext;
|
|
uint64_t slba = 0;
|
|
uint32_t zone_idx = 0;
|
|
uint16_t status;
|
|
uint8_t action = cmd->zsa;
|
|
bool all;
|
|
enum NvmeZoneProcessingMask proc_mask = NVME_PROC_CURRENT_ZONE;
|
|
|
|
all = cmd->zsflags & NVME_ZSFLAG_SELECT_ALL;
|
|
|
|
req->status = NVME_SUCCESS;
|
|
|
|
if (!all) {
|
|
status = nvme_get_mgmt_zone_slba_idx(ns, &req->cmd, &slba, &zone_idx);
|
|
if (status) {
|
|
return status;
|
|
}
|
|
}
|
|
|
|
zone = &ns->zone_array[zone_idx];
|
|
if (slba != zone->d.zslba && action != NVME_ZONE_ACTION_ZRWA_FLUSH) {
|
|
trace_pci_nvme_err_unaligned_zone_cmd(action, slba, zone->d.zslba);
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
|
|
switch (action) {
|
|
|
|
case NVME_ZONE_ACTION_OPEN:
|
|
if (all) {
|
|
proc_mask = NVME_PROC_CLOSED_ZONES;
|
|
}
|
|
trace_pci_nvme_open_zone(slba, zone_idx, all);
|
|
status = nvme_do_zone_op(ns, zone, proc_mask, nvme_open_zone, req);
|
|
break;
|
|
|
|
case NVME_ZONE_ACTION_CLOSE:
|
|
if (all) {
|
|
proc_mask = NVME_PROC_OPENED_ZONES;
|
|
}
|
|
trace_pci_nvme_close_zone(slba, zone_idx, all);
|
|
status = nvme_do_zone_op(ns, zone, proc_mask, nvme_close_zone, req);
|
|
break;
|
|
|
|
case NVME_ZONE_ACTION_FINISH:
|
|
if (all) {
|
|
proc_mask = NVME_PROC_OPENED_ZONES | NVME_PROC_CLOSED_ZONES;
|
|
}
|
|
trace_pci_nvme_finish_zone(slba, zone_idx, all);
|
|
status = nvme_do_zone_op(ns, zone, proc_mask, nvme_finish_zone, req);
|
|
break;
|
|
|
|
case NVME_ZONE_ACTION_RESET:
|
|
trace_pci_nvme_reset_zone(slba, zone_idx, all);
|
|
|
|
iocb = blk_aio_get(&nvme_zone_reset_aiocb_info, ns->blkconf.blk,
|
|
nvme_misc_cb, req);
|
|
|
|
iocb->req = req;
|
|
iocb->ret = 0;
|
|
iocb->all = all;
|
|
iocb->idx = zone_idx;
|
|
iocb->zone = NULL;
|
|
|
|
req->aiocb = &iocb->common;
|
|
nvme_zone_reset_cb(iocb, 0);
|
|
|
|
return NVME_NO_COMPLETE;
|
|
|
|
case NVME_ZONE_ACTION_OFFLINE:
|
|
if (all) {
|
|
proc_mask = NVME_PROC_READ_ONLY_ZONES;
|
|
}
|
|
trace_pci_nvme_offline_zone(slba, zone_idx, all);
|
|
status = nvme_do_zone_op(ns, zone, proc_mask, nvme_offline_zone, req);
|
|
break;
|
|
|
|
case NVME_ZONE_ACTION_SET_ZD_EXT:
|
|
trace_pci_nvme_set_descriptor_extension(slba, zone_idx);
|
|
if (all || !ns->params.zd_extension_size) {
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
zd_ext = nvme_get_zd_extension(ns, zone_idx);
|
|
status = nvme_h2c(n, zd_ext, ns->params.zd_extension_size, req);
|
|
if (status) {
|
|
trace_pci_nvme_err_zd_extension_map_error(zone_idx);
|
|
return status;
|
|
}
|
|
|
|
status = nvme_set_zd_ext(ns, zone);
|
|
if (status == NVME_SUCCESS) {
|
|
trace_pci_nvme_zd_extension_set(zone_idx);
|
|
return status;
|
|
}
|
|
break;
|
|
|
|
case NVME_ZONE_ACTION_ZRWA_FLUSH:
|
|
if (all) {
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
|
|
return nvme_zone_mgmt_send_zrwa_flush(n, zone, slba, req);
|
|
|
|
default:
|
|
trace_pci_nvme_err_invalid_mgmt_action(action);
|
|
status = NVME_INVALID_FIELD;
|
|
}
|
|
|
|
if (status == NVME_ZONE_INVAL_TRANSITION) {
|
|
trace_pci_nvme_err_invalid_zone_state_transition(action, slba,
|
|
zone->d.za);
|
|
}
|
|
if (status) {
|
|
status |= NVME_DNR;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
static bool nvme_zone_matches_filter(uint32_t zafs, NvmeZone *zl)
|
|
{
|
|
NvmeZoneState zs = nvme_get_zone_state(zl);
|
|
|
|
switch (zafs) {
|
|
case NVME_ZONE_REPORT_ALL:
|
|
return true;
|
|
case NVME_ZONE_REPORT_EMPTY:
|
|
return zs == NVME_ZONE_STATE_EMPTY;
|
|
case NVME_ZONE_REPORT_IMPLICITLY_OPEN:
|
|
return zs == NVME_ZONE_STATE_IMPLICITLY_OPEN;
|
|
case NVME_ZONE_REPORT_EXPLICITLY_OPEN:
|
|
return zs == NVME_ZONE_STATE_EXPLICITLY_OPEN;
|
|
case NVME_ZONE_REPORT_CLOSED:
|
|
return zs == NVME_ZONE_STATE_CLOSED;
|
|
case NVME_ZONE_REPORT_FULL:
|
|
return zs == NVME_ZONE_STATE_FULL;
|
|
case NVME_ZONE_REPORT_READ_ONLY:
|
|
return zs == NVME_ZONE_STATE_READ_ONLY;
|
|
case NVME_ZONE_REPORT_OFFLINE:
|
|
return zs == NVME_ZONE_STATE_OFFLINE;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static uint16_t nvme_zone_mgmt_recv(NvmeCtrl *n, NvmeRequest *req)
|
|
{
|
|
NvmeCmd *cmd = &req->cmd;
|
|
NvmeNamespace *ns = req->ns;
|
|
/* cdw12 is zero-based number of dwords to return. Convert to bytes */
|
|
uint32_t data_size = (le32_to_cpu(cmd->cdw12) + 1) << 2;
|
|
uint32_t dw13 = le32_to_cpu(cmd->cdw13);
|
|
uint32_t zone_idx, zra, zrasf, partial;
|
|
uint64_t max_zones, nr_zones = 0;
|
|
uint16_t status;
|
|
uint64_t slba;
|
|
NvmeZoneDescr *z;
|
|
NvmeZone *zone;
|
|
NvmeZoneReportHeader *header;
|
|
void *buf, *buf_p;
|
|
size_t zone_entry_sz;
|
|
int i;
|
|
|
|
req->status = NVME_SUCCESS;
|
|
|
|
status = nvme_get_mgmt_zone_slba_idx(ns, cmd, &slba, &zone_idx);
|
|
if (status) {
|
|
return status;
|
|
}
|
|
|
|
zra = dw13 & 0xff;
|
|
if (zra != NVME_ZONE_REPORT && zra != NVME_ZONE_REPORT_EXTENDED) {
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
if (zra == NVME_ZONE_REPORT_EXTENDED && !ns->params.zd_extension_size) {
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
|
|
zrasf = (dw13 >> 8) & 0xff;
|
|
if (zrasf > NVME_ZONE_REPORT_OFFLINE) {
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
|
|
if (data_size < sizeof(NvmeZoneReportHeader)) {
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
|
|
status = nvme_check_mdts(n, data_size);
|
|
if (status) {
|
|
return status;
|
|
}
|
|
|
|
partial = (dw13 >> 16) & 0x01;
|
|
|
|
zone_entry_sz = sizeof(NvmeZoneDescr);
|
|
if (zra == NVME_ZONE_REPORT_EXTENDED) {
|
|
zone_entry_sz += ns->params.zd_extension_size;
|
|
}
|
|
|
|
max_zones = (data_size - sizeof(NvmeZoneReportHeader)) / zone_entry_sz;
|
|
buf = g_malloc0(data_size);
|
|
|
|
zone = &ns->zone_array[zone_idx];
|
|
for (i = zone_idx; i < ns->num_zones; i++) {
|
|
if (partial && nr_zones >= max_zones) {
|
|
break;
|
|
}
|
|
if (nvme_zone_matches_filter(zrasf, zone++)) {
|
|
nr_zones++;
|
|
}
|
|
}
|
|
header = buf;
|
|
header->nr_zones = cpu_to_le64(nr_zones);
|
|
|
|
buf_p = buf + sizeof(NvmeZoneReportHeader);
|
|
for (; zone_idx < ns->num_zones && max_zones > 0; zone_idx++) {
|
|
zone = &ns->zone_array[zone_idx];
|
|
if (nvme_zone_matches_filter(zrasf, zone)) {
|
|
z = buf_p;
|
|
buf_p += sizeof(NvmeZoneDescr);
|
|
|
|
z->zt = zone->d.zt;
|
|
z->zs = zone->d.zs;
|
|
z->zcap = cpu_to_le64(zone->d.zcap);
|
|
z->zslba = cpu_to_le64(zone->d.zslba);
|
|
z->za = zone->d.za;
|
|
|
|
if (nvme_wp_is_valid(zone)) {
|
|
z->wp = cpu_to_le64(zone->d.wp);
|
|
} else {
|
|
z->wp = cpu_to_le64(~0ULL);
|
|
}
|
|
|
|
if (zra == NVME_ZONE_REPORT_EXTENDED) {
|
|
if (zone->d.za & NVME_ZA_ZD_EXT_VALID) {
|
|
memcpy(buf_p, nvme_get_zd_extension(ns, zone_idx),
|
|
ns->params.zd_extension_size);
|
|
}
|
|
buf_p += ns->params.zd_extension_size;
|
|
}
|
|
|
|
max_zones--;
|
|
}
|
|
}
|
|
|
|
status = nvme_c2h(n, (uint8_t *)buf, data_size, req);
|
|
|
|
g_free(buf);
|
|
|
|
return status;
|
|
}
|
|
|
|
static uint16_t nvme_io_mgmt_recv_ruhs(NvmeCtrl *n, NvmeRequest *req,
|
|
size_t len)
|
|
{
|
|
NvmeNamespace *ns = req->ns;
|
|
NvmeEnduranceGroup *endgrp;
|
|
NvmeRuhStatus *hdr;
|
|
NvmeRuhStatusDescr *ruhsd;
|
|
unsigned int nruhsd;
|
|
uint16_t rg, ph, *ruhid;
|
|
size_t trans_len;
|
|
g_autofree uint8_t *buf = NULL;
|
|
|
|
if (!n->subsys) {
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
|
|
if (ns->params.nsid == 0 || ns->params.nsid == 0xffffffff) {
|
|
return NVME_INVALID_NSID | NVME_DNR;
|
|
}
|
|
|
|
if (!n->subsys->endgrp.fdp.enabled) {
|
|
return NVME_FDP_DISABLED | NVME_DNR;
|
|
}
|
|
|
|
endgrp = ns->endgrp;
|
|
|
|
nruhsd = ns->fdp.nphs * endgrp->fdp.nrg;
|
|
trans_len = sizeof(NvmeRuhStatus) + nruhsd * sizeof(NvmeRuhStatusDescr);
|
|
buf = g_malloc0(trans_len);
|
|
|
|
trans_len = MIN(trans_len, len);
|
|
|
|
hdr = (NvmeRuhStatus *)buf;
|
|
ruhsd = (NvmeRuhStatusDescr *)(buf + sizeof(NvmeRuhStatus));
|
|
|
|
hdr->nruhsd = cpu_to_le16(nruhsd);
|
|
|
|
ruhid = ns->fdp.phs;
|
|
|
|
for (ph = 0; ph < ns->fdp.nphs; ph++, ruhid++) {
|
|
NvmeRuHandle *ruh = &endgrp->fdp.ruhs[*ruhid];
|
|
|
|
for (rg = 0; rg < endgrp->fdp.nrg; rg++, ruhsd++) {
|
|
uint16_t pid = nvme_make_pid(ns, rg, ph);
|
|
|
|
ruhsd->pid = cpu_to_le16(pid);
|
|
ruhsd->ruhid = *ruhid;
|
|
ruhsd->earutr = 0;
|
|
ruhsd->ruamw = cpu_to_le64(ruh->rus[rg].ruamw);
|
|
}
|
|
}
|
|
|
|
return nvme_c2h(n, buf, trans_len, req);
|
|
}
|
|
|
|
static uint16_t nvme_io_mgmt_recv(NvmeCtrl *n, NvmeRequest *req)
|
|
{
|
|
NvmeCmd *cmd = &req->cmd;
|
|
uint32_t cdw10 = le32_to_cpu(cmd->cdw10);
|
|
uint32_t numd = le32_to_cpu(cmd->cdw11);
|
|
uint8_t mo = (cdw10 & 0xff);
|
|
size_t len = (numd + 1) << 2;
|
|
|
|
switch (mo) {
|
|
case NVME_IOMR_MO_NOP:
|
|
return 0;
|
|
case NVME_IOMR_MO_RUH_STATUS:
|
|
return nvme_io_mgmt_recv_ruhs(n, req, len);
|
|
default:
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
};
|
|
}
|
|
|
|
static uint16_t nvme_io_mgmt_send_ruh_update(NvmeCtrl *n, NvmeRequest *req)
|
|
{
|
|
NvmeCmd *cmd = &req->cmd;
|
|
NvmeNamespace *ns = req->ns;
|
|
uint32_t cdw10 = le32_to_cpu(cmd->cdw10);
|
|
uint16_t ret = NVME_SUCCESS;
|
|
uint32_t npid = (cdw10 >> 16) + 1;
|
|
unsigned int i = 0;
|
|
g_autofree uint16_t *pids = NULL;
|
|
uint32_t maxnpid;
|
|
|
|
if (!ns->endgrp || !ns->endgrp->fdp.enabled) {
|
|
return NVME_FDP_DISABLED | NVME_DNR;
|
|
}
|
|
|
|
maxnpid = n->subsys->endgrp.fdp.nrg * n->subsys->endgrp.fdp.nruh;
|
|
|
|
if (unlikely(npid >= MIN(NVME_FDP_MAXPIDS, maxnpid))) {
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
|
|
pids = g_new(uint16_t, npid);
|
|
|
|
ret = nvme_h2c(n, pids, npid * sizeof(uint16_t), req);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
for (; i < npid; i++) {
|
|
if (!nvme_update_ruh(n, ns, pids[i])) {
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static uint16_t nvme_io_mgmt_send(NvmeCtrl *n, NvmeRequest *req)
|
|
{
|
|
NvmeCmd *cmd = &req->cmd;
|
|
uint32_t cdw10 = le32_to_cpu(cmd->cdw10);
|
|
uint8_t mo = (cdw10 & 0xff);
|
|
|
|
switch (mo) {
|
|
case NVME_IOMS_MO_NOP:
|
|
return 0;
|
|
case NVME_IOMS_MO_RUH_UPDATE:
|
|
return nvme_io_mgmt_send_ruh_update(n, req);
|
|
default:
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
};
|
|
}
|
|
|
|
static uint16_t nvme_io_cmd(NvmeCtrl *n, NvmeRequest *req)
|
|
{
|
|
NvmeNamespace *ns;
|
|
uint32_t nsid = le32_to_cpu(req->cmd.nsid);
|
|
|
|
trace_pci_nvme_io_cmd(nvme_cid(req), nsid, nvme_sqid(req),
|
|
req->cmd.opcode, nvme_io_opc_str(req->cmd.opcode));
|
|
|
|
/*
|
|
* In the base NVM command set, Flush may apply to all namespaces
|
|
* (indicated by NSID being set to FFFFFFFFh). But if that feature is used
|
|
* along with TP 4056 (Namespace Types), it may be pretty screwed up.
|
|
*
|
|
* If NSID is indeed set to FFFFFFFFh, we simply cannot associate the
|
|
* opcode with a specific command since we cannot determine a unique I/O
|
|
* command set. Opcode 0h could have any other meaning than something
|
|
* equivalent to flushing and say it DOES have completely different
|
|
* semantics in some other command set - does an NSID of FFFFFFFFh then
|
|
* mean "for all namespaces, apply whatever command set specific command
|
|
* that uses the 0h opcode?" Or does it mean "for all namespaces, apply
|
|
* whatever command that uses the 0h opcode if, and only if, it allows NSID
|
|
* to be FFFFFFFFh"?
|
|
*
|
|
* Anyway (and luckily), for now, we do not care about this since the
|
|
* device only supports namespace types that includes the NVM Flush command
|
|
* (NVM and Zoned), so always do an NVM Flush.
|
|
*/
|
|
|
|
if (req->cmd.opcode == NVME_CMD_FLUSH) {
|
|
return nvme_flush(n, req);
|
|
}
|
|
|
|
if (!nvme_nsid_valid(n, nsid) || nsid == NVME_NSID_BROADCAST) {
|
|
return NVME_INVALID_NSID | NVME_DNR;
|
|
}
|
|
|
|
ns = nvme_ns(n, nsid);
|
|
if (unlikely(!ns)) {
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
|
|
if (!(ns->iocs[req->cmd.opcode] & NVME_CMD_EFF_CSUPP)) {
|
|
trace_pci_nvme_err_invalid_opc(req->cmd.opcode);
|
|
return NVME_INVALID_OPCODE | NVME_DNR;
|
|
}
|
|
|
|
if (ns->status) {
|
|
return ns->status;
|
|
}
|
|
|
|
if (NVME_CMD_FLAGS_FUSE(req->cmd.flags)) {
|
|
return NVME_INVALID_FIELD;
|
|
}
|
|
|
|
req->ns = ns;
|
|
|
|
switch (req->cmd.opcode) {
|
|
case NVME_CMD_WRITE_ZEROES:
|
|
return nvme_write_zeroes(n, req);
|
|
case NVME_CMD_ZONE_APPEND:
|
|
return nvme_zone_append(n, req);
|
|
case NVME_CMD_WRITE:
|
|
return nvme_write(n, req);
|
|
case NVME_CMD_READ:
|
|
return nvme_read(n, req);
|
|
case NVME_CMD_COMPARE:
|
|
return nvme_compare(n, req);
|
|
case NVME_CMD_DSM:
|
|
return nvme_dsm(n, req);
|
|
case NVME_CMD_VERIFY:
|
|
return nvme_verify(n, req);
|
|
case NVME_CMD_COPY:
|
|
return nvme_copy(n, req);
|
|
case NVME_CMD_ZONE_MGMT_SEND:
|
|
return nvme_zone_mgmt_send(n, req);
|
|
case NVME_CMD_ZONE_MGMT_RECV:
|
|
return nvme_zone_mgmt_recv(n, req);
|
|
case NVME_CMD_IO_MGMT_RECV:
|
|
return nvme_io_mgmt_recv(n, req);
|
|
case NVME_CMD_IO_MGMT_SEND:
|
|
return nvme_io_mgmt_send(n, req);
|
|
default:
|
|
g_assert_not_reached();
|
|
}
|
|
|
|
return NVME_INVALID_OPCODE | NVME_DNR;
|
|
}
|
|
|
|
static void nvme_cq_notifier(EventNotifier *e)
|
|
{
|
|
NvmeCQueue *cq = container_of(e, NvmeCQueue, notifier);
|
|
NvmeCtrl *n = cq->ctrl;
|
|
|
|
if (!event_notifier_test_and_clear(e)) {
|
|
return;
|
|
}
|
|
|
|
nvme_update_cq_head(cq);
|
|
|
|
if (cq->tail == cq->head) {
|
|
if (cq->irq_enabled) {
|
|
n->cq_pending--;
|
|
}
|
|
|
|
nvme_irq_deassert(n, cq);
|
|
}
|
|
|
|
qemu_bh_schedule(cq->bh);
|
|
}
|
|
|
|
static int nvme_init_cq_ioeventfd(NvmeCQueue *cq)
|
|
{
|
|
NvmeCtrl *n = cq->ctrl;
|
|
uint16_t offset = (cq->cqid << 3) + (1 << 2);
|
|
int ret;
|
|
|
|
ret = event_notifier_init(&cq->notifier, 0);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
event_notifier_set_handler(&cq->notifier, nvme_cq_notifier);
|
|
memory_region_add_eventfd(&n->iomem,
|
|
0x1000 + offset, 4, false, 0, &cq->notifier);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void nvme_sq_notifier(EventNotifier *e)
|
|
{
|
|
NvmeSQueue *sq = container_of(e, NvmeSQueue, notifier);
|
|
|
|
if (!event_notifier_test_and_clear(e)) {
|
|
return;
|
|
}
|
|
|
|
nvme_process_sq(sq);
|
|
}
|
|
|
|
static int nvme_init_sq_ioeventfd(NvmeSQueue *sq)
|
|
{
|
|
NvmeCtrl *n = sq->ctrl;
|
|
uint16_t offset = sq->sqid << 3;
|
|
int ret;
|
|
|
|
ret = event_notifier_init(&sq->notifier, 0);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
event_notifier_set_handler(&sq->notifier, nvme_sq_notifier);
|
|
memory_region_add_eventfd(&n->iomem,
|
|
0x1000 + offset, 4, false, 0, &sq->notifier);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void nvme_free_sq(NvmeSQueue *sq, NvmeCtrl *n)
|
|
{
|
|
uint16_t offset = sq->sqid << 3;
|
|
|
|
n->sq[sq->sqid] = NULL;
|
|
qemu_bh_delete(sq->bh);
|
|
if (sq->ioeventfd_enabled) {
|
|
memory_region_del_eventfd(&n->iomem,
|
|
0x1000 + offset, 4, false, 0, &sq->notifier);
|
|
event_notifier_set_handler(&sq->notifier, NULL);
|
|
event_notifier_cleanup(&sq->notifier);
|
|
}
|
|
g_free(sq->io_req);
|
|
if (sq->sqid) {
|
|
g_free(sq);
|
|
}
|
|
}
|
|
|
|
static uint16_t nvme_del_sq(NvmeCtrl *n, NvmeRequest *req)
|
|
{
|
|
NvmeDeleteQ *c = (NvmeDeleteQ *)&req->cmd;
|
|
NvmeRequest *r, *next;
|
|
NvmeSQueue *sq;
|
|
NvmeCQueue *cq;
|
|
uint16_t qid = le16_to_cpu(c->qid);
|
|
|
|
if (unlikely(!qid || nvme_check_sqid(n, qid))) {
|
|
trace_pci_nvme_err_invalid_del_sq(qid);
|
|
return NVME_INVALID_QID | NVME_DNR;
|
|
}
|
|
|
|
trace_pci_nvme_del_sq(qid);
|
|
|
|
sq = n->sq[qid];
|
|
while (!QTAILQ_EMPTY(&sq->out_req_list)) {
|
|
r = QTAILQ_FIRST(&sq->out_req_list);
|
|
assert(r->aiocb);
|
|
blk_aio_cancel(r->aiocb);
|
|
}
|
|
|
|
assert(QTAILQ_EMPTY(&sq->out_req_list));
|
|
|
|
if (!nvme_check_cqid(n, sq->cqid)) {
|
|
cq = n->cq[sq->cqid];
|
|
QTAILQ_REMOVE(&cq->sq_list, sq, entry);
|
|
|
|
nvme_post_cqes(cq);
|
|
QTAILQ_FOREACH_SAFE(r, &cq->req_list, entry, next) {
|
|
if (r->sq == sq) {
|
|
QTAILQ_REMOVE(&cq->req_list, r, entry);
|
|
QTAILQ_INSERT_TAIL(&sq->req_list, r, entry);
|
|
}
|
|
}
|
|
}
|
|
|
|
nvme_free_sq(sq, n);
|
|
return NVME_SUCCESS;
|
|
}
|
|
|
|
static void nvme_init_sq(NvmeSQueue *sq, NvmeCtrl *n, uint64_t dma_addr,
|
|
uint16_t sqid, uint16_t cqid, uint16_t size)
|
|
{
|
|
int i;
|
|
NvmeCQueue *cq;
|
|
|
|
sq->ctrl = n;
|
|
sq->dma_addr = dma_addr;
|
|
sq->sqid = sqid;
|
|
sq->size = size;
|
|
sq->cqid = cqid;
|
|
sq->head = sq->tail = 0;
|
|
sq->io_req = g_new0(NvmeRequest, sq->size);
|
|
|
|
QTAILQ_INIT(&sq->req_list);
|
|
QTAILQ_INIT(&sq->out_req_list);
|
|
for (i = 0; i < sq->size; i++) {
|
|
sq->io_req[i].sq = sq;
|
|
QTAILQ_INSERT_TAIL(&(sq->req_list), &sq->io_req[i], entry);
|
|
}
|
|
|
|
sq->bh = qemu_bh_new_guarded(nvme_process_sq, sq,
|
|
&DEVICE(sq->ctrl)->mem_reentrancy_guard);
|
|
|
|
if (n->dbbuf_enabled) {
|
|
sq->db_addr = n->dbbuf_dbs + (sqid << 3);
|
|
sq->ei_addr = n->dbbuf_eis + (sqid << 3);
|
|
|
|
if (n->params.ioeventfd && sq->sqid != 0) {
|
|
if (!nvme_init_sq_ioeventfd(sq)) {
|
|
sq->ioeventfd_enabled = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
assert(n->cq[cqid]);
|
|
cq = n->cq[cqid];
|
|
QTAILQ_INSERT_TAIL(&(cq->sq_list), sq, entry);
|
|
n->sq[sqid] = sq;
|
|
}
|
|
|
|
static uint16_t nvme_create_sq(NvmeCtrl *n, NvmeRequest *req)
|
|
{
|
|
NvmeSQueue *sq;
|
|
NvmeCreateSq *c = (NvmeCreateSq *)&req->cmd;
|
|
|
|
uint16_t cqid = le16_to_cpu(c->cqid);
|
|
uint16_t sqid = le16_to_cpu(c->sqid);
|
|
uint16_t qsize = le16_to_cpu(c->qsize);
|
|
uint16_t qflags = le16_to_cpu(c->sq_flags);
|
|
uint64_t prp1 = le64_to_cpu(c->prp1);
|
|
|
|
trace_pci_nvme_create_sq(prp1, sqid, cqid, qsize, qflags);
|
|
|
|
if (unlikely(!cqid || nvme_check_cqid(n, cqid))) {
|
|
trace_pci_nvme_err_invalid_create_sq_cqid(cqid);
|
|
return NVME_INVALID_CQID | NVME_DNR;
|
|
}
|
|
if (unlikely(!sqid || sqid > n->conf_ioqpairs || n->sq[sqid] != NULL)) {
|
|
trace_pci_nvme_err_invalid_create_sq_sqid(sqid);
|
|
return NVME_INVALID_QID | NVME_DNR;
|
|
}
|
|
if (unlikely(!qsize || qsize > NVME_CAP_MQES(ldq_le_p(&n->bar.cap)))) {
|
|
trace_pci_nvme_err_invalid_create_sq_size(qsize);
|
|
return NVME_MAX_QSIZE_EXCEEDED | NVME_DNR;
|
|
}
|
|
if (unlikely(prp1 & (n->page_size - 1))) {
|
|
trace_pci_nvme_err_invalid_create_sq_addr(prp1);
|
|
return NVME_INVALID_PRP_OFFSET | NVME_DNR;
|
|
}
|
|
if (unlikely(!(NVME_SQ_FLAGS_PC(qflags)))) {
|
|
trace_pci_nvme_err_invalid_create_sq_qflags(NVME_SQ_FLAGS_PC(qflags));
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
sq = g_malloc0(sizeof(*sq));
|
|
nvme_init_sq(sq, n, prp1, sqid, cqid, qsize + 1);
|
|
return NVME_SUCCESS;
|
|
}
|
|
|
|
struct nvme_stats {
|
|
uint64_t units_read;
|
|
uint64_t units_written;
|
|
uint64_t read_commands;
|
|
uint64_t write_commands;
|
|
};
|
|
|
|
static void nvme_set_blk_stats(NvmeNamespace *ns, struct nvme_stats *stats)
|
|
{
|
|
BlockAcctStats *s = blk_get_stats(ns->blkconf.blk);
|
|
|
|
stats->units_read += s->nr_bytes[BLOCK_ACCT_READ];
|
|
stats->units_written += s->nr_bytes[BLOCK_ACCT_WRITE];
|
|
stats->read_commands += s->nr_ops[BLOCK_ACCT_READ];
|
|
stats->write_commands += s->nr_ops[BLOCK_ACCT_WRITE];
|
|
}
|
|
|
|
static uint16_t nvme_smart_info(NvmeCtrl *n, uint8_t rae, uint32_t buf_len,
|
|
uint64_t off, NvmeRequest *req)
|
|
{
|
|
uint32_t nsid = le32_to_cpu(req->cmd.nsid);
|
|
struct nvme_stats stats = { 0 };
|
|
NvmeSmartLog smart = { 0 };
|
|
uint32_t trans_len;
|
|
NvmeNamespace *ns;
|
|
time_t current_ms;
|
|
uint64_t u_read, u_written;
|
|
|
|
if (off >= sizeof(smart)) {
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
|
|
if (nsid != 0xffffffff) {
|
|
ns = nvme_ns(n, nsid);
|
|
if (!ns) {
|
|
return NVME_INVALID_NSID | NVME_DNR;
|
|
}
|
|
nvme_set_blk_stats(ns, &stats);
|
|
} else {
|
|
int i;
|
|
|
|
for (i = 1; i <= NVME_MAX_NAMESPACES; i++) {
|
|
ns = nvme_ns(n, i);
|
|
if (!ns) {
|
|
continue;
|
|
}
|
|
nvme_set_blk_stats(ns, &stats);
|
|
}
|
|
}
|
|
|
|
trans_len = MIN(sizeof(smart) - off, buf_len);
|
|
smart.critical_warning = n->smart_critical_warning;
|
|
|
|
u_read = DIV_ROUND_UP(stats.units_read >> BDRV_SECTOR_BITS, 1000);
|
|
u_written = DIV_ROUND_UP(stats.units_written >> BDRV_SECTOR_BITS, 1000);
|
|
|
|
smart.data_units_read[0] = cpu_to_le64(u_read);
|
|
smart.data_units_written[0] = cpu_to_le64(u_written);
|
|
smart.host_read_commands[0] = cpu_to_le64(stats.read_commands);
|
|
smart.host_write_commands[0] = cpu_to_le64(stats.write_commands);
|
|
|
|
smart.temperature = cpu_to_le16(n->temperature);
|
|
|
|
if ((n->temperature >= n->features.temp_thresh_hi) ||
|
|
(n->temperature <= n->features.temp_thresh_low)) {
|
|
smart.critical_warning |= NVME_SMART_TEMPERATURE;
|
|
}
|
|
|
|
current_ms = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL);
|
|
smart.power_on_hours[0] =
|
|
cpu_to_le64((((current_ms - n->starttime_ms) / 1000) / 60) / 60);
|
|
|
|
if (!rae) {
|
|
nvme_clear_events(n, NVME_AER_TYPE_SMART);
|
|
}
|
|
|
|
return nvme_c2h(n, (uint8_t *) &smart + off, trans_len, req);
|
|
}
|
|
|
|
static uint16_t nvme_endgrp_info(NvmeCtrl *n, uint8_t rae, uint32_t buf_len,
|
|
uint64_t off, NvmeRequest *req)
|
|
{
|
|
uint32_t dw11 = le32_to_cpu(req->cmd.cdw11);
|
|
uint16_t endgrpid = (dw11 >> 16) & 0xffff;
|
|
struct nvme_stats stats = {};
|
|
NvmeEndGrpLog info = {};
|
|
int i;
|
|
|
|
if (!n->subsys || endgrpid != 0x1) {
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
|
|
if (off >= sizeof(info)) {
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
|
|
for (i = 1; i <= NVME_MAX_NAMESPACES; i++) {
|
|
NvmeNamespace *ns = nvme_subsys_ns(n->subsys, i);
|
|
if (!ns) {
|
|
continue;
|
|
}
|
|
|
|
nvme_set_blk_stats(ns, &stats);
|
|
}
|
|
|
|
info.data_units_read[0] =
|
|
cpu_to_le64(DIV_ROUND_UP(stats.units_read / 1000000000, 1000000000));
|
|
info.data_units_written[0] =
|
|
cpu_to_le64(DIV_ROUND_UP(stats.units_written / 1000000000, 1000000000));
|
|
info.media_units_written[0] =
|
|
cpu_to_le64(DIV_ROUND_UP(stats.units_written / 1000000000, 1000000000));
|
|
|
|
info.host_read_commands[0] = cpu_to_le64(stats.read_commands);
|
|
info.host_write_commands[0] = cpu_to_le64(stats.write_commands);
|
|
|
|
buf_len = MIN(sizeof(info) - off, buf_len);
|
|
|
|
return nvme_c2h(n, (uint8_t *)&info + off, buf_len, req);
|
|
}
|
|
|
|
|
|
static uint16_t nvme_fw_log_info(NvmeCtrl *n, uint32_t buf_len, uint64_t off,
|
|
NvmeRequest *req)
|
|
{
|
|
uint32_t trans_len;
|
|
NvmeFwSlotInfoLog fw_log = {
|
|
.afi = 0x1,
|
|
};
|
|
|
|
if (off >= sizeof(fw_log)) {
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
|
|
strpadcpy((char *)&fw_log.frs1, sizeof(fw_log.frs1), "1.0", ' ');
|
|
trans_len = MIN(sizeof(fw_log) - off, buf_len);
|
|
|
|
return nvme_c2h(n, (uint8_t *) &fw_log + off, trans_len, req);
|
|
}
|
|
|
|
static uint16_t nvme_error_info(NvmeCtrl *n, uint8_t rae, uint32_t buf_len,
|
|
uint64_t off, NvmeRequest *req)
|
|
{
|
|
uint32_t trans_len;
|
|
NvmeErrorLog errlog;
|
|
|
|
if (off >= sizeof(errlog)) {
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
|
|
if (!rae) {
|
|
nvme_clear_events(n, NVME_AER_TYPE_ERROR);
|
|
}
|
|
|
|
memset(&errlog, 0x0, sizeof(errlog));
|
|
trans_len = MIN(sizeof(errlog) - off, buf_len);
|
|
|
|
return nvme_c2h(n, (uint8_t *)&errlog, trans_len, req);
|
|
}
|
|
|
|
static uint16_t nvme_changed_nslist(NvmeCtrl *n, uint8_t rae, uint32_t buf_len,
|
|
uint64_t off, NvmeRequest *req)
|
|
{
|
|
uint32_t nslist[1024];
|
|
uint32_t trans_len;
|
|
int i = 0;
|
|
uint32_t nsid;
|
|
|
|
if (off >= sizeof(nslist)) {
|
|
trace_pci_nvme_err_invalid_log_page_offset(off, sizeof(nslist));
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
|
|
memset(nslist, 0x0, sizeof(nslist));
|
|
trans_len = MIN(sizeof(nslist) - off, buf_len);
|
|
|
|
while ((nsid = find_first_bit(n->changed_nsids, NVME_CHANGED_NSID_SIZE)) !=
|
|
NVME_CHANGED_NSID_SIZE) {
|
|
/*
|
|
* If more than 1024 namespaces, the first entry in the log page should
|
|
* be set to FFFFFFFFh and the others to 0 as spec.
|
|
*/
|
|
if (i == ARRAY_SIZE(nslist)) {
|
|
memset(nslist, 0x0, sizeof(nslist));
|
|
nslist[0] = 0xffffffff;
|
|
break;
|
|
}
|
|
|
|
nslist[i++] = nsid;
|
|
clear_bit(nsid, n->changed_nsids);
|
|
}
|
|
|
|
/*
|
|
* Remove all the remaining list entries in case returns directly due to
|
|
* more than 1024 namespaces.
|
|
*/
|
|
if (nslist[0] == 0xffffffff) {
|
|
bitmap_zero(n->changed_nsids, NVME_CHANGED_NSID_SIZE);
|
|
}
|
|
|
|
if (!rae) {
|
|
nvme_clear_events(n, NVME_AER_TYPE_NOTICE);
|
|
}
|
|
|
|
return nvme_c2h(n, ((uint8_t *)nslist) + off, trans_len, req);
|
|
}
|
|
|
|
static uint16_t nvme_cmd_effects(NvmeCtrl *n, uint8_t csi, uint32_t buf_len,
|
|
uint64_t off, NvmeRequest *req)
|
|
{
|
|
NvmeEffectsLog log = {};
|
|
const uint32_t *src_iocs = NULL;
|
|
uint32_t trans_len;
|
|
|
|
if (off >= sizeof(log)) {
|
|
trace_pci_nvme_err_invalid_log_page_offset(off, sizeof(log));
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
|
|
switch (NVME_CC_CSS(ldl_le_p(&n->bar.cc))) {
|
|
case NVME_CC_CSS_NVM:
|
|
src_iocs = nvme_cse_iocs_nvm;
|
|
/* fall through */
|
|
case NVME_CC_CSS_ADMIN_ONLY:
|
|
break;
|
|
case NVME_CC_CSS_CSI:
|
|
switch (csi) {
|
|
case NVME_CSI_NVM:
|
|
src_iocs = nvme_cse_iocs_nvm;
|
|
break;
|
|
case NVME_CSI_ZONED:
|
|
src_iocs = nvme_cse_iocs_zoned;
|
|
break;
|
|
}
|
|
}
|
|
|
|
memcpy(log.acs, nvme_cse_acs, sizeof(nvme_cse_acs));
|
|
|
|
if (src_iocs) {
|
|
memcpy(log.iocs, src_iocs, sizeof(log.iocs));
|
|
}
|
|
|
|
trans_len = MIN(sizeof(log) - off, buf_len);
|
|
|
|
return nvme_c2h(n, ((uint8_t *)&log) + off, trans_len, req);
|
|
}
|
|
|
|
static size_t sizeof_fdp_conf_descr(size_t nruh, size_t vss)
|
|
{
|
|
size_t entry_siz = sizeof(NvmeFdpDescrHdr) + nruh * sizeof(NvmeRuhDescr)
|
|
+ vss;
|
|
return ROUND_UP(entry_siz, 8);
|
|
}
|
|
|
|
static uint16_t nvme_fdp_confs(NvmeCtrl *n, uint32_t endgrpid, uint32_t buf_len,
|
|
uint64_t off, NvmeRequest *req)
|
|
{
|
|
uint32_t log_size, trans_len;
|
|
g_autofree uint8_t *buf = NULL;
|
|
NvmeFdpDescrHdr *hdr;
|
|
NvmeRuhDescr *ruhd;
|
|
NvmeEnduranceGroup *endgrp;
|
|
NvmeFdpConfsHdr *log;
|
|
size_t nruh, fdp_descr_size;
|
|
int i;
|
|
|
|
if (endgrpid != 1 || !n->subsys) {
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
|
|
endgrp = &n->subsys->endgrp;
|
|
|
|
if (endgrp->fdp.enabled) {
|
|
nruh = endgrp->fdp.nruh;
|
|
} else {
|
|
nruh = 1;
|
|
}
|
|
|
|
fdp_descr_size = sizeof_fdp_conf_descr(nruh, FDPVSS);
|
|
log_size = sizeof(NvmeFdpConfsHdr) + fdp_descr_size;
|
|
|
|
if (off >= log_size) {
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
|
|
trans_len = MIN(log_size - off, buf_len);
|
|
|
|
buf = g_malloc0(log_size);
|
|
log = (NvmeFdpConfsHdr *)buf;
|
|
hdr = (NvmeFdpDescrHdr *)(log + 1);
|
|
ruhd = (NvmeRuhDescr *)(buf + sizeof(*log) + sizeof(*hdr));
|
|
|
|
log->num_confs = cpu_to_le16(0);
|
|
log->size = cpu_to_le32(log_size);
|
|
|
|
hdr->descr_size = cpu_to_le16(fdp_descr_size);
|
|
if (endgrp->fdp.enabled) {
|
|
hdr->fdpa = FIELD_DP8(hdr->fdpa, FDPA, VALID, 1);
|
|
hdr->fdpa = FIELD_DP8(hdr->fdpa, FDPA, RGIF, endgrp->fdp.rgif);
|
|
hdr->nrg = cpu_to_le16(endgrp->fdp.nrg);
|
|
hdr->nruh = cpu_to_le16(endgrp->fdp.nruh);
|
|
hdr->maxpids = cpu_to_le16(NVME_FDP_MAXPIDS - 1);
|
|
hdr->nnss = cpu_to_le32(NVME_MAX_NAMESPACES);
|
|
hdr->runs = cpu_to_le64(endgrp->fdp.runs);
|
|
|
|
for (i = 0; i < nruh; i++) {
|
|
ruhd->ruht = NVME_RUHT_INITIALLY_ISOLATED;
|
|
ruhd++;
|
|
}
|
|
} else {
|
|
/* 1 bit for RUH in PIF -> 2 RUHs max. */
|
|
hdr->nrg = cpu_to_le16(1);
|
|
hdr->nruh = cpu_to_le16(1);
|
|
hdr->maxpids = cpu_to_le16(NVME_FDP_MAXPIDS - 1);
|
|
hdr->nnss = cpu_to_le32(1);
|
|
hdr->runs = cpu_to_le64(96 * MiB);
|
|
|
|
ruhd->ruht = NVME_RUHT_INITIALLY_ISOLATED;
|
|
}
|
|
|
|
return nvme_c2h(n, (uint8_t *)buf + off, trans_len, req);
|
|
}
|
|
|
|
static uint16_t nvme_fdp_ruh_usage(NvmeCtrl *n, uint32_t endgrpid,
|
|
uint32_t dw10, uint32_t dw12,
|
|
uint32_t buf_len, uint64_t off,
|
|
NvmeRequest *req)
|
|
{
|
|
NvmeRuHandle *ruh;
|
|
NvmeRuhuLog *hdr;
|
|
NvmeRuhuDescr *ruhud;
|
|
NvmeEnduranceGroup *endgrp;
|
|
g_autofree uint8_t *buf = NULL;
|
|
uint32_t log_size, trans_len;
|
|
uint16_t i;
|
|
|
|
if (endgrpid != 1 || !n->subsys) {
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
|
|
endgrp = &n->subsys->endgrp;
|
|
|
|
if (!endgrp->fdp.enabled) {
|
|
return NVME_FDP_DISABLED | NVME_DNR;
|
|
}
|
|
|
|
log_size = sizeof(NvmeRuhuLog) + endgrp->fdp.nruh * sizeof(NvmeRuhuDescr);
|
|
|
|
if (off >= log_size) {
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
|
|
trans_len = MIN(log_size - off, buf_len);
|
|
|
|
buf = g_malloc0(log_size);
|
|
hdr = (NvmeRuhuLog *)buf;
|
|
ruhud = (NvmeRuhuDescr *)(hdr + 1);
|
|
|
|
ruh = endgrp->fdp.ruhs;
|
|
hdr->nruh = cpu_to_le16(endgrp->fdp.nruh);
|
|
|
|
for (i = 0; i < endgrp->fdp.nruh; i++, ruhud++, ruh++) {
|
|
ruhud->ruha = ruh->ruha;
|
|
}
|
|
|
|
return nvme_c2h(n, (uint8_t *)buf + off, trans_len, req);
|
|
}
|
|
|
|
static uint16_t nvme_fdp_stats(NvmeCtrl *n, uint32_t endgrpid, uint32_t buf_len,
|
|
uint64_t off, NvmeRequest *req)
|
|
{
|
|
NvmeEnduranceGroup *endgrp;
|
|
NvmeFdpStatsLog log = {};
|
|
uint32_t trans_len;
|
|
|
|
if (off >= sizeof(NvmeFdpStatsLog)) {
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
|
|
if (endgrpid != 1 || !n->subsys) {
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
|
|
if (!n->subsys->endgrp.fdp.enabled) {
|
|
return NVME_FDP_DISABLED | NVME_DNR;
|
|
}
|
|
|
|
endgrp = &n->subsys->endgrp;
|
|
|
|
trans_len = MIN(sizeof(log) - off, buf_len);
|
|
|
|
/* spec value is 128 bit, we only use 64 bit */
|
|
log.hbmw[0] = cpu_to_le64(endgrp->fdp.hbmw);
|
|
log.mbmw[0] = cpu_to_le64(endgrp->fdp.mbmw);
|
|
log.mbe[0] = cpu_to_le64(endgrp->fdp.mbe);
|
|
|
|
return nvme_c2h(n, (uint8_t *)&log + off, trans_len, req);
|
|
}
|
|
|
|
static uint16_t nvme_fdp_events(NvmeCtrl *n, uint32_t endgrpid,
|
|
uint32_t buf_len, uint64_t off,
|
|
NvmeRequest *req)
|
|
{
|
|
NvmeEnduranceGroup *endgrp;
|
|
NvmeCmd *cmd = &req->cmd;
|
|
bool host_events = (cmd->cdw10 >> 8) & 0x1;
|
|
uint32_t log_size, trans_len;
|
|
NvmeFdpEventBuffer *ebuf;
|
|
g_autofree NvmeFdpEventsLog *elog = NULL;
|
|
NvmeFdpEvent *event;
|
|
|
|
if (endgrpid != 1 || !n->subsys) {
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
|
|
endgrp = &n->subsys->endgrp;
|
|
|
|
if (!endgrp->fdp.enabled) {
|
|
return NVME_FDP_DISABLED | NVME_DNR;
|
|
}
|
|
|
|
if (host_events) {
|
|
ebuf = &endgrp->fdp.host_events;
|
|
} else {
|
|
ebuf = &endgrp->fdp.ctrl_events;
|
|
}
|
|
|
|
log_size = sizeof(NvmeFdpEventsLog) + ebuf->nelems * sizeof(NvmeFdpEvent);
|
|
|
|
if (off >= log_size) {
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
|
|
trans_len = MIN(log_size - off, buf_len);
|
|
elog = g_malloc0(log_size);
|
|
elog->num_events = cpu_to_le32(ebuf->nelems);
|
|
event = (NvmeFdpEvent *)(elog + 1);
|
|
|
|
if (ebuf->nelems && ebuf->start == ebuf->next) {
|
|
unsigned int nelems = (NVME_FDP_MAX_EVENTS - ebuf->start);
|
|
/* wrap over, copy [start;NVME_FDP_MAX_EVENTS[ and [0; next[ */
|
|
memcpy(event, &ebuf->events[ebuf->start],
|
|
sizeof(NvmeFdpEvent) * nelems);
|
|
memcpy(event + nelems, ebuf->events,
|
|
sizeof(NvmeFdpEvent) * ebuf->next);
|
|
} else if (ebuf->start < ebuf->next) {
|
|
memcpy(event, &ebuf->events[ebuf->start],
|
|
sizeof(NvmeFdpEvent) * (ebuf->next - ebuf->start));
|
|
}
|
|
|
|
return nvme_c2h(n, (uint8_t *)elog + off, trans_len, req);
|
|
}
|
|
|
|
static uint16_t nvme_get_log(NvmeCtrl *n, NvmeRequest *req)
|
|
{
|
|
NvmeCmd *cmd = &req->cmd;
|
|
|
|
uint32_t dw10 = le32_to_cpu(cmd->cdw10);
|
|
uint32_t dw11 = le32_to_cpu(cmd->cdw11);
|
|
uint32_t dw12 = le32_to_cpu(cmd->cdw12);
|
|
uint32_t dw13 = le32_to_cpu(cmd->cdw13);
|
|
uint8_t lid = dw10 & 0xff;
|
|
uint8_t lsp = (dw10 >> 8) & 0xf;
|
|
uint8_t rae = (dw10 >> 15) & 0x1;
|
|
uint8_t csi = le32_to_cpu(cmd->cdw14) >> 24;
|
|
uint32_t numdl, numdu, lspi;
|
|
uint64_t off, lpol, lpou;
|
|
size_t len;
|
|
uint16_t status;
|
|
|
|
numdl = (dw10 >> 16);
|
|
numdu = (dw11 & 0xffff);
|
|
lspi = (dw11 >> 16);
|
|
lpol = dw12;
|
|
lpou = dw13;
|
|
|
|
len = (((numdu << 16) | numdl) + 1) << 2;
|
|
off = (lpou << 32ULL) | lpol;
|
|
|
|
if (off & 0x3) {
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
|
|
trace_pci_nvme_get_log(nvme_cid(req), lid, lsp, rae, len, off);
|
|
|
|
status = nvme_check_mdts(n, len);
|
|
if (status) {
|
|
return status;
|
|
}
|
|
|
|
switch (lid) {
|
|
case NVME_LOG_ERROR_INFO:
|
|
return nvme_error_info(n, rae, len, off, req);
|
|
case NVME_LOG_SMART_INFO:
|
|
return nvme_smart_info(n, rae, len, off, req);
|
|
case NVME_LOG_FW_SLOT_INFO:
|
|
return nvme_fw_log_info(n, len, off, req);
|
|
case NVME_LOG_CHANGED_NSLIST:
|
|
return nvme_changed_nslist(n, rae, len, off, req);
|
|
case NVME_LOG_CMD_EFFECTS:
|
|
return nvme_cmd_effects(n, csi, len, off, req);
|
|
case NVME_LOG_ENDGRP:
|
|
return nvme_endgrp_info(n, rae, len, off, req);
|
|
case NVME_LOG_FDP_CONFS:
|
|
return nvme_fdp_confs(n, lspi, len, off, req);
|
|
case NVME_LOG_FDP_RUH_USAGE:
|
|
return nvme_fdp_ruh_usage(n, lspi, dw10, dw12, len, off, req);
|
|
case NVME_LOG_FDP_STATS:
|
|
return nvme_fdp_stats(n, lspi, len, off, req);
|
|
case NVME_LOG_FDP_EVENTS:
|
|
return nvme_fdp_events(n, lspi, len, off, req);
|
|
default:
|
|
trace_pci_nvme_err_invalid_log_page(nvme_cid(req), lid);
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
}
|
|
|
|
static void nvme_free_cq(NvmeCQueue *cq, NvmeCtrl *n)
|
|
{
|
|
PCIDevice *pci = PCI_DEVICE(n);
|
|
uint16_t offset = (cq->cqid << 3) + (1 << 2);
|
|
|
|
n->cq[cq->cqid] = NULL;
|
|
qemu_bh_delete(cq->bh);
|
|
if (cq->ioeventfd_enabled) {
|
|
memory_region_del_eventfd(&n->iomem,
|
|
0x1000 + offset, 4, false, 0, &cq->notifier);
|
|
event_notifier_set_handler(&cq->notifier, NULL);
|
|
event_notifier_cleanup(&cq->notifier);
|
|
}
|
|
if (msix_enabled(pci)) {
|
|
msix_vector_unuse(pci, cq->vector);
|
|
}
|
|
if (cq->cqid) {
|
|
g_free(cq);
|
|
}
|
|
}
|
|
|
|
static uint16_t nvme_del_cq(NvmeCtrl *n, NvmeRequest *req)
|
|
{
|
|
NvmeDeleteQ *c = (NvmeDeleteQ *)&req->cmd;
|
|
NvmeCQueue *cq;
|
|
uint16_t qid = le16_to_cpu(c->qid);
|
|
|
|
if (unlikely(!qid || nvme_check_cqid(n, qid))) {
|
|
trace_pci_nvme_err_invalid_del_cq_cqid(qid);
|
|
return NVME_INVALID_CQID | NVME_DNR;
|
|
}
|
|
|
|
cq = n->cq[qid];
|
|
if (unlikely(!QTAILQ_EMPTY(&cq->sq_list))) {
|
|
trace_pci_nvme_err_invalid_del_cq_notempty(qid);
|
|
return NVME_INVALID_QUEUE_DEL;
|
|
}
|
|
|
|
if (cq->irq_enabled && cq->tail != cq->head) {
|
|
n->cq_pending--;
|
|
}
|
|
|
|
nvme_irq_deassert(n, cq);
|
|
trace_pci_nvme_del_cq(qid);
|
|
nvme_free_cq(cq, n);
|
|
return NVME_SUCCESS;
|
|
}
|
|
|
|
static void nvme_init_cq(NvmeCQueue *cq, NvmeCtrl *n, uint64_t dma_addr,
|
|
uint16_t cqid, uint16_t vector, uint16_t size,
|
|
uint16_t irq_enabled)
|
|
{
|
|
PCIDevice *pci = PCI_DEVICE(n);
|
|
|
|
if (msix_enabled(pci)) {
|
|
msix_vector_use(pci, vector);
|
|
}
|
|
cq->ctrl = n;
|
|
cq->cqid = cqid;
|
|
cq->size = size;
|
|
cq->dma_addr = dma_addr;
|
|
cq->phase = 1;
|
|
cq->irq_enabled = irq_enabled;
|
|
cq->vector = vector;
|
|
cq->head = cq->tail = 0;
|
|
QTAILQ_INIT(&cq->req_list);
|
|
QTAILQ_INIT(&cq->sq_list);
|
|
if (n->dbbuf_enabled) {
|
|
cq->db_addr = n->dbbuf_dbs + (cqid << 3) + (1 << 2);
|
|
cq->ei_addr = n->dbbuf_eis + (cqid << 3) + (1 << 2);
|
|
|
|
if (n->params.ioeventfd && cqid != 0) {
|
|
if (!nvme_init_cq_ioeventfd(cq)) {
|
|
cq->ioeventfd_enabled = true;
|
|
}
|
|
}
|
|
}
|
|
n->cq[cqid] = cq;
|
|
cq->bh = qemu_bh_new_guarded(nvme_post_cqes, cq,
|
|
&DEVICE(cq->ctrl)->mem_reentrancy_guard);
|
|
}
|
|
|
|
static uint16_t nvme_create_cq(NvmeCtrl *n, NvmeRequest *req)
|
|
{
|
|
NvmeCQueue *cq;
|
|
NvmeCreateCq *c = (NvmeCreateCq *)&req->cmd;
|
|
uint16_t cqid = le16_to_cpu(c->cqid);
|
|
uint16_t vector = le16_to_cpu(c->irq_vector);
|
|
uint16_t qsize = le16_to_cpu(c->qsize);
|
|
uint16_t qflags = le16_to_cpu(c->cq_flags);
|
|
uint64_t prp1 = le64_to_cpu(c->prp1);
|
|
uint32_t cc = ldq_le_p(&n->bar.cc);
|
|
uint8_t iocqes = NVME_CC_IOCQES(cc);
|
|
uint8_t iosqes = NVME_CC_IOSQES(cc);
|
|
|
|
trace_pci_nvme_create_cq(prp1, cqid, vector, qsize, qflags,
|
|
NVME_CQ_FLAGS_IEN(qflags) != 0);
|
|
|
|
if (iosqes != NVME_SQES || iocqes != NVME_CQES) {
|
|
trace_pci_nvme_err_invalid_create_cq_entry_size(iosqes, iocqes);
|
|
return NVME_MAX_QSIZE_EXCEEDED | NVME_DNR;
|
|
}
|
|
|
|
if (unlikely(!cqid || cqid > n->conf_ioqpairs || n->cq[cqid] != NULL)) {
|
|
trace_pci_nvme_err_invalid_create_cq_cqid(cqid);
|
|
return NVME_INVALID_QID | NVME_DNR;
|
|
}
|
|
if (unlikely(!qsize || qsize > NVME_CAP_MQES(ldq_le_p(&n->bar.cap)))) {
|
|
trace_pci_nvme_err_invalid_create_cq_size(qsize);
|
|
return NVME_MAX_QSIZE_EXCEEDED | NVME_DNR;
|
|
}
|
|
if (unlikely(prp1 & (n->page_size - 1))) {
|
|
trace_pci_nvme_err_invalid_create_cq_addr(prp1);
|
|
return NVME_INVALID_PRP_OFFSET | NVME_DNR;
|
|
}
|
|
if (unlikely(!msix_enabled(PCI_DEVICE(n)) && vector)) {
|
|
trace_pci_nvme_err_invalid_create_cq_vector(vector);
|
|
return NVME_INVALID_IRQ_VECTOR | NVME_DNR;
|
|
}
|
|
if (unlikely(vector >= n->conf_msix_qsize)) {
|
|
trace_pci_nvme_err_invalid_create_cq_vector(vector);
|
|
return NVME_INVALID_IRQ_VECTOR | NVME_DNR;
|
|
}
|
|
if (unlikely(!(NVME_CQ_FLAGS_PC(qflags)))) {
|
|
trace_pci_nvme_err_invalid_create_cq_qflags(NVME_CQ_FLAGS_PC(qflags));
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
|
|
cq = g_malloc0(sizeof(*cq));
|
|
nvme_init_cq(cq, n, prp1, cqid, vector, qsize + 1,
|
|
NVME_CQ_FLAGS_IEN(qflags));
|
|
|
|
/*
|
|
* It is only required to set qs_created when creating a completion queue;
|
|
* creating a submission queue without a matching completion queue will
|
|
* fail.
|
|
*/
|
|
n->qs_created = true;
|
|
return NVME_SUCCESS;
|
|
}
|
|
|
|
static uint16_t nvme_rpt_empty_id_struct(NvmeCtrl *n, NvmeRequest *req)
|
|
{
|
|
uint8_t id[NVME_IDENTIFY_DATA_SIZE] = {};
|
|
|
|
return nvme_c2h(n, id, sizeof(id), req);
|
|
}
|
|
|
|
static uint16_t nvme_identify_ctrl(NvmeCtrl *n, NvmeRequest *req)
|
|
{
|
|
trace_pci_nvme_identify_ctrl();
|
|
|
|
return nvme_c2h(n, (uint8_t *)&n->id_ctrl, sizeof(n->id_ctrl), req);
|
|
}
|
|
|
|
static uint16_t nvme_identify_ctrl_csi(NvmeCtrl *n, NvmeRequest *req)
|
|
{
|
|
NvmeIdentify *c = (NvmeIdentify *)&req->cmd;
|
|
uint8_t id[NVME_IDENTIFY_DATA_SIZE] = {};
|
|
NvmeIdCtrlNvm *id_nvm = (NvmeIdCtrlNvm *)&id;
|
|
|
|
trace_pci_nvme_identify_ctrl_csi(c->csi);
|
|
|
|
switch (c->csi) {
|
|
case NVME_CSI_NVM:
|
|
id_nvm->vsl = n->params.vsl;
|
|
id_nvm->dmrsl = cpu_to_le32(n->dmrsl);
|
|
break;
|
|
|
|
case NVME_CSI_ZONED:
|
|
((NvmeIdCtrlZoned *)&id)->zasl = n->params.zasl;
|
|
break;
|
|
|
|
default:
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
|
|
return nvme_c2h(n, id, sizeof(id), req);
|
|
}
|
|
|
|
static uint16_t nvme_identify_ns(NvmeCtrl *n, NvmeRequest *req, bool active)
|
|
{
|
|
NvmeNamespace *ns;
|
|
NvmeIdentify *c = (NvmeIdentify *)&req->cmd;
|
|
uint32_t nsid = le32_to_cpu(c->nsid);
|
|
|
|
trace_pci_nvme_identify_ns(nsid);
|
|
|
|
if (!nvme_nsid_valid(n, nsid) || nsid == NVME_NSID_BROADCAST) {
|
|
return NVME_INVALID_NSID | NVME_DNR;
|
|
}
|
|
|
|
ns = nvme_ns(n, nsid);
|
|
if (unlikely(!ns)) {
|
|
if (!active) {
|
|
ns = nvme_subsys_ns(n->subsys, nsid);
|
|
if (!ns) {
|
|
return nvme_rpt_empty_id_struct(n, req);
|
|
}
|
|
} else {
|
|
return nvme_rpt_empty_id_struct(n, req);
|
|
}
|
|
}
|
|
|
|
if (active || ns->csi == NVME_CSI_NVM) {
|
|
return nvme_c2h(n, (uint8_t *)&ns->id_ns, sizeof(NvmeIdNs), req);
|
|
}
|
|
|
|
return NVME_INVALID_CMD_SET | NVME_DNR;
|
|
}
|
|
|
|
static uint16_t nvme_identify_ctrl_list(NvmeCtrl *n, NvmeRequest *req,
|
|
bool attached)
|
|
{
|
|
NvmeIdentify *c = (NvmeIdentify *)&req->cmd;
|
|
uint32_t nsid = le32_to_cpu(c->nsid);
|
|
uint16_t min_id = le16_to_cpu(c->ctrlid);
|
|
uint16_t list[NVME_CONTROLLER_LIST_SIZE] = {};
|
|
uint16_t *ids = &list[1];
|
|
NvmeNamespace *ns;
|
|
NvmeCtrl *ctrl;
|
|
int cntlid, nr_ids = 0;
|
|
|
|
trace_pci_nvme_identify_ctrl_list(c->cns, min_id);
|
|
|
|
if (!n->subsys) {
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
|
|
if (attached) {
|
|
if (nsid == NVME_NSID_BROADCAST) {
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
|
|
ns = nvme_subsys_ns(n->subsys, nsid);
|
|
if (!ns) {
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
}
|
|
|
|
for (cntlid = min_id; cntlid < ARRAY_SIZE(n->subsys->ctrls); cntlid++) {
|
|
ctrl = nvme_subsys_ctrl(n->subsys, cntlid);
|
|
if (!ctrl) {
|
|
continue;
|
|
}
|
|
|
|
if (attached && !nvme_ns(ctrl, nsid)) {
|
|
continue;
|
|
}
|
|
|
|
ids[nr_ids++] = cntlid;
|
|
}
|
|
|
|
list[0] = nr_ids;
|
|
|
|
return nvme_c2h(n, (uint8_t *)list, sizeof(list), req);
|
|
}
|
|
|
|
static uint16_t nvme_identify_pri_ctrl_cap(NvmeCtrl *n, NvmeRequest *req)
|
|
{
|
|
trace_pci_nvme_identify_pri_ctrl_cap(le16_to_cpu(n->pri_ctrl_cap.cntlid));
|
|
|
|
return nvme_c2h(n, (uint8_t *)&n->pri_ctrl_cap,
|
|
sizeof(NvmePriCtrlCap), req);
|
|
}
|
|
|
|
static uint16_t nvme_identify_sec_ctrl_list(NvmeCtrl *n, NvmeRequest *req)
|
|
{
|
|
NvmeIdentify *c = (NvmeIdentify *)&req->cmd;
|
|
uint16_t pri_ctrl_id = le16_to_cpu(n->pri_ctrl_cap.cntlid);
|
|
uint16_t min_id = le16_to_cpu(c->ctrlid);
|
|
uint8_t num_sec_ctrl = n->nr_sec_ctrls;
|
|
NvmeSecCtrlList list = {0};
|
|
uint8_t i;
|
|
|
|
for (i = 0; i < num_sec_ctrl; i++) {
|
|
if (n->sec_ctrl_list[i].scid >= min_id) {
|
|
list.numcntl = MIN(num_sec_ctrl - i, 127);
|
|
memcpy(&list.sec, n->sec_ctrl_list + i,
|
|
list.numcntl * sizeof(NvmeSecCtrlEntry));
|
|
break;
|
|
}
|
|
}
|
|
|
|
trace_pci_nvme_identify_sec_ctrl_list(pri_ctrl_id, list.numcntl);
|
|
|
|
return nvme_c2h(n, (uint8_t *)&list, sizeof(list), req);
|
|
}
|
|
|
|
static uint16_t nvme_identify_ns_csi(NvmeCtrl *n, NvmeRequest *req,
|
|
bool active)
|
|
{
|
|
NvmeNamespace *ns;
|
|
NvmeIdentify *c = (NvmeIdentify *)&req->cmd;
|
|
uint32_t nsid = le32_to_cpu(c->nsid);
|
|
|
|
trace_pci_nvme_identify_ns_csi(nsid, c->csi);
|
|
|
|
if (!nvme_nsid_valid(n, nsid) || nsid == NVME_NSID_BROADCAST) {
|
|
return NVME_INVALID_NSID | NVME_DNR;
|
|
}
|
|
|
|
ns = nvme_ns(n, nsid);
|
|
if (unlikely(!ns)) {
|
|
if (!active) {
|
|
ns = nvme_subsys_ns(n->subsys, nsid);
|
|
if (!ns) {
|
|
return nvme_rpt_empty_id_struct(n, req);
|
|
}
|
|
} else {
|
|
return nvme_rpt_empty_id_struct(n, req);
|
|
}
|
|
}
|
|
|
|
if (c->csi == NVME_CSI_NVM) {
|
|
return nvme_c2h(n, (uint8_t *)&ns->id_ns_nvm, sizeof(NvmeIdNsNvm),
|
|
req);
|
|
} else if (c->csi == NVME_CSI_ZONED && ns->csi == NVME_CSI_ZONED) {
|
|
return nvme_c2h(n, (uint8_t *)ns->id_ns_zoned, sizeof(NvmeIdNsZoned),
|
|
req);
|
|
}
|
|
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
|
|
static uint16_t nvme_identify_nslist(NvmeCtrl *n, NvmeRequest *req,
|
|
bool active)
|
|
{
|
|
NvmeNamespace *ns;
|
|
NvmeIdentify *c = (NvmeIdentify *)&req->cmd;
|
|
uint32_t min_nsid = le32_to_cpu(c->nsid);
|
|
uint8_t list[NVME_IDENTIFY_DATA_SIZE] = {};
|
|
static const int data_len = sizeof(list);
|
|
uint32_t *list_ptr = (uint32_t *)list;
|
|
int i, j = 0;
|
|
|
|
trace_pci_nvme_identify_nslist(min_nsid);
|
|
|
|
/*
|
|
* Both FFFFFFFFh (NVME_NSID_BROADCAST) and FFFFFFFFEh are invalid values
|
|
* since the Active Namespace ID List should return namespaces with ids
|
|
* *higher* than the NSID specified in the command. This is also specified
|
|
* in the spec (NVM Express v1.3d, Section 5.15.4).
|
|
*/
|
|
if (min_nsid >= NVME_NSID_BROADCAST - 1) {
|
|
return NVME_INVALID_NSID | NVME_DNR;
|
|
}
|
|
|
|
for (i = 1; i <= NVME_MAX_NAMESPACES; i++) {
|
|
ns = nvme_ns(n, i);
|
|
if (!ns) {
|
|
if (!active) {
|
|
ns = nvme_subsys_ns(n->subsys, i);
|
|
if (!ns) {
|
|
continue;
|
|
}
|
|
} else {
|
|
continue;
|
|
}
|
|
}
|
|
if (ns->params.nsid <= min_nsid) {
|
|
continue;
|
|
}
|
|
list_ptr[j++] = cpu_to_le32(ns->params.nsid);
|
|
if (j == data_len / sizeof(uint32_t)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return nvme_c2h(n, list, data_len, req);
|
|
}
|
|
|
|
static uint16_t nvme_identify_nslist_csi(NvmeCtrl *n, NvmeRequest *req,
|
|
bool active)
|
|
{
|
|
NvmeNamespace *ns;
|
|
NvmeIdentify *c = (NvmeIdentify *)&req->cmd;
|
|
uint32_t min_nsid = le32_to_cpu(c->nsid);
|
|
uint8_t list[NVME_IDENTIFY_DATA_SIZE] = {};
|
|
static const int data_len = sizeof(list);
|
|
uint32_t *list_ptr = (uint32_t *)list;
|
|
int i, j = 0;
|
|
|
|
trace_pci_nvme_identify_nslist_csi(min_nsid, c->csi);
|
|
|
|
/*
|
|
* Same as in nvme_identify_nslist(), FFFFFFFFh/FFFFFFFFEh are invalid.
|
|
*/
|
|
if (min_nsid >= NVME_NSID_BROADCAST - 1) {
|
|
return NVME_INVALID_NSID | NVME_DNR;
|
|
}
|
|
|
|
if (c->csi != NVME_CSI_NVM && c->csi != NVME_CSI_ZONED) {
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
|
|
for (i = 1; i <= NVME_MAX_NAMESPACES; i++) {
|
|
ns = nvme_ns(n, i);
|
|
if (!ns) {
|
|
if (!active) {
|
|
ns = nvme_subsys_ns(n->subsys, i);
|
|
if (!ns) {
|
|
continue;
|
|
}
|
|
} else {
|
|
continue;
|
|
}
|
|
}
|
|
if (ns->params.nsid <= min_nsid || c->csi != ns->csi) {
|
|
continue;
|
|
}
|
|
list_ptr[j++] = cpu_to_le32(ns->params.nsid);
|
|
if (j == data_len / sizeof(uint32_t)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return nvme_c2h(n, list, data_len, req);
|
|
}
|
|
|
|
static uint16_t nvme_endurance_group_list(NvmeCtrl *n, NvmeRequest *req)
|
|
{
|
|
uint16_t list[NVME_CONTROLLER_LIST_SIZE] = {};
|
|
uint16_t *nr_ids = &list[0];
|
|
uint16_t *ids = &list[1];
|
|
uint16_t endgid = le32_to_cpu(req->cmd.cdw11) & 0xffff;
|
|
|
|
/*
|
|
* The current nvme-subsys only supports Endurance Group #1.
|
|
*/
|
|
if (!endgid) {
|
|
*nr_ids = 1;
|
|
ids[0] = 1;
|
|
} else {
|
|
*nr_ids = 0;
|
|
}
|
|
|
|
return nvme_c2h(n, list, sizeof(list), req);
|
|
}
|
|
|
|
static uint16_t nvme_identify_ns_descr_list(NvmeCtrl *n, NvmeRequest *req)
|
|
{
|
|
NvmeNamespace *ns;
|
|
NvmeIdentify *c = (NvmeIdentify *)&req->cmd;
|
|
uint32_t nsid = le32_to_cpu(c->nsid);
|
|
uint8_t list[NVME_IDENTIFY_DATA_SIZE] = {};
|
|
uint8_t *pos = list;
|
|
struct {
|
|
NvmeIdNsDescr hdr;
|
|
uint8_t v[NVME_NIDL_UUID];
|
|
} QEMU_PACKED uuid = {};
|
|
struct {
|
|
NvmeIdNsDescr hdr;
|
|
uint8_t v[NVME_NIDL_NGUID];
|
|
} QEMU_PACKED nguid = {};
|
|
struct {
|
|
NvmeIdNsDescr hdr;
|
|
uint64_t v;
|
|
} QEMU_PACKED eui64 = {};
|
|
struct {
|
|
NvmeIdNsDescr hdr;
|
|
uint8_t v;
|
|
} QEMU_PACKED csi = {};
|
|
|
|
trace_pci_nvme_identify_ns_descr_list(nsid);
|
|
|
|
if (!nvme_nsid_valid(n, nsid) || nsid == NVME_NSID_BROADCAST) {
|
|
return NVME_INVALID_NSID | NVME_DNR;
|
|
}
|
|
|
|
ns = nvme_ns(n, nsid);
|
|
if (unlikely(!ns)) {
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
|
|
if (!qemu_uuid_is_null(&ns->params.uuid)) {
|
|
uuid.hdr.nidt = NVME_NIDT_UUID;
|
|
uuid.hdr.nidl = NVME_NIDL_UUID;
|
|
memcpy(uuid.v, ns->params.uuid.data, NVME_NIDL_UUID);
|
|
memcpy(pos, &uuid, sizeof(uuid));
|
|
pos += sizeof(uuid);
|
|
}
|
|
|
|
if (!nvme_nguid_is_null(&ns->params.nguid)) {
|
|
nguid.hdr.nidt = NVME_NIDT_NGUID;
|
|
nguid.hdr.nidl = NVME_NIDL_NGUID;
|
|
memcpy(nguid.v, ns->params.nguid.data, NVME_NIDL_NGUID);
|
|
memcpy(pos, &nguid, sizeof(nguid));
|
|
pos += sizeof(nguid);
|
|
}
|
|
|
|
if (ns->params.eui64) {
|
|
eui64.hdr.nidt = NVME_NIDT_EUI64;
|
|
eui64.hdr.nidl = NVME_NIDL_EUI64;
|
|
eui64.v = cpu_to_be64(ns->params.eui64);
|
|
memcpy(pos, &eui64, sizeof(eui64));
|
|
pos += sizeof(eui64);
|
|
}
|
|
|
|
csi.hdr.nidt = NVME_NIDT_CSI;
|
|
csi.hdr.nidl = NVME_NIDL_CSI;
|
|
csi.v = ns->csi;
|
|
memcpy(pos, &csi, sizeof(csi));
|
|
pos += sizeof(csi);
|
|
|
|
return nvme_c2h(n, list, sizeof(list), req);
|
|
}
|
|
|
|
static uint16_t nvme_identify_cmd_set(NvmeCtrl *n, NvmeRequest *req)
|
|
{
|
|
uint8_t list[NVME_IDENTIFY_DATA_SIZE] = {};
|
|
static const int data_len = sizeof(list);
|
|
|
|
trace_pci_nvme_identify_cmd_set();
|
|
|
|
NVME_SET_CSI(*list, NVME_CSI_NVM);
|
|
NVME_SET_CSI(*list, NVME_CSI_ZONED);
|
|
|
|
return nvme_c2h(n, list, data_len, req);
|
|
}
|
|
|
|
static uint16_t nvme_identify(NvmeCtrl *n, NvmeRequest *req)
|
|
{
|
|
NvmeIdentify *c = (NvmeIdentify *)&req->cmd;
|
|
|
|
trace_pci_nvme_identify(nvme_cid(req), c->cns, le16_to_cpu(c->ctrlid),
|
|
c->csi);
|
|
|
|
switch (c->cns) {
|
|
case NVME_ID_CNS_NS:
|
|
return nvme_identify_ns(n, req, true);
|
|
case NVME_ID_CNS_NS_PRESENT:
|
|
return nvme_identify_ns(n, req, false);
|
|
case NVME_ID_CNS_NS_ATTACHED_CTRL_LIST:
|
|
return nvme_identify_ctrl_list(n, req, true);
|
|
case NVME_ID_CNS_CTRL_LIST:
|
|
return nvme_identify_ctrl_list(n, req, false);
|
|
case NVME_ID_CNS_PRIMARY_CTRL_CAP:
|
|
return nvme_identify_pri_ctrl_cap(n, req);
|
|
case NVME_ID_CNS_SECONDARY_CTRL_LIST:
|
|
return nvme_identify_sec_ctrl_list(n, req);
|
|
case NVME_ID_CNS_CS_NS:
|
|
return nvme_identify_ns_csi(n, req, true);
|
|
case NVME_ID_CNS_CS_NS_PRESENT:
|
|
return nvme_identify_ns_csi(n, req, false);
|
|
case NVME_ID_CNS_CTRL:
|
|
return nvme_identify_ctrl(n, req);
|
|
case NVME_ID_CNS_CS_CTRL:
|
|
return nvme_identify_ctrl_csi(n, req);
|
|
case NVME_ID_CNS_NS_ACTIVE_LIST:
|
|
return nvme_identify_nslist(n, req, true);
|
|
case NVME_ID_CNS_NS_PRESENT_LIST:
|
|
return nvme_identify_nslist(n, req, false);
|
|
case NVME_ID_CNS_CS_NS_ACTIVE_LIST:
|
|
return nvme_identify_nslist_csi(n, req, true);
|
|
case NVME_ID_CNS_ENDURANCE_GROUP_LIST:
|
|
return nvme_endurance_group_list(n, req);
|
|
case NVME_ID_CNS_CS_NS_PRESENT_LIST:
|
|
return nvme_identify_nslist_csi(n, req, false);
|
|
case NVME_ID_CNS_NS_DESCR_LIST:
|
|
return nvme_identify_ns_descr_list(n, req);
|
|
case NVME_ID_CNS_IO_COMMAND_SET:
|
|
return nvme_identify_cmd_set(n, req);
|
|
default:
|
|
trace_pci_nvme_err_invalid_identify_cns(le32_to_cpu(c->cns));
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
}
|
|
|
|
static uint16_t nvme_abort(NvmeCtrl *n, NvmeRequest *req)
|
|
{
|
|
uint16_t sqid = le32_to_cpu(req->cmd.cdw10) & 0xffff;
|
|
uint16_t cid = (le32_to_cpu(req->cmd.cdw10) >> 16) & 0xffff;
|
|
NvmeSQueue *sq = n->sq[sqid];
|
|
NvmeRequest *r, *next;
|
|
int i;
|
|
|
|
req->cqe.result = 1;
|
|
if (nvme_check_sqid(n, sqid)) {
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
|
|
if (sqid == 0) {
|
|
for (i = 0; i < n->outstanding_aers; i++) {
|
|
NvmeRequest *re = n->aer_reqs[i];
|
|
if (re->cqe.cid == cid) {
|
|
memmove(n->aer_reqs + i, n->aer_reqs + i + 1,
|
|
(n->outstanding_aers - i - 1) * sizeof(NvmeRequest *));
|
|
n->outstanding_aers--;
|
|
re->status = NVME_CMD_ABORT_REQ;
|
|
req->cqe.result = 0;
|
|
nvme_enqueue_req_completion(&n->admin_cq, re);
|
|
return NVME_SUCCESS;
|
|
}
|
|
}
|
|
}
|
|
|
|
QTAILQ_FOREACH_SAFE(r, &sq->out_req_list, entry, next) {
|
|
if (r->cqe.cid == cid) {
|
|
if (r->aiocb) {
|
|
blk_aio_cancel_async(r->aiocb);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
return NVME_SUCCESS;
|
|
}
|
|
|
|
static inline void nvme_set_timestamp(NvmeCtrl *n, uint64_t ts)
|
|
{
|
|
trace_pci_nvme_setfeat_timestamp(ts);
|
|
|
|
n->host_timestamp = le64_to_cpu(ts);
|
|
n->timestamp_set_qemu_clock_ms = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL);
|
|
}
|
|
|
|
static inline uint64_t nvme_get_timestamp(const NvmeCtrl *n)
|
|
{
|
|
uint64_t current_time = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL);
|
|
uint64_t elapsed_time = current_time - n->timestamp_set_qemu_clock_ms;
|
|
|
|
union nvme_timestamp {
|
|
struct {
|
|
uint64_t timestamp:48;
|
|
uint64_t sync:1;
|
|
uint64_t origin:3;
|
|
uint64_t rsvd1:12;
|
|
};
|
|
uint64_t all;
|
|
};
|
|
|
|
union nvme_timestamp ts;
|
|
ts.all = 0;
|
|
ts.timestamp = n->host_timestamp + elapsed_time;
|
|
|
|
/* If the host timestamp is non-zero, set the timestamp origin */
|
|
ts.origin = n->host_timestamp ? 0x01 : 0x00;
|
|
|
|
trace_pci_nvme_getfeat_timestamp(ts.all);
|
|
|
|
return cpu_to_le64(ts.all);
|
|
}
|
|
|
|
static uint16_t nvme_get_feature_timestamp(NvmeCtrl *n, NvmeRequest *req)
|
|
{
|
|
uint64_t timestamp = nvme_get_timestamp(n);
|
|
|
|
return nvme_c2h(n, (uint8_t *)×tamp, sizeof(timestamp), req);
|
|
}
|
|
|
|
static int nvme_get_feature_fdp(NvmeCtrl *n, uint32_t endgrpid,
|
|
uint32_t *result)
|
|
{
|
|
*result = 0;
|
|
|
|
if (!n->subsys || !n->subsys->endgrp.fdp.enabled) {
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
|
|
*result = FIELD_DP16(0, FEAT_FDP, FDPE, 1);
|
|
*result = FIELD_DP16(*result, FEAT_FDP, CONF_NDX, 0);
|
|
|
|
return NVME_SUCCESS;
|
|
}
|
|
|
|
static uint16_t nvme_get_feature_fdp_events(NvmeCtrl *n, NvmeNamespace *ns,
|
|
NvmeRequest *req, uint32_t *result)
|
|
{
|
|
NvmeCmd *cmd = &req->cmd;
|
|
uint32_t cdw11 = le32_to_cpu(cmd->cdw11);
|
|
uint16_t ph = cdw11 & 0xffff;
|
|
uint8_t noet = (cdw11 >> 16) & 0xff;
|
|
uint16_t ruhid, ret;
|
|
uint32_t nentries = 0;
|
|
uint8_t s_events_ndx = 0;
|
|
size_t s_events_siz = sizeof(NvmeFdpEventDescr) * noet;
|
|
g_autofree NvmeFdpEventDescr *s_events = g_malloc0(s_events_siz);
|
|
NvmeRuHandle *ruh;
|
|
NvmeFdpEventDescr *s_event;
|
|
|
|
if (!n->subsys || !n->subsys->endgrp.fdp.enabled) {
|
|
return NVME_FDP_DISABLED | NVME_DNR;
|
|
}
|
|
|
|
if (!nvme_ph_valid(ns, ph)) {
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
|
|
ruhid = ns->fdp.phs[ph];
|
|
ruh = &n->subsys->endgrp.fdp.ruhs[ruhid];
|
|
|
|
assert(ruh);
|
|
|
|
if (unlikely(noet == 0)) {
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
|
|
for (uint8_t event_type = 0; event_type < FDP_EVT_MAX; event_type++) {
|
|
uint8_t shift = nvme_fdp_evf_shifts[event_type];
|
|
if (!shift && event_type) {
|
|
/*
|
|
* only first entry (event_type == 0) has a shift value of 0
|
|
* other entries are simply unpopulated.
|
|
*/
|
|
continue;
|
|
}
|
|
|
|
nentries++;
|
|
|
|
s_event = &s_events[s_events_ndx];
|
|
s_event->evt = event_type;
|
|
s_event->evta = (ruh->event_filter >> shift) & 0x1;
|
|
|
|
/* break if all `noet` entries are filled */
|
|
if ((++s_events_ndx) == noet) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
ret = nvme_c2h(n, s_events, s_events_siz, req);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
*result = nentries;
|
|
return NVME_SUCCESS;
|
|
}
|
|
|
|
static uint16_t nvme_get_feature(NvmeCtrl *n, NvmeRequest *req)
|
|
{
|
|
NvmeCmd *cmd = &req->cmd;
|
|
uint32_t dw10 = le32_to_cpu(cmd->cdw10);
|
|
uint32_t dw11 = le32_to_cpu(cmd->cdw11);
|
|
uint32_t nsid = le32_to_cpu(cmd->nsid);
|
|
uint32_t result = 0;
|
|
uint8_t fid = NVME_GETSETFEAT_FID(dw10);
|
|
NvmeGetFeatureSelect sel = NVME_GETFEAT_SELECT(dw10);
|
|
uint16_t iv;
|
|
NvmeNamespace *ns;
|
|
int i;
|
|
uint16_t endgrpid = 0, ret = NVME_SUCCESS;
|
|
|
|
static const uint32_t nvme_feature_default[NVME_FID_MAX] = {
|
|
[NVME_ARBITRATION] = NVME_ARB_AB_NOLIMIT,
|
|
};
|
|
|
|
trace_pci_nvme_getfeat(nvme_cid(req), nsid, fid, sel, dw11);
|
|
|
|
if (!nvme_feature_support[fid]) {
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
|
|
if (nvme_feature_cap[fid] & NVME_FEAT_CAP_NS) {
|
|
if (!nvme_nsid_valid(n, nsid) || nsid == NVME_NSID_BROADCAST) {
|
|
/*
|
|
* The Reservation Notification Mask and Reservation Persistence
|
|
* features require a status code of Invalid Field in Command when
|
|
* NSID is FFFFFFFFh. Since the device does not support those
|
|
* features we can always return Invalid Namespace or Format as we
|
|
* should do for all other features.
|
|
*/
|
|
return NVME_INVALID_NSID | NVME_DNR;
|
|
}
|
|
|
|
if (!nvme_ns(n, nsid)) {
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
}
|
|
|
|
switch (sel) {
|
|
case NVME_GETFEAT_SELECT_CURRENT:
|
|
break;
|
|
case NVME_GETFEAT_SELECT_SAVED:
|
|
/* no features are saveable by the controller; fallthrough */
|
|
case NVME_GETFEAT_SELECT_DEFAULT:
|
|
goto defaults;
|
|
case NVME_GETFEAT_SELECT_CAP:
|
|
result = nvme_feature_cap[fid];
|
|
goto out;
|
|
}
|
|
|
|
switch (fid) {
|
|
case NVME_TEMPERATURE_THRESHOLD:
|
|
result = 0;
|
|
|
|
/*
|
|
* The controller only implements the Composite Temperature sensor, so
|
|
* return 0 for all other sensors.
|
|
*/
|
|
if (NVME_TEMP_TMPSEL(dw11) != NVME_TEMP_TMPSEL_COMPOSITE) {
|
|
goto out;
|
|
}
|
|
|
|
switch (NVME_TEMP_THSEL(dw11)) {
|
|
case NVME_TEMP_THSEL_OVER:
|
|
result = n->features.temp_thresh_hi;
|
|
goto out;
|
|
case NVME_TEMP_THSEL_UNDER:
|
|
result = n->features.temp_thresh_low;
|
|
goto out;
|
|
}
|
|
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
case NVME_ERROR_RECOVERY:
|
|
if (!nvme_nsid_valid(n, nsid)) {
|
|
return NVME_INVALID_NSID | NVME_DNR;
|
|
}
|
|
|
|
ns = nvme_ns(n, nsid);
|
|
if (unlikely(!ns)) {
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
|
|
result = ns->features.err_rec;
|
|
goto out;
|
|
case NVME_VOLATILE_WRITE_CACHE:
|
|
result = 0;
|
|
for (i = 1; i <= NVME_MAX_NAMESPACES; i++) {
|
|
ns = nvme_ns(n, i);
|
|
if (!ns) {
|
|
continue;
|
|
}
|
|
|
|
result = blk_enable_write_cache(ns->blkconf.blk);
|
|
if (result) {
|
|
break;
|
|
}
|
|
}
|
|
trace_pci_nvme_getfeat_vwcache(result ? "enabled" : "disabled");
|
|
goto out;
|
|
case NVME_ASYNCHRONOUS_EVENT_CONF:
|
|
result = n->features.async_config;
|
|
goto out;
|
|
case NVME_TIMESTAMP:
|
|
return nvme_get_feature_timestamp(n, req);
|
|
case NVME_HOST_BEHAVIOR_SUPPORT:
|
|
return nvme_c2h(n, (uint8_t *)&n->features.hbs,
|
|
sizeof(n->features.hbs), req);
|
|
case NVME_FDP_MODE:
|
|
endgrpid = dw11 & 0xff;
|
|
|
|
if (endgrpid != 0x1) {
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
|
|
ret = nvme_get_feature_fdp(n, endgrpid, &result);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
goto out;
|
|
case NVME_FDP_EVENTS:
|
|
if (!nvme_nsid_valid(n, nsid)) {
|
|
return NVME_INVALID_NSID | NVME_DNR;
|
|
}
|
|
|
|
ns = nvme_ns(n, nsid);
|
|
if (unlikely(!ns)) {
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
|
|
ret = nvme_get_feature_fdp_events(n, ns, req, &result);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
goto out;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
defaults:
|
|
switch (fid) {
|
|
case NVME_TEMPERATURE_THRESHOLD:
|
|
result = 0;
|
|
|
|
if (NVME_TEMP_TMPSEL(dw11) != NVME_TEMP_TMPSEL_COMPOSITE) {
|
|
break;
|
|
}
|
|
|
|
if (NVME_TEMP_THSEL(dw11) == NVME_TEMP_THSEL_OVER) {
|
|
result = NVME_TEMPERATURE_WARNING;
|
|
}
|
|
|
|
break;
|
|
case NVME_NUMBER_OF_QUEUES:
|
|
result = (n->conf_ioqpairs - 1) | ((n->conf_ioqpairs - 1) << 16);
|
|
trace_pci_nvme_getfeat_numq(result);
|
|
break;
|
|
case NVME_INTERRUPT_VECTOR_CONF:
|
|
iv = dw11 & 0xffff;
|
|
if (iv >= n->conf_ioqpairs + 1) {
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
|
|
result = iv;
|
|
if (iv == n->admin_cq.vector) {
|
|
result |= NVME_INTVC_NOCOALESCING;
|
|
}
|
|
break;
|
|
case NVME_FDP_MODE:
|
|
endgrpid = dw11 & 0xff;
|
|
|
|
if (endgrpid != 0x1) {
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
|
|
ret = nvme_get_feature_fdp(n, endgrpid, &result);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
break;
|
|
|
|
case NVME_WRITE_ATOMICITY:
|
|
result = n->dn;
|
|
break;
|
|
default:
|
|
result = nvme_feature_default[fid];
|
|
break;
|
|
}
|
|
|
|
out:
|
|
req->cqe.result = cpu_to_le32(result);
|
|
return ret;
|
|
}
|
|
|
|
static uint16_t nvme_set_feature_timestamp(NvmeCtrl *n, NvmeRequest *req)
|
|
{
|
|
uint16_t ret;
|
|
uint64_t timestamp;
|
|
|
|
ret = nvme_h2c(n, (uint8_t *)×tamp, sizeof(timestamp), req);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
nvme_set_timestamp(n, timestamp);
|
|
|
|
return NVME_SUCCESS;
|
|
}
|
|
|
|
static uint16_t nvme_set_feature_fdp_events(NvmeCtrl *n, NvmeNamespace *ns,
|
|
NvmeRequest *req)
|
|
{
|
|
NvmeCmd *cmd = &req->cmd;
|
|
uint32_t cdw11 = le32_to_cpu(cmd->cdw11);
|
|
uint16_t ph = cdw11 & 0xffff;
|
|
uint8_t noet = (cdw11 >> 16) & 0xff;
|
|
uint16_t ret, ruhid;
|
|
uint8_t enable = le32_to_cpu(cmd->cdw12) & 0x1;
|
|
uint8_t event_mask = 0;
|
|
unsigned int i;
|
|
g_autofree uint8_t *events = g_malloc0(noet);
|
|
NvmeRuHandle *ruh = NULL;
|
|
|
|
assert(ns);
|
|
|
|
if (!n->subsys || !n->subsys->endgrp.fdp.enabled) {
|
|
return NVME_FDP_DISABLED | NVME_DNR;
|
|
}
|
|
|
|
if (!nvme_ph_valid(ns, ph)) {
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
|
|
ruhid = ns->fdp.phs[ph];
|
|
ruh = &n->subsys->endgrp.fdp.ruhs[ruhid];
|
|
|
|
ret = nvme_h2c(n, events, noet, req);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
for (i = 0; i < noet; i++) {
|
|
event_mask |= (1 << nvme_fdp_evf_shifts[events[i]]);
|
|
}
|
|
|
|
if (enable) {
|
|
ruh->event_filter |= event_mask;
|
|
} else {
|
|
ruh->event_filter = ruh->event_filter & ~event_mask;
|
|
}
|
|
|
|
return NVME_SUCCESS;
|
|
}
|
|
|
|
static uint16_t nvme_set_feature(NvmeCtrl *n, NvmeRequest *req)
|
|
{
|
|
NvmeNamespace *ns = NULL;
|
|
|
|
NvmeCmd *cmd = &req->cmd;
|
|
uint32_t dw10 = le32_to_cpu(cmd->cdw10);
|
|
uint32_t dw11 = le32_to_cpu(cmd->cdw11);
|
|
uint32_t nsid = le32_to_cpu(cmd->nsid);
|
|
uint8_t fid = NVME_GETSETFEAT_FID(dw10);
|
|
uint8_t save = NVME_SETFEAT_SAVE(dw10);
|
|
uint16_t status;
|
|
int i;
|
|
NvmeIdCtrl *id = &n->id_ctrl;
|
|
NvmeAtomic *atomic = &n->atomic;
|
|
|
|
trace_pci_nvme_setfeat(nvme_cid(req), nsid, fid, save, dw11);
|
|
|
|
if (save && !(nvme_feature_cap[fid] & NVME_FEAT_CAP_SAVE)) {
|
|
return NVME_FID_NOT_SAVEABLE | NVME_DNR;
|
|
}
|
|
|
|
if (!nvme_feature_support[fid]) {
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
|
|
if (nvme_feature_cap[fid] & NVME_FEAT_CAP_NS) {
|
|
if (nsid != NVME_NSID_BROADCAST) {
|
|
if (!nvme_nsid_valid(n, nsid)) {
|
|
return NVME_INVALID_NSID | NVME_DNR;
|
|
}
|
|
|
|
ns = nvme_ns(n, nsid);
|
|
if (unlikely(!ns)) {
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
}
|
|
} else if (nsid && nsid != NVME_NSID_BROADCAST) {
|
|
if (!nvme_nsid_valid(n, nsid)) {
|
|
return NVME_INVALID_NSID | NVME_DNR;
|
|
}
|
|
|
|
return NVME_FEAT_NOT_NS_SPEC | NVME_DNR;
|
|
}
|
|
|
|
if (!(nvme_feature_cap[fid] & NVME_FEAT_CAP_CHANGE)) {
|
|
return NVME_FEAT_NOT_CHANGEABLE | NVME_DNR;
|
|
}
|
|
|
|
switch (fid) {
|
|
case NVME_TEMPERATURE_THRESHOLD:
|
|
if (NVME_TEMP_TMPSEL(dw11) != NVME_TEMP_TMPSEL_COMPOSITE) {
|
|
break;
|
|
}
|
|
|
|
switch (NVME_TEMP_THSEL(dw11)) {
|
|
case NVME_TEMP_THSEL_OVER:
|
|
n->features.temp_thresh_hi = NVME_TEMP_TMPTH(dw11);
|
|
break;
|
|
case NVME_TEMP_THSEL_UNDER:
|
|
n->features.temp_thresh_low = NVME_TEMP_TMPTH(dw11);
|
|
break;
|
|
default:
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
|
|
if ((n->temperature >= n->features.temp_thresh_hi) ||
|
|
(n->temperature <= n->features.temp_thresh_low)) {
|
|
nvme_smart_event(n, NVME_SMART_TEMPERATURE);
|
|
}
|
|
|
|
break;
|
|
case NVME_ERROR_RECOVERY:
|
|
if (nsid == NVME_NSID_BROADCAST) {
|
|
for (i = 1; i <= NVME_MAX_NAMESPACES; i++) {
|
|
ns = nvme_ns(n, i);
|
|
|
|
if (!ns) {
|
|
continue;
|
|
}
|
|
|
|
if (NVME_ID_NS_NSFEAT_DULBE(ns->id_ns.nsfeat)) {
|
|
ns->features.err_rec = dw11;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
assert(ns);
|
|
if (NVME_ID_NS_NSFEAT_DULBE(ns->id_ns.nsfeat)) {
|
|
ns->features.err_rec = dw11;
|
|
}
|
|
break;
|
|
case NVME_VOLATILE_WRITE_CACHE:
|
|
for (i = 1; i <= NVME_MAX_NAMESPACES; i++) {
|
|
ns = nvme_ns(n, i);
|
|
if (!ns) {
|
|
continue;
|
|
}
|
|
|
|
if (!(dw11 & 0x1) && blk_enable_write_cache(ns->blkconf.blk)) {
|
|
blk_flush(ns->blkconf.blk);
|
|
}
|
|
|
|
blk_set_enable_write_cache(ns->blkconf.blk, dw11 & 1);
|
|
}
|
|
|
|
break;
|
|
|
|
case NVME_NUMBER_OF_QUEUES:
|
|
if (n->qs_created) {
|
|
return NVME_CMD_SEQ_ERROR | NVME_DNR;
|
|
}
|
|
|
|
/*
|
|
* NVMe v1.3, Section 5.21.1.7: FFFFh is not an allowed value for NCQR
|
|
* and NSQR.
|
|
*/
|
|
if ((dw11 & 0xffff) == 0xffff || ((dw11 >> 16) & 0xffff) == 0xffff) {
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
|
|
trace_pci_nvme_setfeat_numq((dw11 & 0xffff) + 1,
|
|
((dw11 >> 16) & 0xffff) + 1,
|
|
n->conf_ioqpairs,
|
|
n->conf_ioqpairs);
|
|
req->cqe.result = cpu_to_le32((n->conf_ioqpairs - 1) |
|
|
((n->conf_ioqpairs - 1) << 16));
|
|
break;
|
|
case NVME_ASYNCHRONOUS_EVENT_CONF:
|
|
n->features.async_config = dw11;
|
|
break;
|
|
case NVME_TIMESTAMP:
|
|
return nvme_set_feature_timestamp(n, req);
|
|
case NVME_HOST_BEHAVIOR_SUPPORT:
|
|
status = nvme_h2c(n, (uint8_t *)&n->features.hbs,
|
|
sizeof(n->features.hbs), req);
|
|
if (status) {
|
|
return status;
|
|
}
|
|
|
|
for (i = 1; i <= NVME_MAX_NAMESPACES; i++) {
|
|
ns = nvme_ns(n, i);
|
|
|
|
if (!ns) {
|
|
continue;
|
|
}
|
|
|
|
ns->id_ns.nlbaf = ns->nlbaf - 1;
|
|
if (!n->features.hbs.lbafee) {
|
|
ns->id_ns.nlbaf = MIN(ns->id_ns.nlbaf, 15);
|
|
}
|
|
}
|
|
|
|
return status;
|
|
case NVME_COMMAND_SET_PROFILE:
|
|
if (dw11 & 0x1ff) {
|
|
trace_pci_nvme_err_invalid_iocsci(dw11 & 0x1ff);
|
|
return NVME_CMD_SET_CMB_REJECTED | NVME_DNR;
|
|
}
|
|
break;
|
|
case NVME_FDP_MODE:
|
|
/* spec: abort with cmd seq err if there's one or more NS' in endgrp */
|
|
return NVME_CMD_SEQ_ERROR | NVME_DNR;
|
|
case NVME_FDP_EVENTS:
|
|
return nvme_set_feature_fdp_events(n, ns, req);
|
|
case NVME_WRITE_ATOMICITY:
|
|
|
|
n->dn = 0x1 & dw11;
|
|
|
|
if (n->dn) {
|
|
atomic->atomic_max_write_size = le16_to_cpu(id->awupf) + 1;
|
|
} else {
|
|
atomic->atomic_max_write_size = le16_to_cpu(id->awun) + 1;
|
|
}
|
|
|
|
if (atomic->atomic_max_write_size == 1) {
|
|
atomic->atomic_writes = 0;
|
|
} else {
|
|
atomic->atomic_writes = 1;
|
|
}
|
|
break;
|
|
default:
|
|
return NVME_FEAT_NOT_CHANGEABLE | NVME_DNR;
|
|
}
|
|
return NVME_SUCCESS;
|
|
}
|
|
|
|
static uint16_t nvme_aer(NvmeCtrl *n, NvmeRequest *req)
|
|
{
|
|
trace_pci_nvme_aer(nvme_cid(req));
|
|
|
|
if (n->outstanding_aers > n->params.aerl) {
|
|
trace_pci_nvme_aer_aerl_exceeded();
|
|
return NVME_AER_LIMIT_EXCEEDED;
|
|
}
|
|
|
|
n->aer_reqs[n->outstanding_aers] = req;
|
|
n->outstanding_aers++;
|
|
|
|
if (!QTAILQ_EMPTY(&n->aer_queue)) {
|
|
nvme_process_aers(n);
|
|
}
|
|
|
|
return NVME_NO_COMPLETE;
|
|
}
|
|
|
|
static void nvme_update_dmrsl(NvmeCtrl *n)
|
|
{
|
|
int nsid;
|
|
|
|
for (nsid = 1; nsid <= NVME_MAX_NAMESPACES; nsid++) {
|
|
NvmeNamespace *ns = nvme_ns(n, nsid);
|
|
if (!ns) {
|
|
continue;
|
|
}
|
|
|
|
n->dmrsl = MIN_NON_ZERO(n->dmrsl,
|
|
BDRV_REQUEST_MAX_BYTES / nvme_l2b(ns, 1));
|
|
}
|
|
}
|
|
|
|
static void nvme_select_iocs_ns(NvmeCtrl *n, NvmeNamespace *ns)
|
|
{
|
|
uint32_t cc = ldl_le_p(&n->bar.cc);
|
|
|
|
ns->iocs = nvme_cse_iocs_none;
|
|
switch (ns->csi) {
|
|
case NVME_CSI_NVM:
|
|
if (NVME_CC_CSS(cc) != NVME_CC_CSS_ADMIN_ONLY) {
|
|
ns->iocs = nvme_cse_iocs_nvm;
|
|
}
|
|
break;
|
|
case NVME_CSI_ZONED:
|
|
if (NVME_CC_CSS(cc) == NVME_CC_CSS_CSI) {
|
|
ns->iocs = nvme_cse_iocs_zoned;
|
|
} else if (NVME_CC_CSS(cc) == NVME_CC_CSS_NVM) {
|
|
ns->iocs = nvme_cse_iocs_nvm;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
static uint16_t nvme_ns_attachment(NvmeCtrl *n, NvmeRequest *req)
|
|
{
|
|
NvmeNamespace *ns;
|
|
NvmeCtrl *ctrl;
|
|
uint16_t list[NVME_CONTROLLER_LIST_SIZE] = {};
|
|
uint32_t nsid = le32_to_cpu(req->cmd.nsid);
|
|
uint32_t dw10 = le32_to_cpu(req->cmd.cdw10);
|
|
uint8_t sel = dw10 & 0xf;
|
|
uint16_t *nr_ids = &list[0];
|
|
uint16_t *ids = &list[1];
|
|
uint16_t ret;
|
|
int i;
|
|
|
|
trace_pci_nvme_ns_attachment(nvme_cid(req), dw10 & 0xf);
|
|
|
|
if (!nvme_nsid_valid(n, nsid)) {
|
|
return NVME_INVALID_NSID | NVME_DNR;
|
|
}
|
|
|
|
ns = nvme_subsys_ns(n->subsys, nsid);
|
|
if (!ns) {
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
|
|
ret = nvme_h2c(n, (uint8_t *)list, 4096, req);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
if (!*nr_ids) {
|
|
return NVME_NS_CTRL_LIST_INVALID | NVME_DNR;
|
|
}
|
|
|
|
*nr_ids = MIN(*nr_ids, NVME_CONTROLLER_LIST_SIZE - 1);
|
|
for (i = 0; i < *nr_ids; i++) {
|
|
ctrl = nvme_subsys_ctrl(n->subsys, ids[i]);
|
|
if (!ctrl) {
|
|
return NVME_NS_CTRL_LIST_INVALID | NVME_DNR;
|
|
}
|
|
|
|
switch (sel) {
|
|
case NVME_NS_ATTACHMENT_ATTACH:
|
|
if (nvme_ns(ctrl, nsid)) {
|
|
return NVME_NS_ALREADY_ATTACHED | NVME_DNR;
|
|
}
|
|
|
|
if (ns->attached && !ns->params.shared) {
|
|
return NVME_NS_PRIVATE | NVME_DNR;
|
|
}
|
|
|
|
nvme_attach_ns(ctrl, ns);
|
|
nvme_select_iocs_ns(ctrl, ns);
|
|
|
|
break;
|
|
|
|
case NVME_NS_ATTACHMENT_DETACH:
|
|
if (!nvme_ns(ctrl, nsid)) {
|
|
return NVME_NS_NOT_ATTACHED | NVME_DNR;
|
|
}
|
|
|
|
ctrl->namespaces[nsid] = NULL;
|
|
ns->attached--;
|
|
|
|
nvme_update_dmrsl(ctrl);
|
|
|
|
break;
|
|
|
|
default:
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
|
|
/*
|
|
* Add namespace id to the changed namespace id list for event clearing
|
|
* via Get Log Page command.
|
|
*/
|
|
if (!test_and_set_bit(nsid, ctrl->changed_nsids)) {
|
|
nvme_enqueue_event(ctrl, NVME_AER_TYPE_NOTICE,
|
|
NVME_AER_INFO_NOTICE_NS_ATTR_CHANGED,
|
|
NVME_LOG_CHANGED_NSLIST);
|
|
}
|
|
}
|
|
|
|
return NVME_SUCCESS;
|
|
}
|
|
|
|
typedef struct NvmeFormatAIOCB {
|
|
BlockAIOCB common;
|
|
BlockAIOCB *aiocb;
|
|
NvmeRequest *req;
|
|
int ret;
|
|
|
|
NvmeNamespace *ns;
|
|
uint32_t nsid;
|
|
bool broadcast;
|
|
int64_t offset;
|
|
|
|
uint8_t lbaf;
|
|
uint8_t mset;
|
|
uint8_t pi;
|
|
uint8_t pil;
|
|
} NvmeFormatAIOCB;
|
|
|
|
static void nvme_format_cancel(BlockAIOCB *aiocb)
|
|
{
|
|
NvmeFormatAIOCB *iocb = container_of(aiocb, NvmeFormatAIOCB, common);
|
|
|
|
iocb->ret = -ECANCELED;
|
|
|
|
if (iocb->aiocb) {
|
|
blk_aio_cancel_async(iocb->aiocb);
|
|
iocb->aiocb = NULL;
|
|
}
|
|
}
|
|
|
|
static const AIOCBInfo nvme_format_aiocb_info = {
|
|
.aiocb_size = sizeof(NvmeFormatAIOCB),
|
|
.cancel_async = nvme_format_cancel,
|
|
};
|
|
|
|
static void nvme_format_set(NvmeNamespace *ns, uint8_t lbaf, uint8_t mset,
|
|
uint8_t pi, uint8_t pil)
|
|
{
|
|
uint8_t lbafl = lbaf & 0xf;
|
|
uint8_t lbafu = lbaf >> 4;
|
|
|
|
trace_pci_nvme_format_set(ns->params.nsid, lbaf, mset, pi, pil);
|
|
|
|
ns->id_ns.dps = (pil << 3) | pi;
|
|
ns->id_ns.flbas = (lbafu << 5) | (mset << 4) | lbafl;
|
|
|
|
nvme_ns_init_format(ns);
|
|
}
|
|
|
|
static void nvme_do_format(NvmeFormatAIOCB *iocb);
|
|
|
|
static void nvme_format_ns_cb(void *opaque, int ret)
|
|
{
|
|
NvmeFormatAIOCB *iocb = opaque;
|
|
NvmeNamespace *ns = iocb->ns;
|
|
int bytes;
|
|
|
|
if (iocb->ret < 0) {
|
|
goto done;
|
|
} else if (ret < 0) {
|
|
iocb->ret = ret;
|
|
goto done;
|
|
}
|
|
|
|
assert(ns);
|
|
|
|
if (iocb->offset < ns->size) {
|
|
bytes = MIN(BDRV_REQUEST_MAX_BYTES, ns->size - iocb->offset);
|
|
|
|
iocb->aiocb = blk_aio_pwrite_zeroes(ns->blkconf.blk, iocb->offset,
|
|
bytes, BDRV_REQ_MAY_UNMAP,
|
|
nvme_format_ns_cb, iocb);
|
|
|
|
iocb->offset += bytes;
|
|
return;
|
|
}
|
|
|
|
nvme_format_set(ns, iocb->lbaf, iocb->mset, iocb->pi, iocb->pil);
|
|
ns->status = 0x0;
|
|
iocb->ns = NULL;
|
|
iocb->offset = 0;
|
|
|
|
done:
|
|
nvme_do_format(iocb);
|
|
}
|
|
|
|
static uint16_t nvme_format_check(NvmeNamespace *ns, uint8_t lbaf, uint8_t pi)
|
|
{
|
|
if (ns->params.zoned) {
|
|
return NVME_INVALID_FORMAT | NVME_DNR;
|
|
}
|
|
|
|
if (lbaf > ns->id_ns.nlbaf) {
|
|
return NVME_INVALID_FORMAT | NVME_DNR;
|
|
}
|
|
|
|
if (pi && (ns->id_ns.lbaf[lbaf].ms < nvme_pi_tuple_size(ns))) {
|
|
return NVME_INVALID_FORMAT | NVME_DNR;
|
|
}
|
|
|
|
if (pi && pi > NVME_ID_NS_DPS_TYPE_3) {
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
|
|
return NVME_SUCCESS;
|
|
}
|
|
|
|
static void nvme_do_format(NvmeFormatAIOCB *iocb)
|
|
{
|
|
NvmeRequest *req = iocb->req;
|
|
NvmeCtrl *n = nvme_ctrl(req);
|
|
uint32_t dw10 = le32_to_cpu(req->cmd.cdw10);
|
|
uint8_t lbaf = dw10 & 0xf;
|
|
uint8_t pi = (dw10 >> 5) & 0x7;
|
|
uint16_t status;
|
|
int i;
|
|
|
|
if (iocb->ret < 0) {
|
|
goto done;
|
|
}
|
|
|
|
if (iocb->broadcast) {
|
|
for (i = iocb->nsid + 1; i <= NVME_MAX_NAMESPACES; i++) {
|
|
iocb->ns = nvme_ns(n, i);
|
|
if (iocb->ns) {
|
|
iocb->nsid = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!iocb->ns) {
|
|
goto done;
|
|
}
|
|
|
|
status = nvme_format_check(iocb->ns, lbaf, pi);
|
|
if (status) {
|
|
req->status = status;
|
|
goto done;
|
|
}
|
|
|
|
iocb->ns->status = NVME_FORMAT_IN_PROGRESS;
|
|
nvme_format_ns_cb(iocb, 0);
|
|
return;
|
|
|
|
done:
|
|
iocb->common.cb(iocb->common.opaque, iocb->ret);
|
|
qemu_aio_unref(iocb);
|
|
}
|
|
|
|
static uint16_t nvme_format(NvmeCtrl *n, NvmeRequest *req)
|
|
{
|
|
NvmeFormatAIOCB *iocb;
|
|
uint32_t nsid = le32_to_cpu(req->cmd.nsid);
|
|
uint32_t dw10 = le32_to_cpu(req->cmd.cdw10);
|
|
uint8_t lbaf = dw10 & 0xf;
|
|
uint8_t mset = (dw10 >> 4) & 0x1;
|
|
uint8_t pi = (dw10 >> 5) & 0x7;
|
|
uint8_t pil = (dw10 >> 8) & 0x1;
|
|
uint8_t lbafu = (dw10 >> 12) & 0x3;
|
|
uint16_t status;
|
|
|
|
iocb = qemu_aio_get(&nvme_format_aiocb_info, NULL, nvme_misc_cb, req);
|
|
|
|
iocb->req = req;
|
|
iocb->ret = 0;
|
|
iocb->ns = NULL;
|
|
iocb->nsid = 0;
|
|
iocb->lbaf = lbaf;
|
|
iocb->mset = mset;
|
|
iocb->pi = pi;
|
|
iocb->pil = pil;
|
|
iocb->broadcast = (nsid == NVME_NSID_BROADCAST);
|
|
iocb->offset = 0;
|
|
|
|
if (n->features.hbs.lbafee) {
|
|
iocb->lbaf |= lbafu << 4;
|
|
}
|
|
|
|
if (!iocb->broadcast) {
|
|
if (!nvme_nsid_valid(n, nsid)) {
|
|
status = NVME_INVALID_NSID | NVME_DNR;
|
|
goto out;
|
|
}
|
|
|
|
iocb->ns = nvme_ns(n, nsid);
|
|
if (!iocb->ns) {
|
|
status = NVME_INVALID_FIELD | NVME_DNR;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
req->aiocb = &iocb->common;
|
|
nvme_do_format(iocb);
|
|
|
|
return NVME_NO_COMPLETE;
|
|
|
|
out:
|
|
qemu_aio_unref(iocb);
|
|
|
|
return status;
|
|
}
|
|
|
|
static void nvme_get_virt_res_num(NvmeCtrl *n, uint8_t rt, int *num_total,
|
|
int *num_prim, int *num_sec)
|
|
{
|
|
*num_total = le32_to_cpu(rt ?
|
|
n->pri_ctrl_cap.vifrt : n->pri_ctrl_cap.vqfrt);
|
|
*num_prim = le16_to_cpu(rt ?
|
|
n->pri_ctrl_cap.virfap : n->pri_ctrl_cap.vqrfap);
|
|
*num_sec = le16_to_cpu(rt ? n->pri_ctrl_cap.virfa : n->pri_ctrl_cap.vqrfa);
|
|
}
|
|
|
|
static uint16_t nvme_assign_virt_res_to_prim(NvmeCtrl *n, NvmeRequest *req,
|
|
uint16_t cntlid, uint8_t rt,
|
|
int nr)
|
|
{
|
|
int num_total, num_prim, num_sec;
|
|
|
|
if (cntlid != n->cntlid) {
|
|
return NVME_INVALID_CTRL_ID | NVME_DNR;
|
|
}
|
|
|
|
nvme_get_virt_res_num(n, rt, &num_total, &num_prim, &num_sec);
|
|
|
|
if (nr > num_total) {
|
|
return NVME_INVALID_NUM_RESOURCES | NVME_DNR;
|
|
}
|
|
|
|
if (nr > num_total - num_sec) {
|
|
return NVME_INVALID_RESOURCE_ID | NVME_DNR;
|
|
}
|
|
|
|
if (rt) {
|
|
n->next_pri_ctrl_cap.virfap = cpu_to_le16(nr);
|
|
} else {
|
|
n->next_pri_ctrl_cap.vqrfap = cpu_to_le16(nr);
|
|
}
|
|
|
|
req->cqe.result = cpu_to_le32(nr);
|
|
return req->status;
|
|
}
|
|
|
|
static void nvme_update_virt_res(NvmeCtrl *n, NvmeSecCtrlEntry *sctrl,
|
|
uint8_t rt, int nr)
|
|
{
|
|
int prev_nr, prev_total;
|
|
|
|
if (rt) {
|
|
prev_nr = le16_to_cpu(sctrl->nvi);
|
|
prev_total = le32_to_cpu(n->pri_ctrl_cap.virfa);
|
|
sctrl->nvi = cpu_to_le16(nr);
|
|
n->pri_ctrl_cap.virfa = cpu_to_le32(prev_total + nr - prev_nr);
|
|
} else {
|
|
prev_nr = le16_to_cpu(sctrl->nvq);
|
|
prev_total = le32_to_cpu(n->pri_ctrl_cap.vqrfa);
|
|
sctrl->nvq = cpu_to_le16(nr);
|
|
n->pri_ctrl_cap.vqrfa = cpu_to_le32(prev_total + nr - prev_nr);
|
|
}
|
|
}
|
|
|
|
static uint16_t nvme_assign_virt_res_to_sec(NvmeCtrl *n, NvmeRequest *req,
|
|
uint16_t cntlid, uint8_t rt, int nr)
|
|
{
|
|
int num_total, num_prim, num_sec, num_free, diff, limit;
|
|
NvmeSecCtrlEntry *sctrl;
|
|
|
|
sctrl = nvme_sctrl_for_cntlid(n, cntlid);
|
|
if (!sctrl) {
|
|
return NVME_INVALID_CTRL_ID | NVME_DNR;
|
|
}
|
|
|
|
if (sctrl->scs) {
|
|
return NVME_INVALID_SEC_CTRL_STATE | NVME_DNR;
|
|
}
|
|
|
|
limit = le16_to_cpu(rt ? n->pri_ctrl_cap.vifrsm : n->pri_ctrl_cap.vqfrsm);
|
|
if (nr > limit) {
|
|
return NVME_INVALID_NUM_RESOURCES | NVME_DNR;
|
|
}
|
|
|
|
nvme_get_virt_res_num(n, rt, &num_total, &num_prim, &num_sec);
|
|
num_free = num_total - num_prim - num_sec;
|
|
diff = nr - le16_to_cpu(rt ? sctrl->nvi : sctrl->nvq);
|
|
|
|
if (diff > num_free) {
|
|
return NVME_INVALID_RESOURCE_ID | NVME_DNR;
|
|
}
|
|
|
|
nvme_update_virt_res(n, sctrl, rt, nr);
|
|
req->cqe.result = cpu_to_le32(nr);
|
|
|
|
return req->status;
|
|
}
|
|
|
|
static uint16_t nvme_virt_set_state(NvmeCtrl *n, uint16_t cntlid, bool online)
|
|
{
|
|
PCIDevice *pci = PCI_DEVICE(n);
|
|
NvmeCtrl *sn = NULL;
|
|
NvmeSecCtrlEntry *sctrl;
|
|
int vf_index;
|
|
|
|
sctrl = nvme_sctrl_for_cntlid(n, cntlid);
|
|
if (!sctrl) {
|
|
return NVME_INVALID_CTRL_ID | NVME_DNR;
|
|
}
|
|
|
|
if (!pci_is_vf(pci)) {
|
|
vf_index = le16_to_cpu(sctrl->vfn) - 1;
|
|
sn = NVME(pcie_sriov_get_vf_at_index(pci, vf_index));
|
|
}
|
|
|
|
if (online) {
|
|
if (!sctrl->nvi || (le16_to_cpu(sctrl->nvq) < 2) || !sn) {
|
|
return NVME_INVALID_SEC_CTRL_STATE | NVME_DNR;
|
|
}
|
|
|
|
if (!sctrl->scs) {
|
|
sctrl->scs = 0x1;
|
|
nvme_ctrl_reset(sn, NVME_RESET_FUNCTION);
|
|
}
|
|
} else {
|
|
nvme_update_virt_res(n, sctrl, NVME_VIRT_RES_INTERRUPT, 0);
|
|
nvme_update_virt_res(n, sctrl, NVME_VIRT_RES_QUEUE, 0);
|
|
|
|
if (sctrl->scs) {
|
|
sctrl->scs = 0x0;
|
|
if (sn) {
|
|
nvme_ctrl_reset(sn, NVME_RESET_FUNCTION);
|
|
}
|
|
}
|
|
}
|
|
|
|
return NVME_SUCCESS;
|
|
}
|
|
|
|
static uint16_t nvme_virt_mngmt(NvmeCtrl *n, NvmeRequest *req)
|
|
{
|
|
uint32_t dw10 = le32_to_cpu(req->cmd.cdw10);
|
|
uint32_t dw11 = le32_to_cpu(req->cmd.cdw11);
|
|
uint8_t act = dw10 & 0xf;
|
|
uint8_t rt = (dw10 >> 8) & 0x7;
|
|
uint16_t cntlid = (dw10 >> 16) & 0xffff;
|
|
int nr = dw11 & 0xffff;
|
|
|
|
trace_pci_nvme_virt_mngmt(nvme_cid(req), act, cntlid, rt ? "VI" : "VQ", nr);
|
|
|
|
if (rt != NVME_VIRT_RES_QUEUE && rt != NVME_VIRT_RES_INTERRUPT) {
|
|
return NVME_INVALID_RESOURCE_ID | NVME_DNR;
|
|
}
|
|
|
|
switch (act) {
|
|
case NVME_VIRT_MNGMT_ACTION_SEC_ASSIGN:
|
|
return nvme_assign_virt_res_to_sec(n, req, cntlid, rt, nr);
|
|
case NVME_VIRT_MNGMT_ACTION_PRM_ALLOC:
|
|
return nvme_assign_virt_res_to_prim(n, req, cntlid, rt, nr);
|
|
case NVME_VIRT_MNGMT_ACTION_SEC_ONLINE:
|
|
return nvme_virt_set_state(n, cntlid, true);
|
|
case NVME_VIRT_MNGMT_ACTION_SEC_OFFLINE:
|
|
return nvme_virt_set_state(n, cntlid, false);
|
|
default:
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
}
|
|
|
|
static uint16_t nvme_dbbuf_config(NvmeCtrl *n, const NvmeRequest *req)
|
|
{
|
|
PCIDevice *pci = PCI_DEVICE(n);
|
|
uint64_t dbs_addr = le64_to_cpu(req->cmd.dptr.prp1);
|
|
uint64_t eis_addr = le64_to_cpu(req->cmd.dptr.prp2);
|
|
int i;
|
|
|
|
/* Address should be page aligned */
|
|
if (dbs_addr & (n->page_size - 1) || eis_addr & (n->page_size - 1)) {
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
|
|
/* Save shadow buffer base addr for use during queue creation */
|
|
n->dbbuf_dbs = dbs_addr;
|
|
n->dbbuf_eis = eis_addr;
|
|
n->dbbuf_enabled = true;
|
|
|
|
for (i = 0; i < n->params.max_ioqpairs + 1; i++) {
|
|
NvmeSQueue *sq = n->sq[i];
|
|
NvmeCQueue *cq = n->cq[i];
|
|
|
|
if (sq) {
|
|
/*
|
|
* CAP.DSTRD is 0, so offset of ith sq db_addr is (i<<3)
|
|
* nvme_process_db() uses this hard-coded way to calculate
|
|
* doorbell offsets. Be consistent with that here.
|
|
*/
|
|
sq->db_addr = dbs_addr + (i << 3);
|
|
sq->ei_addr = eis_addr + (i << 3);
|
|
stl_le_pci_dma(pci, sq->db_addr, sq->tail, MEMTXATTRS_UNSPECIFIED);
|
|
|
|
if (n->params.ioeventfd && sq->sqid != 0) {
|
|
if (!nvme_init_sq_ioeventfd(sq)) {
|
|
sq->ioeventfd_enabled = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (cq) {
|
|
/* CAP.DSTRD is 0, so offset of ith cq db_addr is (i<<3)+(1<<2) */
|
|
cq->db_addr = dbs_addr + (i << 3) + (1 << 2);
|
|
cq->ei_addr = eis_addr + (i << 3) + (1 << 2);
|
|
stl_le_pci_dma(pci, cq->db_addr, cq->head, MEMTXATTRS_UNSPECIFIED);
|
|
|
|
if (n->params.ioeventfd && cq->cqid != 0) {
|
|
if (!nvme_init_cq_ioeventfd(cq)) {
|
|
cq->ioeventfd_enabled = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
trace_pci_nvme_dbbuf_config(dbs_addr, eis_addr);
|
|
|
|
return NVME_SUCCESS;
|
|
}
|
|
|
|
static uint16_t nvme_directive_send(NvmeCtrl *n, NvmeRequest *req)
|
|
{
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
|
|
static uint16_t nvme_directive_receive(NvmeCtrl *n, NvmeRequest *req)
|
|
{
|
|
NvmeNamespace *ns;
|
|
uint32_t dw10 = le32_to_cpu(req->cmd.cdw10);
|
|
uint32_t dw11 = le32_to_cpu(req->cmd.cdw11);
|
|
uint32_t nsid = le32_to_cpu(req->cmd.nsid);
|
|
uint8_t doper, dtype;
|
|
uint32_t numd, trans_len;
|
|
NvmeDirectiveIdentify id = {
|
|
.supported = 1 << NVME_DIRECTIVE_IDENTIFY,
|
|
.enabled = 1 << NVME_DIRECTIVE_IDENTIFY,
|
|
};
|
|
|
|
numd = dw10 + 1;
|
|
doper = dw11 & 0xff;
|
|
dtype = (dw11 >> 8) & 0xff;
|
|
|
|
trans_len = MIN(sizeof(NvmeDirectiveIdentify), numd << 2);
|
|
|
|
if (nsid == NVME_NSID_BROADCAST || dtype != NVME_DIRECTIVE_IDENTIFY ||
|
|
doper != NVME_DIRECTIVE_RETURN_PARAMS) {
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
|
|
ns = nvme_ns(n, nsid);
|
|
if (!ns) {
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
|
|
switch (dtype) {
|
|
case NVME_DIRECTIVE_IDENTIFY:
|
|
switch (doper) {
|
|
case NVME_DIRECTIVE_RETURN_PARAMS:
|
|
if (ns->endgrp && ns->endgrp->fdp.enabled) {
|
|
id.supported |= 1 << NVME_DIRECTIVE_DATA_PLACEMENT;
|
|
id.enabled |= 1 << NVME_DIRECTIVE_DATA_PLACEMENT;
|
|
id.persistent |= 1 << NVME_DIRECTIVE_DATA_PLACEMENT;
|
|
}
|
|
|
|
return nvme_c2h(n, (uint8_t *)&id, trans_len, req);
|
|
|
|
default:
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
|
|
default:
|
|
return NVME_INVALID_FIELD;
|
|
}
|
|
}
|
|
|
|
static uint16_t nvme_admin_cmd(NvmeCtrl *n, NvmeRequest *req)
|
|
{
|
|
trace_pci_nvme_admin_cmd(nvme_cid(req), nvme_sqid(req), req->cmd.opcode,
|
|
nvme_adm_opc_str(req->cmd.opcode));
|
|
|
|
if (!(nvme_cse_acs[req->cmd.opcode] & NVME_CMD_EFF_CSUPP)) {
|
|
trace_pci_nvme_err_invalid_admin_opc(req->cmd.opcode);
|
|
return NVME_INVALID_OPCODE | NVME_DNR;
|
|
}
|
|
|
|
/* SGLs shall not be used for Admin commands in NVMe over PCIe */
|
|
if (NVME_CMD_FLAGS_PSDT(req->cmd.flags) != NVME_PSDT_PRP) {
|
|
return NVME_INVALID_FIELD | NVME_DNR;
|
|
}
|
|
|
|
if (NVME_CMD_FLAGS_FUSE(req->cmd.flags)) {
|
|
return NVME_INVALID_FIELD;
|
|
}
|
|
|
|
switch (req->cmd.opcode) {
|
|
case NVME_ADM_CMD_DELETE_SQ:
|
|
return nvme_del_sq(n, req);
|
|
case NVME_ADM_CMD_CREATE_SQ:
|
|
return nvme_create_sq(n, req);
|
|
case NVME_ADM_CMD_GET_LOG_PAGE:
|
|
return nvme_get_log(n, req);
|
|
case NVME_ADM_CMD_DELETE_CQ:
|
|
return nvme_del_cq(n, req);
|
|
case NVME_ADM_CMD_CREATE_CQ:
|
|
return nvme_create_cq(n, req);
|
|
case NVME_ADM_CMD_IDENTIFY:
|
|
return nvme_identify(n, req);
|
|
case NVME_ADM_CMD_ABORT:
|
|
return nvme_abort(n, req);
|
|
case NVME_ADM_CMD_SET_FEATURES:
|
|
return nvme_set_feature(n, req);
|
|
case NVME_ADM_CMD_GET_FEATURES:
|
|
return nvme_get_feature(n, req);
|
|
case NVME_ADM_CMD_ASYNC_EV_REQ:
|
|
return nvme_aer(n, req);
|
|
case NVME_ADM_CMD_NS_ATTACHMENT:
|
|
return nvme_ns_attachment(n, req);
|
|
case NVME_ADM_CMD_VIRT_MNGMT:
|
|
return nvme_virt_mngmt(n, req);
|
|
case NVME_ADM_CMD_DBBUF_CONFIG:
|
|
return nvme_dbbuf_config(n, req);
|
|
case NVME_ADM_CMD_FORMAT_NVM:
|
|
return nvme_format(n, req);
|
|
case NVME_ADM_CMD_DIRECTIVE_SEND:
|
|
return nvme_directive_send(n, req);
|
|
case NVME_ADM_CMD_DIRECTIVE_RECV:
|
|
return nvme_directive_receive(n, req);
|
|
default:
|
|
g_assert_not_reached();
|
|
}
|
|
|
|
return NVME_INVALID_OPCODE | NVME_DNR;
|
|
}
|
|
|
|
static void nvme_update_sq_eventidx(const NvmeSQueue *sq)
|
|
{
|
|
trace_pci_nvme_update_sq_eventidx(sq->sqid, sq->tail);
|
|
|
|
stl_le_pci_dma(PCI_DEVICE(sq->ctrl), sq->ei_addr, sq->tail,
|
|
MEMTXATTRS_UNSPECIFIED);
|
|
}
|
|
|
|
static void nvme_update_sq_tail(NvmeSQueue *sq)
|
|
{
|
|
ldl_le_pci_dma(PCI_DEVICE(sq->ctrl), sq->db_addr, &sq->tail,
|
|
MEMTXATTRS_UNSPECIFIED);
|
|
|
|
trace_pci_nvme_update_sq_tail(sq->sqid, sq->tail);
|
|
}
|
|
|
|
#define NVME_ATOMIC_NO_START 0
|
|
#define NVME_ATOMIC_START_ATOMIC 1
|
|
#define NVME_ATOMIC_START_NONATOMIC 2
|
|
|
|
static int nvme_atomic_write_check(NvmeCtrl *n, NvmeCmd *cmd,
|
|
NvmeAtomic *atomic)
|
|
{
|
|
NvmeRwCmd *rw = (NvmeRwCmd *)cmd;
|
|
uint64_t slba = le64_to_cpu(rw->slba);
|
|
uint32_t nlb = (uint32_t)le16_to_cpu(rw->nlb);
|
|
uint64_t elba = slba + nlb;
|
|
bool cmd_atomic_wr = true;
|
|
int i;
|
|
|
|
if ((cmd->opcode == NVME_CMD_READ) || ((cmd->opcode == NVME_CMD_WRITE) &&
|
|
((rw->nlb + 1) > atomic->atomic_max_write_size))) {
|
|
cmd_atomic_wr = false;
|
|
}
|
|
|
|
/*
|
|
* Walk the queues to see if there are any atomic conflicts.
|
|
*/
|
|
for (i = 1; i < n->params.max_ioqpairs + 1; i++) {
|
|
NvmeSQueue *sq;
|
|
NvmeRequest *req;
|
|
NvmeRwCmd *req_rw;
|
|
uint64_t req_slba;
|
|
uint32_t req_nlb;
|
|
uint64_t req_elba;
|
|
|
|
sq = n->sq[i];
|
|
if (!sq) {
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* Walk all the requests on a given queue.
|
|
*/
|
|
QTAILQ_FOREACH(req, &sq->out_req_list, entry) {
|
|
req_rw = (NvmeRwCmd *)&req->cmd;
|
|
|
|
if (((req_rw->opcode == NVME_CMD_WRITE) ||
|
|
(req_rw->opcode == NVME_CMD_READ)) &&
|
|
(cmd->nsid == req->ns->params.nsid)) {
|
|
req_slba = le64_to_cpu(req_rw->slba);
|
|
req_nlb = (uint32_t)le16_to_cpu(req_rw->nlb);
|
|
req_elba = req_slba + req_nlb;
|
|
|
|
if (cmd_atomic_wr) {
|
|
if ((elba >= req_slba) && (slba <= req_elba)) {
|
|
return NVME_ATOMIC_NO_START;
|
|
}
|
|
} else {
|
|
if (req->atomic_write && ((elba >= req_slba) &&
|
|
(slba <= req_elba))) {
|
|
return NVME_ATOMIC_NO_START;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (cmd_atomic_wr) {
|
|
return NVME_ATOMIC_START_ATOMIC;
|
|
}
|
|
return NVME_ATOMIC_START_NONATOMIC;
|
|
}
|
|
|
|
static NvmeAtomic *nvme_get_atomic(NvmeCtrl *n, NvmeCmd *cmd)
|
|
{
|
|
if (n->atomic.atomic_writes) {
|
|
return &n->atomic;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static void nvme_process_sq(void *opaque)
|
|
{
|
|
NvmeSQueue *sq = opaque;
|
|
NvmeCtrl *n = sq->ctrl;
|
|
NvmeCQueue *cq = n->cq[sq->cqid];
|
|
|
|
uint16_t status;
|
|
hwaddr addr;
|
|
NvmeCmd cmd;
|
|
NvmeRequest *req;
|
|
|
|
if (n->dbbuf_enabled) {
|
|
nvme_update_sq_tail(sq);
|
|
}
|
|
|
|
while (!(nvme_sq_empty(sq) || QTAILQ_EMPTY(&sq->req_list))) {
|
|
NvmeAtomic *atomic;
|
|
bool cmd_is_atomic;
|
|
|
|
addr = sq->dma_addr + (sq->head << NVME_SQES);
|
|
if (nvme_addr_read(n, addr, (void *)&cmd, sizeof(cmd))) {
|
|
trace_pci_nvme_err_addr_read(addr);
|
|
trace_pci_nvme_err_cfs();
|
|
stl_le_p(&n->bar.csts, NVME_CSTS_FAILED);
|
|
break;
|
|
}
|
|
|
|
atomic = nvme_get_atomic(n, &cmd);
|
|
|
|
cmd_is_atomic = false;
|
|
if (sq->sqid && atomic) {
|
|
int ret;
|
|
|
|
ret = nvme_atomic_write_check(n, &cmd, atomic);
|
|
switch (ret) {
|
|
case NVME_ATOMIC_NO_START:
|
|
qemu_bh_schedule(sq->bh);
|
|
return;
|
|
case NVME_ATOMIC_START_ATOMIC:
|
|
cmd_is_atomic = true;
|
|
break;
|
|
case NVME_ATOMIC_START_NONATOMIC:
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
nvme_inc_sq_head(sq);
|
|
|
|
req = QTAILQ_FIRST(&sq->req_list);
|
|
QTAILQ_REMOVE(&sq->req_list, req, entry);
|
|
QTAILQ_INSERT_TAIL(&sq->out_req_list, req, entry);
|
|
nvme_req_clear(req);
|
|
req->cqe.cid = cmd.cid;
|
|
memcpy(&req->cmd, &cmd, sizeof(NvmeCmd));
|
|
|
|
if (sq->sqid && atomic) {
|
|
req->atomic_write = cmd_is_atomic;
|
|
}
|
|
|
|
status = sq->sqid ? nvme_io_cmd(n, req) :
|
|
nvme_admin_cmd(n, req);
|
|
if (status != NVME_NO_COMPLETE) {
|
|
req->status = status;
|
|
nvme_enqueue_req_completion(cq, req);
|
|
}
|
|
|
|
if (n->dbbuf_enabled) {
|
|
nvme_update_sq_eventidx(sq);
|
|
nvme_update_sq_tail(sq);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void nvme_update_msixcap_ts(PCIDevice *pci_dev, uint32_t table_size)
|
|
{
|
|
uint8_t *config;
|
|
|
|
if (!msix_present(pci_dev)) {
|
|
return;
|
|
}
|
|
|
|
assert(table_size > 0 && table_size <= pci_dev->msix_entries_nr);
|
|
|
|
config = pci_dev->config + pci_dev->msix_cap;
|
|
pci_set_word_by_mask(config + PCI_MSIX_FLAGS, PCI_MSIX_FLAGS_QSIZE,
|
|
table_size - 1);
|
|
}
|
|
|
|
static void nvme_activate_virt_res(NvmeCtrl *n)
|
|
{
|
|
PCIDevice *pci_dev = PCI_DEVICE(n);
|
|
NvmePriCtrlCap *cap = &n->pri_ctrl_cap;
|
|
NvmeSecCtrlEntry *sctrl;
|
|
|
|
/* -1 to account for the admin queue */
|
|
if (pci_is_vf(pci_dev)) {
|
|
sctrl = nvme_sctrl(n);
|
|
cap->vqprt = sctrl->nvq;
|
|
cap->viprt = sctrl->nvi;
|
|
n->conf_ioqpairs = sctrl->nvq ? le16_to_cpu(sctrl->nvq) - 1 : 0;
|
|
n->conf_msix_qsize = sctrl->nvi ? le16_to_cpu(sctrl->nvi) : 1;
|
|
} else {
|
|
cap->vqrfap = n->next_pri_ctrl_cap.vqrfap;
|
|
cap->virfap = n->next_pri_ctrl_cap.virfap;
|
|
n->conf_ioqpairs = le16_to_cpu(cap->vqprt) +
|
|
le16_to_cpu(cap->vqrfap) - 1;
|
|
n->conf_msix_qsize = le16_to_cpu(cap->viprt) +
|
|
le16_to_cpu(cap->virfap);
|
|
}
|
|
}
|
|
|
|
static void nvme_ctrl_reset(NvmeCtrl *n, NvmeResetType rst)
|
|
{
|
|
PCIDevice *pci_dev = PCI_DEVICE(n);
|
|
NvmeSecCtrlEntry *sctrl;
|
|
NvmeNamespace *ns;
|
|
int i;
|
|
|
|
for (i = 1; i <= NVME_MAX_NAMESPACES; i++) {
|
|
ns = nvme_ns(n, i);
|
|
if (!ns) {
|
|
continue;
|
|
}
|
|
|
|
nvme_ns_drain(ns);
|
|
}
|
|
|
|
for (i = 0; i < n->params.max_ioqpairs + 1; i++) {
|
|
if (n->sq[i] != NULL) {
|
|
nvme_free_sq(n->sq[i], n);
|
|
}
|
|
}
|
|
for (i = 0; i < n->params.max_ioqpairs + 1; i++) {
|
|
if (n->cq[i] != NULL) {
|
|
nvme_free_cq(n->cq[i], n);
|
|
}
|
|
}
|
|
|
|
while (!QTAILQ_EMPTY(&n->aer_queue)) {
|
|
NvmeAsyncEvent *event = QTAILQ_FIRST(&n->aer_queue);
|
|
QTAILQ_REMOVE(&n->aer_queue, event, entry);
|
|
g_free(event);
|
|
}
|
|
|
|
if (n->params.sriov_max_vfs) {
|
|
if (!pci_is_vf(pci_dev)) {
|
|
for (i = 0; i < n->nr_sec_ctrls; i++) {
|
|
sctrl = &n->sec_ctrl_list[i];
|
|
nvme_virt_set_state(n, le16_to_cpu(sctrl->scid), false);
|
|
}
|
|
}
|
|
|
|
if (rst != NVME_RESET_CONTROLLER) {
|
|
nvme_activate_virt_res(n);
|
|
}
|
|
}
|
|
|
|
n->aer_queued = 0;
|
|
n->aer_mask = 0;
|
|
n->outstanding_aers = 0;
|
|
n->qs_created = false;
|
|
|
|
n->dn = n->params.atomic_dn; /* Set Disable Normal */
|
|
|
|
nvme_update_msixcap_ts(pci_dev, n->conf_msix_qsize);
|
|
|
|
if (pci_is_vf(pci_dev)) {
|
|
sctrl = nvme_sctrl(n);
|
|
|
|
stl_le_p(&n->bar.csts, sctrl->scs ? 0 : NVME_CSTS_FAILED);
|
|
} else {
|
|
stl_le_p(&n->bar.csts, 0);
|
|
}
|
|
|
|
stl_le_p(&n->bar.intms, 0);
|
|
stl_le_p(&n->bar.intmc, 0);
|
|
stl_le_p(&n->bar.cc, 0);
|
|
|
|
n->dbbuf_dbs = 0;
|
|
n->dbbuf_eis = 0;
|
|
n->dbbuf_enabled = false;
|
|
}
|
|
|
|
static void nvme_ctrl_shutdown(NvmeCtrl *n)
|
|
{
|
|
NvmeNamespace *ns;
|
|
int i;
|
|
|
|
if (n->pmr.dev) {
|
|
memory_region_msync(&n->pmr.dev->mr, 0, n->pmr.dev->size);
|
|
}
|
|
|
|
for (i = 1; i <= NVME_MAX_NAMESPACES; i++) {
|
|
ns = nvme_ns(n, i);
|
|
if (!ns) {
|
|
continue;
|
|
}
|
|
|
|
nvme_ns_shutdown(ns);
|
|
}
|
|
}
|
|
|
|
static void nvme_select_iocs(NvmeCtrl *n)
|
|
{
|
|
NvmeNamespace *ns;
|
|
int i;
|
|
|
|
for (i = 1; i <= NVME_MAX_NAMESPACES; i++) {
|
|
ns = nvme_ns(n, i);
|
|
if (!ns) {
|
|
continue;
|
|
}
|
|
|
|
nvme_select_iocs_ns(n, ns);
|
|
}
|
|
}
|
|
|
|
static int nvme_start_ctrl(NvmeCtrl *n)
|
|
{
|
|
uint64_t cap = ldq_le_p(&n->bar.cap);
|
|
uint32_t cc = ldl_le_p(&n->bar.cc);
|
|
uint32_t aqa = ldl_le_p(&n->bar.aqa);
|
|
uint64_t asq = ldq_le_p(&n->bar.asq);
|
|
uint64_t acq = ldq_le_p(&n->bar.acq);
|
|
uint32_t page_bits = NVME_CC_MPS(cc) + 12;
|
|
uint32_t page_size = 1 << page_bits;
|
|
NvmeSecCtrlEntry *sctrl = nvme_sctrl(n);
|
|
|
|
if (pci_is_vf(PCI_DEVICE(n)) && !sctrl->scs) {
|
|
trace_pci_nvme_err_startfail_virt_state(le16_to_cpu(sctrl->nvi),
|
|
le16_to_cpu(sctrl->nvq));
|
|
return -1;
|
|
}
|
|
if (unlikely(n->cq[0])) {
|
|
trace_pci_nvme_err_startfail_cq();
|
|
return -1;
|
|
}
|
|
if (unlikely(n->sq[0])) {
|
|
trace_pci_nvme_err_startfail_sq();
|
|
return -1;
|
|
}
|
|
if (unlikely(asq & (page_size - 1))) {
|
|
trace_pci_nvme_err_startfail_asq_misaligned(asq);
|
|
return -1;
|
|
}
|
|
if (unlikely(acq & (page_size - 1))) {
|
|
trace_pci_nvme_err_startfail_acq_misaligned(acq);
|
|
return -1;
|
|
}
|
|
if (unlikely(!(NVME_CAP_CSS(cap) & (1 << NVME_CC_CSS(cc))))) {
|
|
trace_pci_nvme_err_startfail_css(NVME_CC_CSS(cc));
|
|
return -1;
|
|
}
|
|
if (unlikely(NVME_CC_MPS(cc) < NVME_CAP_MPSMIN(cap))) {
|
|
trace_pci_nvme_err_startfail_page_too_small(
|
|
NVME_CC_MPS(cc),
|
|
NVME_CAP_MPSMIN(cap));
|
|
return -1;
|
|
}
|
|
if (unlikely(NVME_CC_MPS(cc) >
|
|
NVME_CAP_MPSMAX(cap))) {
|
|
trace_pci_nvme_err_startfail_page_too_large(
|
|
NVME_CC_MPS(cc),
|
|
NVME_CAP_MPSMAX(cap));
|
|
return -1;
|
|
}
|
|
if (unlikely(!NVME_AQA_ASQS(aqa))) {
|
|
trace_pci_nvme_err_startfail_asqent_sz_zero();
|
|
return -1;
|
|
}
|
|
if (unlikely(!NVME_AQA_ACQS(aqa))) {
|
|
trace_pci_nvme_err_startfail_acqent_sz_zero();
|
|
return -1;
|
|
}
|
|
|
|
n->page_bits = page_bits;
|
|
n->page_size = page_size;
|
|
n->max_prp_ents = n->page_size / sizeof(uint64_t);
|
|
nvme_init_cq(&n->admin_cq, n, acq, 0, 0, NVME_AQA_ACQS(aqa) + 1, 1);
|
|
nvme_init_sq(&n->admin_sq, n, asq, 0, 0, NVME_AQA_ASQS(aqa) + 1);
|
|
|
|
nvme_set_timestamp(n, 0ULL);
|
|
|
|
nvme_select_iocs(n);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void nvme_cmb_enable_regs(NvmeCtrl *n)
|
|
{
|
|
uint32_t cmbloc = ldl_le_p(&n->bar.cmbloc);
|
|
uint32_t cmbsz = ldl_le_p(&n->bar.cmbsz);
|
|
|
|
NVME_CMBLOC_SET_CDPCILS(cmbloc, 1);
|
|
NVME_CMBLOC_SET_CDPMLS(cmbloc, 1);
|
|
NVME_CMBLOC_SET_BIR(cmbloc, NVME_CMB_BIR);
|
|
stl_le_p(&n->bar.cmbloc, cmbloc);
|
|
|
|
NVME_CMBSZ_SET_SQS(cmbsz, 1);
|
|
NVME_CMBSZ_SET_CQS(cmbsz, 0);
|
|
NVME_CMBSZ_SET_LISTS(cmbsz, 1);
|
|
NVME_CMBSZ_SET_RDS(cmbsz, 1);
|
|
NVME_CMBSZ_SET_WDS(cmbsz, 1);
|
|
NVME_CMBSZ_SET_SZU(cmbsz, 2); /* MBs */
|
|
NVME_CMBSZ_SET_SZ(cmbsz, n->params.cmb_size_mb);
|
|
stl_le_p(&n->bar.cmbsz, cmbsz);
|
|
}
|
|
|
|
static void nvme_write_bar(NvmeCtrl *n, hwaddr offset, uint64_t data,
|
|
unsigned size)
|
|
{
|
|
PCIDevice *pci = PCI_DEVICE(n);
|
|
uint64_t cap = ldq_le_p(&n->bar.cap);
|
|
uint32_t cc = ldl_le_p(&n->bar.cc);
|
|
uint32_t intms = ldl_le_p(&n->bar.intms);
|
|
uint32_t csts = ldl_le_p(&n->bar.csts);
|
|
uint32_t pmrsts = ldl_le_p(&n->bar.pmrsts);
|
|
|
|
if (unlikely(offset & (sizeof(uint32_t) - 1))) {
|
|
NVME_GUEST_ERR(pci_nvme_ub_mmiowr_misaligned32,
|
|
"MMIO write not 32-bit aligned,"
|
|
" offset=0x%"PRIx64"", offset);
|
|
/* should be ignored, fall through for now */
|
|
}
|
|
|
|
if (unlikely(size < sizeof(uint32_t))) {
|
|
NVME_GUEST_ERR(pci_nvme_ub_mmiowr_toosmall,
|
|
"MMIO write smaller than 32-bits,"
|
|
" offset=0x%"PRIx64", size=%u",
|
|
offset, size);
|
|
/* should be ignored, fall through for now */
|
|
}
|
|
|
|
switch (offset) {
|
|
case NVME_REG_INTMS:
|
|
if (unlikely(msix_enabled(pci))) {
|
|
NVME_GUEST_ERR(pci_nvme_ub_mmiowr_intmask_with_msix,
|
|
"undefined access to interrupt mask set"
|
|
" when MSI-X is enabled");
|
|
/* should be ignored, fall through for now */
|
|
}
|
|
intms |= data;
|
|
stl_le_p(&n->bar.intms, intms);
|
|
n->bar.intmc = n->bar.intms;
|
|
trace_pci_nvme_mmio_intm_set(data & 0xffffffff, intms);
|
|
nvme_irq_check(n);
|
|
break;
|
|
case NVME_REG_INTMC:
|
|
if (unlikely(msix_enabled(pci))) {
|
|
NVME_GUEST_ERR(pci_nvme_ub_mmiowr_intmask_with_msix,
|
|
"undefined access to interrupt mask clr"
|
|
" when MSI-X is enabled");
|
|
/* should be ignored, fall through for now */
|
|
}
|
|
intms &= ~data;
|
|
stl_le_p(&n->bar.intms, intms);
|
|
n->bar.intmc = n->bar.intms;
|
|
trace_pci_nvme_mmio_intm_clr(data & 0xffffffff, intms);
|
|
nvme_irq_check(n);
|
|
break;
|
|
case NVME_REG_CC:
|
|
stl_le_p(&n->bar.cc, data);
|
|
|
|
trace_pci_nvme_mmio_cfg(data & 0xffffffff);
|
|
|
|
if (NVME_CC_SHN(data) && !(NVME_CC_SHN(cc))) {
|
|
trace_pci_nvme_mmio_shutdown_set();
|
|
nvme_ctrl_shutdown(n);
|
|
csts &= ~(CSTS_SHST_MASK << CSTS_SHST_SHIFT);
|
|
csts |= NVME_CSTS_SHST_COMPLETE;
|
|
} else if (!NVME_CC_SHN(data) && NVME_CC_SHN(cc)) {
|
|
trace_pci_nvme_mmio_shutdown_cleared();
|
|
csts &= ~(CSTS_SHST_MASK << CSTS_SHST_SHIFT);
|
|
}
|
|
|
|
if (NVME_CC_EN(data) && !NVME_CC_EN(cc)) {
|
|
if (unlikely(nvme_start_ctrl(n))) {
|
|
trace_pci_nvme_err_startfail();
|
|
csts = NVME_CSTS_FAILED;
|
|
} else {
|
|
trace_pci_nvme_mmio_start_success();
|
|
csts = NVME_CSTS_READY;
|
|
}
|
|
} else if (!NVME_CC_EN(data) && NVME_CC_EN(cc)) {
|
|
trace_pci_nvme_mmio_stopped();
|
|
nvme_ctrl_reset(n, NVME_RESET_CONTROLLER);
|
|
|
|
break;
|
|
}
|
|
|
|
stl_le_p(&n->bar.csts, csts);
|
|
|
|
break;
|
|
case NVME_REG_CSTS:
|
|
if (data & (1 << 4)) {
|
|
NVME_GUEST_ERR(pci_nvme_ub_mmiowr_ssreset_w1c_unsupported,
|
|
"attempted to W1C CSTS.NSSRO"
|
|
" but CAP.NSSRS is zero (not supported)");
|
|
} else if (data != 0) {
|
|
NVME_GUEST_ERR(pci_nvme_ub_mmiowr_ro_csts,
|
|
"attempted to set a read only bit"
|
|
" of controller status");
|
|
}
|
|
break;
|
|
case NVME_REG_NSSR:
|
|
if (data == 0x4e564d65) {
|
|
trace_pci_nvme_ub_mmiowr_ssreset_unsupported();
|
|
} else {
|
|
/* The spec says that writes of other values have no effect */
|
|
return;
|
|
}
|
|
break;
|
|
case NVME_REG_AQA:
|
|
stl_le_p(&n->bar.aqa, data);
|
|
trace_pci_nvme_mmio_aqattr(data & 0xffffffff);
|
|
break;
|
|
case NVME_REG_ASQ:
|
|
stn_le_p(&n->bar.asq, size, data);
|
|
trace_pci_nvme_mmio_asqaddr(data);
|
|
break;
|
|
case NVME_REG_ASQ + 4:
|
|
stl_le_p((uint8_t *)&n->bar.asq + 4, data);
|
|
trace_pci_nvme_mmio_asqaddr_hi(data, ldq_le_p(&n->bar.asq));
|
|
break;
|
|
case NVME_REG_ACQ:
|
|
trace_pci_nvme_mmio_acqaddr(data);
|
|
stn_le_p(&n->bar.acq, size, data);
|
|
break;
|
|
case NVME_REG_ACQ + 4:
|
|
stl_le_p((uint8_t *)&n->bar.acq + 4, data);
|
|
trace_pci_nvme_mmio_acqaddr_hi(data, ldq_le_p(&n->bar.acq));
|
|
break;
|
|
case NVME_REG_CMBLOC:
|
|
NVME_GUEST_ERR(pci_nvme_ub_mmiowr_cmbloc_reserved,
|
|
"invalid write to reserved CMBLOC"
|
|
" when CMBSZ is zero, ignored");
|
|
return;
|
|
case NVME_REG_CMBSZ:
|
|
NVME_GUEST_ERR(pci_nvme_ub_mmiowr_cmbsz_readonly,
|
|
"invalid write to read only CMBSZ, ignored");
|
|
return;
|
|
case NVME_REG_CMBMSC:
|
|
if (!NVME_CAP_CMBS(cap)) {
|
|
return;
|
|
}
|
|
|
|
stn_le_p(&n->bar.cmbmsc, size, data);
|
|
n->cmb.cmse = false;
|
|
|
|
if (NVME_CMBMSC_CRE(data)) {
|
|
nvme_cmb_enable_regs(n);
|
|
|
|
if (NVME_CMBMSC_CMSE(data)) {
|
|
uint64_t cmbmsc = ldq_le_p(&n->bar.cmbmsc);
|
|
hwaddr cba = NVME_CMBMSC_CBA(cmbmsc) << CMBMSC_CBA_SHIFT;
|
|
if (cba + int128_get64(n->cmb.mem.size) < cba) {
|
|
uint32_t cmbsts = ldl_le_p(&n->bar.cmbsts);
|
|
NVME_CMBSTS_SET_CBAI(cmbsts, 1);
|
|
stl_le_p(&n->bar.cmbsts, cmbsts);
|
|
return;
|
|
}
|
|
|
|
n->cmb.cba = cba;
|
|
n->cmb.cmse = true;
|
|
}
|
|
} else {
|
|
n->bar.cmbsz = 0;
|
|
n->bar.cmbloc = 0;
|
|
}
|
|
|
|
return;
|
|
case NVME_REG_CMBMSC + 4:
|
|
stl_le_p((uint8_t *)&n->bar.cmbmsc + 4, data);
|
|
return;
|
|
|
|
case NVME_REG_PMRCAP:
|
|
NVME_GUEST_ERR(pci_nvme_ub_mmiowr_pmrcap_readonly,
|
|
"invalid write to PMRCAP register, ignored");
|
|
return;
|
|
case NVME_REG_PMRCTL:
|
|
if (!NVME_CAP_PMRS(cap)) {
|
|
return;
|
|
}
|
|
|
|
stl_le_p(&n->bar.pmrctl, data);
|
|
if (NVME_PMRCTL_EN(data)) {
|
|
memory_region_set_enabled(&n->pmr.dev->mr, true);
|
|
pmrsts = 0;
|
|
} else {
|
|
memory_region_set_enabled(&n->pmr.dev->mr, false);
|
|
NVME_PMRSTS_SET_NRDY(pmrsts, 1);
|
|
n->pmr.cmse = false;
|
|
}
|
|
stl_le_p(&n->bar.pmrsts, pmrsts);
|
|
return;
|
|
case NVME_REG_PMRSTS:
|
|
NVME_GUEST_ERR(pci_nvme_ub_mmiowr_pmrsts_readonly,
|
|
"invalid write to PMRSTS register, ignored");
|
|
return;
|
|
case NVME_REG_PMREBS:
|
|
NVME_GUEST_ERR(pci_nvme_ub_mmiowr_pmrebs_readonly,
|
|
"invalid write to PMREBS register, ignored");
|
|
return;
|
|
case NVME_REG_PMRSWTP:
|
|
NVME_GUEST_ERR(pci_nvme_ub_mmiowr_pmrswtp_readonly,
|
|
"invalid write to PMRSWTP register, ignored");
|
|
return;
|
|
case NVME_REG_PMRMSCL:
|
|
if (!NVME_CAP_PMRS(cap)) {
|
|
return;
|
|
}
|
|
|
|
stl_le_p(&n->bar.pmrmscl, data);
|
|
n->pmr.cmse = false;
|
|
|
|
if (NVME_PMRMSCL_CMSE(data)) {
|
|
uint64_t pmrmscu = ldl_le_p(&n->bar.pmrmscu);
|
|
hwaddr cba = pmrmscu << 32 |
|
|
(NVME_PMRMSCL_CBA(data) << PMRMSCL_CBA_SHIFT);
|
|
if (cba + int128_get64(n->pmr.dev->mr.size) < cba) {
|
|
NVME_PMRSTS_SET_CBAI(pmrsts, 1);
|
|
stl_le_p(&n->bar.pmrsts, pmrsts);
|
|
return;
|
|
}
|
|
|
|
n->pmr.cmse = true;
|
|
n->pmr.cba = cba;
|
|
}
|
|
|
|
return;
|
|
case NVME_REG_PMRMSCU:
|
|
if (!NVME_CAP_PMRS(cap)) {
|
|
return;
|
|
}
|
|
|
|
stl_le_p(&n->bar.pmrmscu, data);
|
|
return;
|
|
default:
|
|
NVME_GUEST_ERR(pci_nvme_ub_mmiowr_invalid,
|
|
"invalid MMIO write,"
|
|
" offset=0x%"PRIx64", data=%"PRIx64"",
|
|
offset, data);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static uint64_t nvme_mmio_read(void *opaque, hwaddr addr, unsigned size)
|
|
{
|
|
NvmeCtrl *n = (NvmeCtrl *)opaque;
|
|
uint8_t *ptr = (uint8_t *)&n->bar;
|
|
|
|
trace_pci_nvme_mmio_read(addr, size);
|
|
|
|
if (unlikely(addr & (sizeof(uint32_t) - 1))) {
|
|
NVME_GUEST_ERR(pci_nvme_ub_mmiord_misaligned32,
|
|
"MMIO read not 32-bit aligned,"
|
|
" offset=0x%"PRIx64"", addr);
|
|
/* should RAZ, fall through for now */
|
|
} else if (unlikely(size < sizeof(uint32_t))) {
|
|
NVME_GUEST_ERR(pci_nvme_ub_mmiord_toosmall,
|
|
"MMIO read smaller than 32-bits,"
|
|
" offset=0x%"PRIx64"", addr);
|
|
/* should RAZ, fall through for now */
|
|
}
|
|
|
|
if (addr > sizeof(n->bar) - size) {
|
|
NVME_GUEST_ERR(pci_nvme_ub_mmiord_invalid_ofs,
|
|
"MMIO read beyond last register,"
|
|
" offset=0x%"PRIx64", returning 0", addr);
|
|
|
|
return 0;
|
|
}
|
|
|
|
if (pci_is_vf(PCI_DEVICE(n)) && !nvme_sctrl(n)->scs &&
|
|
addr != NVME_REG_CSTS) {
|
|
trace_pci_nvme_err_ignored_mmio_vf_offline(addr, size);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* When PMRWBM bit 1 is set then read from
|
|
* from PMRSTS should ensure prior writes
|
|
* made it to persistent media
|
|
*/
|
|
if (addr == NVME_REG_PMRSTS &&
|
|
(NVME_PMRCAP_PMRWBM(ldl_le_p(&n->bar.pmrcap)) & 0x02)) {
|
|
memory_region_msync(&n->pmr.dev->mr, 0, n->pmr.dev->size);
|
|
}
|
|
|
|
return ldn_le_p(ptr + addr, size);
|
|
}
|
|
|
|
static void nvme_process_db(NvmeCtrl *n, hwaddr addr, int val)
|
|
{
|
|
PCIDevice *pci = PCI_DEVICE(n);
|
|
uint32_t qid;
|
|
|
|
if (unlikely(addr & ((1 << 2) - 1))) {
|
|
NVME_GUEST_ERR(pci_nvme_ub_db_wr_misaligned,
|
|
"doorbell write not 32-bit aligned,"
|
|
" offset=0x%"PRIx64", ignoring", addr);
|
|
return;
|
|
}
|
|
|
|
if (((addr - 0x1000) >> 2) & 1) {
|
|
/* Completion queue doorbell write */
|
|
|
|
uint16_t new_head = val & 0xffff;
|
|
int start_sqs;
|
|
NvmeCQueue *cq;
|
|
|
|
qid = (addr - (0x1000 + (1 << 2))) >> 3;
|
|
if (unlikely(nvme_check_cqid(n, qid))) {
|
|
NVME_GUEST_ERR(pci_nvme_ub_db_wr_invalid_cq,
|
|
"completion queue doorbell write"
|
|
" for nonexistent queue,"
|
|
" sqid=%"PRIu32", ignoring", qid);
|
|
|
|
/*
|
|
* NVM Express v1.3d, Section 4.1 state: "If host software writes
|
|
* an invalid value to the Submission Queue Tail Doorbell or
|
|
* Completion Queue Head Doorbell register and an Asynchronous Event
|
|
* Request command is outstanding, then an asynchronous event is
|
|
* posted to the Admin Completion Queue with a status code of
|
|
* Invalid Doorbell Write Value."
|
|
*
|
|
* Also note that the spec includes the "Invalid Doorbell Register"
|
|
* status code, but nowhere does it specify when to use it.
|
|
* However, it seems reasonable to use it here in a similar
|
|
* fashion.
|
|
*/
|
|
if (n->outstanding_aers) {
|
|
nvme_enqueue_event(n, NVME_AER_TYPE_ERROR,
|
|
NVME_AER_INFO_ERR_INVALID_DB_REGISTER,
|
|
NVME_LOG_ERROR_INFO);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
cq = n->cq[qid];
|
|
if (unlikely(new_head >= cq->size)) {
|
|
NVME_GUEST_ERR(pci_nvme_ub_db_wr_invalid_cqhead,
|
|
"completion queue doorbell write value"
|
|
" beyond queue size, sqid=%"PRIu32","
|
|
" new_head=%"PRIu16", ignoring",
|
|
qid, new_head);
|
|
|
|
if (n->outstanding_aers) {
|
|
nvme_enqueue_event(n, NVME_AER_TYPE_ERROR,
|
|
NVME_AER_INFO_ERR_INVALID_DB_VALUE,
|
|
NVME_LOG_ERROR_INFO);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
trace_pci_nvme_mmio_doorbell_cq(cq->cqid, new_head);
|
|
|
|
start_sqs = nvme_cq_full(cq) ? 1 : 0;
|
|
cq->head = new_head;
|
|
if (!qid && n->dbbuf_enabled) {
|
|
stl_le_pci_dma(pci, cq->db_addr, cq->head, MEMTXATTRS_UNSPECIFIED);
|
|
}
|
|
if (start_sqs) {
|
|
NvmeSQueue *sq;
|
|
QTAILQ_FOREACH(sq, &cq->sq_list, entry) {
|
|
qemu_bh_schedule(sq->bh);
|
|
}
|
|
qemu_bh_schedule(cq->bh);
|
|
}
|
|
|
|
if (cq->tail == cq->head) {
|
|
if (cq->irq_enabled) {
|
|
n->cq_pending--;
|
|
}
|
|
|
|
nvme_irq_deassert(n, cq);
|
|
}
|
|
} else {
|
|
/* Submission queue doorbell write */
|
|
|
|
uint16_t new_tail = val & 0xffff;
|
|
NvmeSQueue *sq;
|
|
|
|
qid = (addr - 0x1000) >> 3;
|
|
if (unlikely(nvme_check_sqid(n, qid))) {
|
|
NVME_GUEST_ERR(pci_nvme_ub_db_wr_invalid_sq,
|
|
"submission queue doorbell write"
|
|
" for nonexistent queue,"
|
|
" sqid=%"PRIu32", ignoring", qid);
|
|
|
|
if (n->outstanding_aers) {
|
|
nvme_enqueue_event(n, NVME_AER_TYPE_ERROR,
|
|
NVME_AER_INFO_ERR_INVALID_DB_REGISTER,
|
|
NVME_LOG_ERROR_INFO);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
sq = n->sq[qid];
|
|
if (unlikely(new_tail >= sq->size)) {
|
|
NVME_GUEST_ERR(pci_nvme_ub_db_wr_invalid_sqtail,
|
|
"submission queue doorbell write value"
|
|
" beyond queue size, sqid=%"PRIu32","
|
|
" new_tail=%"PRIu16", ignoring",
|
|
qid, new_tail);
|
|
|
|
if (n->outstanding_aers) {
|
|
nvme_enqueue_event(n, NVME_AER_TYPE_ERROR,
|
|
NVME_AER_INFO_ERR_INVALID_DB_VALUE,
|
|
NVME_LOG_ERROR_INFO);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
trace_pci_nvme_mmio_doorbell_sq(sq->sqid, new_tail);
|
|
|
|
sq->tail = new_tail;
|
|
if (!qid && n->dbbuf_enabled) {
|
|
/*
|
|
* The spec states "the host shall also update the controller's
|
|
* corresponding doorbell property to match the value of that entry
|
|
* in the Shadow Doorbell buffer."
|
|
*
|
|
* Since this context is currently a VM trap, we can safely enforce
|
|
* the requirement from the device side in case the host is
|
|
* misbehaving.
|
|
*
|
|
* Note, we shouldn't have to do this, but various drivers
|
|
* including ones that run on Linux, are not updating Admin Queues,
|
|
* so we can't trust reading it for an appropriate sq tail.
|
|
*/
|
|
stl_le_pci_dma(pci, sq->db_addr, sq->tail, MEMTXATTRS_UNSPECIFIED);
|
|
}
|
|
|
|
qemu_bh_schedule(sq->bh);
|
|
}
|
|
}
|
|
|
|
static void nvme_mmio_write(void *opaque, hwaddr addr, uint64_t data,
|
|
unsigned size)
|
|
{
|
|
NvmeCtrl *n = (NvmeCtrl *)opaque;
|
|
|
|
trace_pci_nvme_mmio_write(addr, data, size);
|
|
|
|
if (pci_is_vf(PCI_DEVICE(n)) && !nvme_sctrl(n)->scs &&
|
|
addr != NVME_REG_CSTS) {
|
|
trace_pci_nvme_err_ignored_mmio_vf_offline(addr, size);
|
|
return;
|
|
}
|
|
|
|
if (addr < sizeof(n->bar)) {
|
|
nvme_write_bar(n, addr, data, size);
|
|
} else {
|
|
nvme_process_db(n, addr, data);
|
|
}
|
|
}
|
|
|
|
static const MemoryRegionOps nvme_mmio_ops = {
|
|
.read = nvme_mmio_read,
|
|
.write = nvme_mmio_write,
|
|
.endianness = DEVICE_LITTLE_ENDIAN,
|
|
.impl = {
|
|
.min_access_size = 2,
|
|
.max_access_size = 8,
|
|
},
|
|
};
|
|
|
|
static void nvme_cmb_write(void *opaque, hwaddr addr, uint64_t data,
|
|
unsigned size)
|
|
{
|
|
NvmeCtrl *n = (NvmeCtrl *)opaque;
|
|
stn_le_p(&n->cmb.buf[addr], size, data);
|
|
}
|
|
|
|
static uint64_t nvme_cmb_read(void *opaque, hwaddr addr, unsigned size)
|
|
{
|
|
NvmeCtrl *n = (NvmeCtrl *)opaque;
|
|
return ldn_le_p(&n->cmb.buf[addr], size);
|
|
}
|
|
|
|
static const MemoryRegionOps nvme_cmb_ops = {
|
|
.read = nvme_cmb_read,
|
|
.write = nvme_cmb_write,
|
|
.endianness = DEVICE_LITTLE_ENDIAN,
|
|
.impl = {
|
|
.min_access_size = 1,
|
|
.max_access_size = 8,
|
|
},
|
|
};
|
|
|
|
static bool nvme_check_params(NvmeCtrl *n, Error **errp)
|
|
{
|
|
NvmeParams *params = &n->params;
|
|
|
|
if (params->num_queues) {
|
|
warn_report("num_queues is deprecated; please use max_ioqpairs "
|
|
"instead");
|
|
|
|
params->max_ioqpairs = params->num_queues - 1;
|
|
}
|
|
|
|
if (n->namespace.blkconf.blk && n->subsys) {
|
|
error_setg(errp, "subsystem support is unavailable with legacy "
|
|
"namespace ('drive' property)");
|
|
return false;
|
|
}
|
|
|
|
if (params->max_ioqpairs < 1 ||
|
|
params->max_ioqpairs > NVME_MAX_IOQPAIRS) {
|
|
error_setg(errp, "max_ioqpairs must be between 1 and %d",
|
|
NVME_MAX_IOQPAIRS);
|
|
return false;
|
|
}
|
|
|
|
if (params->msix_qsize < 1 ||
|
|
params->msix_qsize > PCI_MSIX_FLAGS_QSIZE + 1) {
|
|
error_setg(errp, "msix_qsize must be between 1 and %d",
|
|
PCI_MSIX_FLAGS_QSIZE + 1);
|
|
return false;
|
|
}
|
|
|
|
if (!params->serial) {
|
|
error_setg(errp, "serial property not set");
|
|
return false;
|
|
}
|
|
|
|
if (params->mqes < 1) {
|
|
error_setg(errp, "mqes property cannot be less than 1");
|
|
return false;
|
|
}
|
|
|
|
if (n->pmr.dev) {
|
|
if (params->msix_exclusive_bar) {
|
|
error_setg(errp, "not enough BARs available to enable PMR");
|
|
return false;
|
|
}
|
|
|
|
if (host_memory_backend_is_mapped(n->pmr.dev)) {
|
|
error_setg(errp, "can't use already busy memdev: %s",
|
|
object_get_canonical_path_component(OBJECT(n->pmr.dev)));
|
|
return false;
|
|
}
|
|
|
|
if (!is_power_of_2(n->pmr.dev->size)) {
|
|
error_setg(errp, "pmr backend size needs to be power of 2 in size");
|
|
return false;
|
|
}
|
|
|
|
host_memory_backend_set_mapped(n->pmr.dev, true);
|
|
}
|
|
|
|
if (n->params.zasl > n->params.mdts) {
|
|
error_setg(errp, "zoned.zasl (Zone Append Size Limit) must be less "
|
|
"than or equal to mdts (Maximum Data Transfer Size)");
|
|
return false;
|
|
}
|
|
|
|
if (!n->params.vsl) {
|
|
error_setg(errp, "vsl must be non-zero");
|
|
return false;
|
|
}
|
|
|
|
if (params->sriov_max_vfs) {
|
|
if (!n->subsys) {
|
|
error_setg(errp, "subsystem is required for the use of SR-IOV");
|
|
return false;
|
|
}
|
|
|
|
if (params->cmb_size_mb) {
|
|
error_setg(errp, "CMB is not supported with SR-IOV");
|
|
return false;
|
|
}
|
|
|
|
if (n->pmr.dev) {
|
|
error_setg(errp, "PMR is not supported with SR-IOV");
|
|
return false;
|
|
}
|
|
|
|
if (!params->sriov_vq_flexible || !params->sriov_vi_flexible) {
|
|
error_setg(errp, "both sriov_vq_flexible and sriov_vi_flexible"
|
|
" must be set for the use of SR-IOV");
|
|
return false;
|
|
}
|
|
|
|
if (params->sriov_vq_flexible < params->sriov_max_vfs * 2) {
|
|
error_setg(errp, "sriov_vq_flexible must be greater than or equal"
|
|
" to %d (sriov_max_vfs * 2)", params->sriov_max_vfs * 2);
|
|
return false;
|
|
}
|
|
|
|
if (params->max_ioqpairs < params->sriov_vq_flexible + 2) {
|
|
error_setg(errp, "(max_ioqpairs - sriov_vq_flexible) must be"
|
|
" greater than or equal to 2");
|
|
return false;
|
|
}
|
|
|
|
if (params->sriov_vi_flexible < params->sriov_max_vfs) {
|
|
error_setg(errp, "sriov_vi_flexible must be greater than or equal"
|
|
" to %d (sriov_max_vfs)", params->sriov_max_vfs);
|
|
return false;
|
|
}
|
|
|
|
if (params->msix_qsize < params->sriov_vi_flexible + 1) {
|
|
error_setg(errp, "(msix_qsize - sriov_vi_flexible) must be"
|
|
" greater than or equal to 1");
|
|
return false;
|
|
}
|
|
|
|
if (params->sriov_max_vi_per_vf &&
|
|
(params->sriov_max_vi_per_vf - 1) % NVME_VF_RES_GRANULARITY) {
|
|
error_setg(errp, "sriov_max_vi_per_vf must meet:"
|
|
" (sriov_max_vi_per_vf - 1) %% %d == 0 and"
|
|
" sriov_max_vi_per_vf >= 1", NVME_VF_RES_GRANULARITY);
|
|
return false;
|
|
}
|
|
|
|
if (params->sriov_max_vq_per_vf &&
|
|
(params->sriov_max_vq_per_vf < 2 ||
|
|
(params->sriov_max_vq_per_vf - 1) % NVME_VF_RES_GRANULARITY)) {
|
|
error_setg(errp, "sriov_max_vq_per_vf must meet:"
|
|
" (sriov_max_vq_per_vf - 1) %% %d == 0 and"
|
|
" sriov_max_vq_per_vf >= 2", NVME_VF_RES_GRANULARITY);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void nvme_init_state(NvmeCtrl *n)
|
|
{
|
|
NvmePriCtrlCap *cap = &n->pri_ctrl_cap;
|
|
NvmeSecCtrlEntry *list = n->sec_ctrl_list;
|
|
NvmeSecCtrlEntry *sctrl;
|
|
PCIDevice *pci = PCI_DEVICE(n);
|
|
NvmeAtomic *atomic = &n->atomic;
|
|
NvmeIdCtrl *id = &n->id_ctrl;
|
|
uint8_t max_vfs;
|
|
int i;
|
|
|
|
if (pci_is_vf(pci)) {
|
|
sctrl = nvme_sctrl(n);
|
|
max_vfs = 0;
|
|
n->conf_ioqpairs = sctrl->nvq ? le16_to_cpu(sctrl->nvq) - 1 : 0;
|
|
n->conf_msix_qsize = sctrl->nvi ? le16_to_cpu(sctrl->nvi) : 1;
|
|
} else {
|
|
max_vfs = n->params.sriov_max_vfs;
|
|
n->conf_ioqpairs = n->params.max_ioqpairs;
|
|
n->conf_msix_qsize = n->params.msix_qsize;
|
|
}
|
|
|
|
n->sq = g_new0(NvmeSQueue *, n->params.max_ioqpairs + 1);
|
|
n->cq = g_new0(NvmeCQueue *, n->params.max_ioqpairs + 1);
|
|
n->temperature = NVME_TEMPERATURE;
|
|
n->features.temp_thresh_hi = NVME_TEMPERATURE_WARNING;
|
|
n->starttime_ms = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL);
|
|
n->aer_reqs = g_new0(NvmeRequest *, n->params.aerl + 1);
|
|
QTAILQ_INIT(&n->aer_queue);
|
|
|
|
n->nr_sec_ctrls = max_vfs;
|
|
for (i = 0; i < max_vfs; i++) {
|
|
sctrl = &list[i];
|
|
sctrl->pcid = cpu_to_le16(n->cntlid);
|
|
sctrl->vfn = cpu_to_le16(i + 1);
|
|
}
|
|
|
|
cap->cntlid = cpu_to_le16(n->cntlid);
|
|
cap->crt = NVME_CRT_VQ | NVME_CRT_VI;
|
|
|
|
if (pci_is_vf(pci)) {
|
|
cap->vqprt = cpu_to_le16(1 + n->conf_ioqpairs);
|
|
} else {
|
|
cap->vqprt = cpu_to_le16(1 + n->params.max_ioqpairs -
|
|
n->params.sriov_vq_flexible);
|
|
cap->vqfrt = cpu_to_le32(n->params.sriov_vq_flexible);
|
|
cap->vqrfap = cap->vqfrt;
|
|
cap->vqgran = cpu_to_le16(NVME_VF_RES_GRANULARITY);
|
|
cap->vqfrsm = n->params.sriov_max_vq_per_vf ?
|
|
cpu_to_le16(n->params.sriov_max_vq_per_vf) :
|
|
cap->vqfrt / MAX(max_vfs, 1);
|
|
}
|
|
|
|
if (pci_is_vf(pci)) {
|
|
cap->viprt = cpu_to_le16(n->conf_msix_qsize);
|
|
} else {
|
|
cap->viprt = cpu_to_le16(n->params.msix_qsize -
|
|
n->params.sriov_vi_flexible);
|
|
cap->vifrt = cpu_to_le32(n->params.sriov_vi_flexible);
|
|
cap->virfap = cap->vifrt;
|
|
cap->vigran = cpu_to_le16(NVME_VF_RES_GRANULARITY);
|
|
cap->vifrsm = n->params.sriov_max_vi_per_vf ?
|
|
cpu_to_le16(n->params.sriov_max_vi_per_vf) :
|
|
cap->vifrt / MAX(max_vfs, 1);
|
|
}
|
|
|
|
/* Atomic Write */
|
|
id->awun = cpu_to_le16(n->params.atomic_awun);
|
|
id->awupf = cpu_to_le16(n->params.atomic_awupf);
|
|
n->dn = n->params.atomic_dn;
|
|
|
|
if (id->awun || id->awupf) {
|
|
if (id->awupf > id->awun) {
|
|
id->awupf = 0;
|
|
}
|
|
|
|
if (n->dn) {
|
|
atomic->atomic_max_write_size = id->awupf + 1;
|
|
} else {
|
|
atomic->atomic_max_write_size = id->awun + 1;
|
|
}
|
|
|
|
if (atomic->atomic_max_write_size == 1) {
|
|
atomic->atomic_writes = 0;
|
|
} else {
|
|
atomic->atomic_writes = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void nvme_init_cmb(NvmeCtrl *n, PCIDevice *pci_dev)
|
|
{
|
|
uint64_t cmb_size = n->params.cmb_size_mb * MiB;
|
|
uint64_t cap = ldq_le_p(&n->bar.cap);
|
|
|
|
n->cmb.buf = g_malloc0(cmb_size);
|
|
memory_region_init_io(&n->cmb.mem, OBJECT(n), &nvme_cmb_ops, n,
|
|
"nvme-cmb", cmb_size);
|
|
pci_register_bar(pci_dev, NVME_CMB_BIR,
|
|
PCI_BASE_ADDRESS_SPACE_MEMORY |
|
|
PCI_BASE_ADDRESS_MEM_TYPE_64 |
|
|
PCI_BASE_ADDRESS_MEM_PREFETCH, &n->cmb.mem);
|
|
|
|
NVME_CAP_SET_CMBS(cap, 1);
|
|
stq_le_p(&n->bar.cap, cap);
|
|
|
|
if (n->params.legacy_cmb) {
|
|
nvme_cmb_enable_regs(n);
|
|
n->cmb.cmse = true;
|
|
}
|
|
}
|
|
|
|
static void nvme_init_pmr(NvmeCtrl *n, PCIDevice *pci_dev)
|
|
{
|
|
uint32_t pmrcap = ldl_le_p(&n->bar.pmrcap);
|
|
|
|
NVME_PMRCAP_SET_RDS(pmrcap, 1);
|
|
NVME_PMRCAP_SET_WDS(pmrcap, 1);
|
|
NVME_PMRCAP_SET_BIR(pmrcap, NVME_PMR_BIR);
|
|
/* Turn on bit 1 support */
|
|
NVME_PMRCAP_SET_PMRWBM(pmrcap, 0x02);
|
|
NVME_PMRCAP_SET_CMSS(pmrcap, 1);
|
|
stl_le_p(&n->bar.pmrcap, pmrcap);
|
|
|
|
pci_register_bar(pci_dev, NVME_PMR_BIR,
|
|
PCI_BASE_ADDRESS_SPACE_MEMORY |
|
|
PCI_BASE_ADDRESS_MEM_TYPE_64 |
|
|
PCI_BASE_ADDRESS_MEM_PREFETCH, &n->pmr.dev->mr);
|
|
|
|
memory_region_set_enabled(&n->pmr.dev->mr, false);
|
|
}
|
|
|
|
static uint64_t nvme_mbar_size(unsigned total_queues, unsigned total_irqs,
|
|
unsigned *msix_table_offset,
|
|
unsigned *msix_pba_offset)
|
|
{
|
|
uint64_t bar_size, msix_table_size;
|
|
|
|
bar_size = sizeof(NvmeBar) + 2 * total_queues * NVME_DB_SIZE;
|
|
|
|
if (total_irqs == 0) {
|
|
goto out;
|
|
}
|
|
|
|
bar_size = QEMU_ALIGN_UP(bar_size, 4 * KiB);
|
|
|
|
if (msix_table_offset) {
|
|
*msix_table_offset = bar_size;
|
|
}
|
|
|
|
msix_table_size = PCI_MSIX_ENTRY_SIZE * total_irqs;
|
|
bar_size += msix_table_size;
|
|
bar_size = QEMU_ALIGN_UP(bar_size, 4 * KiB);
|
|
|
|
if (msix_pba_offset) {
|
|
*msix_pba_offset = bar_size;
|
|
}
|
|
|
|
bar_size += QEMU_ALIGN_UP(total_irqs, 64) / 8;
|
|
|
|
out:
|
|
return pow2ceil(bar_size);
|
|
}
|
|
|
|
static void nvme_init_sriov(NvmeCtrl *n, PCIDevice *pci_dev, uint16_t offset)
|
|
{
|
|
uint16_t vf_dev_id = n->params.use_intel_id ?
|
|
PCI_DEVICE_ID_INTEL_NVME : PCI_DEVICE_ID_REDHAT_NVME;
|
|
NvmePriCtrlCap *cap = &n->pri_ctrl_cap;
|
|
uint64_t bar_size = nvme_mbar_size(le16_to_cpu(cap->vqfrsm),
|
|
le16_to_cpu(cap->vifrsm),
|
|
NULL, NULL);
|
|
|
|
pcie_sriov_pf_init(pci_dev, offset, "nvme", vf_dev_id,
|
|
n->params.sriov_max_vfs, n->params.sriov_max_vfs,
|
|
NVME_VF_OFFSET, NVME_VF_STRIDE);
|
|
|
|
pcie_sriov_pf_init_vf_bar(pci_dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY |
|
|
PCI_BASE_ADDRESS_MEM_TYPE_64, bar_size);
|
|
}
|
|
|
|
static int nvme_add_pm_capability(PCIDevice *pci_dev, uint8_t offset)
|
|
{
|
|
Error *err = NULL;
|
|
int ret;
|
|
|
|
ret = pci_add_capability(pci_dev, PCI_CAP_ID_PM, offset,
|
|
PCI_PM_SIZEOF, &err);
|
|
if (err) {
|
|
error_report_err(err);
|
|
return ret;
|
|
}
|
|
|
|
pci_set_word(pci_dev->config + offset + PCI_PM_PMC,
|
|
PCI_PM_CAP_VER_1_2);
|
|
pci_set_word(pci_dev->config + offset + PCI_PM_CTRL,
|
|
PCI_PM_CTRL_NO_SOFT_RESET);
|
|
pci_set_word(pci_dev->wmask + offset + PCI_PM_CTRL,
|
|
PCI_PM_CTRL_STATE_MASK);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool pcie_doe_spdm_rsp(DOECap *doe_cap)
|
|
{
|
|
void *req = pcie_doe_get_write_mbox_ptr(doe_cap);
|
|
uint32_t req_len = pcie_doe_get_obj_len(req) * 4;
|
|
void *rsp = doe_cap->read_mbox;
|
|
uint32_t rsp_len = SPDM_SOCKET_MAX_MESSAGE_BUFFER_SIZE;
|
|
|
|
uint32_t recvd = spdm_socket_rsp(doe_cap->spdm_socket,
|
|
SPDM_SOCKET_TRANSPORT_TYPE_PCI_DOE,
|
|
req, req_len, rsp, rsp_len);
|
|
doe_cap->read_mbox_len += DIV_ROUND_UP(recvd, 4);
|
|
|
|
return recvd != 0;
|
|
}
|
|
|
|
static DOEProtocol doe_spdm_prot[] = {
|
|
{ PCI_VENDOR_ID_PCI_SIG, PCI_SIG_DOE_CMA, pcie_doe_spdm_rsp },
|
|
{ PCI_VENDOR_ID_PCI_SIG, PCI_SIG_DOE_SECURED_CMA, pcie_doe_spdm_rsp },
|
|
{ }
|
|
};
|
|
|
|
static bool nvme_init_pci(NvmeCtrl *n, PCIDevice *pci_dev, Error **errp)
|
|
{
|
|
ERRP_GUARD();
|
|
uint8_t *pci_conf = pci_dev->config;
|
|
uint64_t bar_size;
|
|
unsigned msix_table_offset = 0, msix_pba_offset = 0;
|
|
unsigned nr_vectors;
|
|
int ret;
|
|
|
|
pci_conf[PCI_INTERRUPT_PIN] = 1;
|
|
pci_config_set_prog_interface(pci_conf, 0x2);
|
|
|
|
if (n->params.use_intel_id) {
|
|
pci_config_set_vendor_id(pci_conf, PCI_VENDOR_ID_INTEL);
|
|
pci_config_set_device_id(pci_conf, PCI_DEVICE_ID_INTEL_NVME);
|
|
} else {
|
|
pci_config_set_vendor_id(pci_conf, PCI_VENDOR_ID_REDHAT);
|
|
pci_config_set_device_id(pci_conf, PCI_DEVICE_ID_REDHAT_NVME);
|
|
}
|
|
|
|
pci_config_set_class(pci_conf, PCI_CLASS_STORAGE_EXPRESS);
|
|
nvme_add_pm_capability(pci_dev, 0x60);
|
|
pcie_endpoint_cap_init(pci_dev, 0x80);
|
|
pcie_cap_flr_init(pci_dev);
|
|
if (n->params.sriov_max_vfs) {
|
|
pcie_ari_init(pci_dev, 0x100);
|
|
}
|
|
|
|
if (n->params.msix_exclusive_bar && !pci_is_vf(pci_dev)) {
|
|
bar_size = nvme_mbar_size(n->params.max_ioqpairs + 1, 0, NULL, NULL);
|
|
memory_region_init_io(&n->iomem, OBJECT(n), &nvme_mmio_ops, n, "nvme",
|
|
bar_size);
|
|
pci_register_bar(pci_dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY |
|
|
PCI_BASE_ADDRESS_MEM_TYPE_64, &n->iomem);
|
|
ret = msix_init_exclusive_bar(pci_dev, n->params.msix_qsize, 4, errp);
|
|
} else {
|
|
assert(n->params.msix_qsize >= 1);
|
|
|
|
/* add one to max_ioqpairs to account for the admin queue pair */
|
|
if (!pci_is_vf(pci_dev)) {
|
|
nr_vectors = n->params.msix_qsize;
|
|
bar_size = nvme_mbar_size(n->params.max_ioqpairs + 1,
|
|
nr_vectors, &msix_table_offset,
|
|
&msix_pba_offset);
|
|
} else {
|
|
NvmeCtrl *pn = NVME(pcie_sriov_get_pf(pci_dev));
|
|
NvmePriCtrlCap *cap = &pn->pri_ctrl_cap;
|
|
|
|
nr_vectors = le16_to_cpu(cap->vifrsm);
|
|
bar_size = nvme_mbar_size(le16_to_cpu(cap->vqfrsm), nr_vectors,
|
|
&msix_table_offset, &msix_pba_offset);
|
|
}
|
|
|
|
memory_region_init(&n->bar0, OBJECT(n), "nvme-bar0", bar_size);
|
|
memory_region_init_io(&n->iomem, OBJECT(n), &nvme_mmio_ops, n, "nvme",
|
|
msix_table_offset);
|
|
memory_region_add_subregion(&n->bar0, 0, &n->iomem);
|
|
|
|
if (pci_is_vf(pci_dev)) {
|
|
pcie_sriov_vf_register_bar(pci_dev, 0, &n->bar0);
|
|
} else {
|
|
pci_register_bar(pci_dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY |
|
|
PCI_BASE_ADDRESS_MEM_TYPE_64, &n->bar0);
|
|
}
|
|
|
|
ret = msix_init(pci_dev, nr_vectors,
|
|
&n->bar0, 0, msix_table_offset,
|
|
&n->bar0, 0, msix_pba_offset, 0, errp);
|
|
}
|
|
|
|
if (ret == -ENOTSUP) {
|
|
/* report that msix is not supported, but do not error out */
|
|
warn_report_err(*errp);
|
|
*errp = NULL;
|
|
} else if (ret < 0) {
|
|
/* propagate error to caller */
|
|
return false;
|
|
}
|
|
|
|
nvme_update_msixcap_ts(pci_dev, n->conf_msix_qsize);
|
|
|
|
pcie_cap_deverr_init(pci_dev);
|
|
|
|
/* DOE Initialisation */
|
|
if (pci_dev->spdm_port) {
|
|
uint16_t doe_offset = n->params.sriov_max_vfs ?
|
|
PCI_CONFIG_SPACE_SIZE + PCI_ARI_SIZEOF
|
|
: PCI_CONFIG_SPACE_SIZE;
|
|
|
|
pcie_doe_init(pci_dev, &pci_dev->doe_spdm, doe_offset,
|
|
doe_spdm_prot, true, 0);
|
|
|
|
pci_dev->doe_spdm.spdm_socket = spdm_socket_connect(pci_dev->spdm_port,
|
|
errp);
|
|
|
|
if (pci_dev->doe_spdm.spdm_socket < 0) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (n->params.cmb_size_mb) {
|
|
nvme_init_cmb(n, pci_dev);
|
|
}
|
|
|
|
if (n->pmr.dev) {
|
|
nvme_init_pmr(n, pci_dev);
|
|
}
|
|
|
|
if (!pci_is_vf(pci_dev) && n->params.sriov_max_vfs) {
|
|
nvme_init_sriov(n, pci_dev, 0x120);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void nvme_init_subnqn(NvmeCtrl *n)
|
|
{
|
|
NvmeSubsystem *subsys = n->subsys;
|
|
NvmeIdCtrl *id = &n->id_ctrl;
|
|
|
|
if (!subsys) {
|
|
snprintf((char *)id->subnqn, sizeof(id->subnqn),
|
|
"nqn.2019-08.org.qemu:%s", n->params.serial);
|
|
} else {
|
|
pstrcpy((char *)id->subnqn, sizeof(id->subnqn), (char*)subsys->subnqn);
|
|
}
|
|
}
|
|
|
|
static void nvme_init_ctrl(NvmeCtrl *n, PCIDevice *pci_dev)
|
|
{
|
|
NvmeIdCtrl *id = &n->id_ctrl;
|
|
uint8_t *pci_conf = pci_dev->config;
|
|
uint64_t cap = ldq_le_p(&n->bar.cap);
|
|
NvmeSecCtrlEntry *sctrl = nvme_sctrl(n);
|
|
uint32_t ctratt;
|
|
|
|
id->vid = cpu_to_le16(pci_get_word(pci_conf + PCI_VENDOR_ID));
|
|
id->ssvid = cpu_to_le16(pci_get_word(pci_conf + PCI_SUBSYSTEM_VENDOR_ID));
|
|
strpadcpy((char *)id->mn, sizeof(id->mn), "QEMU NVMe Ctrl", ' ');
|
|
strpadcpy((char *)id->fr, sizeof(id->fr), QEMU_VERSION, ' ');
|
|
strpadcpy((char *)id->sn, sizeof(id->sn), n->params.serial, ' ');
|
|
|
|
id->cntlid = cpu_to_le16(n->cntlid);
|
|
|
|
id->oaes = cpu_to_le32(NVME_OAES_NS_ATTR);
|
|
|
|
ctratt = NVME_CTRATT_ELBAS;
|
|
if (n->params.ctratt.mem) {
|
|
ctratt |= NVME_CTRATT_MEM;
|
|
}
|
|
|
|
id->rab = 6;
|
|
|
|
if (n->params.use_intel_id) {
|
|
id->ieee[0] = 0xb3;
|
|
id->ieee[1] = 0x02;
|
|
id->ieee[2] = 0x00;
|
|
} else {
|
|
id->ieee[0] = 0x00;
|
|
id->ieee[1] = 0x54;
|
|
id->ieee[2] = 0x52;
|
|
}
|
|
|
|
id->mdts = n->params.mdts;
|
|
id->ver = cpu_to_le32(NVME_SPEC_VER);
|
|
id->oacs =
|
|
cpu_to_le16(NVME_OACS_NS_MGMT | NVME_OACS_FORMAT | NVME_OACS_DBBUF |
|
|
NVME_OACS_DIRECTIVES);
|
|
id->cntrltype = 0x1;
|
|
|
|
/*
|
|
* Because the controller always completes the Abort command immediately,
|
|
* there can never be more than one concurrently executing Abort command,
|
|
* so this value is never used for anything. Note that there can easily be
|
|
* many Abort commands in the queues, but they are not considered
|
|
* "executing" until processed by nvme_abort.
|
|
*
|
|
* The specification recommends a value of 3 for Abort Command Limit (four
|
|
* concurrently outstanding Abort commands), so lets use that though it is
|
|
* inconsequential.
|
|
*/
|
|
id->acl = 3;
|
|
id->aerl = n->params.aerl;
|
|
id->frmw = (NVME_NUM_FW_SLOTS << 1) | NVME_FRMW_SLOT1_RO;
|
|
id->lpa = NVME_LPA_NS_SMART | NVME_LPA_CSE | NVME_LPA_EXTENDED;
|
|
|
|
/* recommended default value (~70 C) */
|
|
id->wctemp = cpu_to_le16(NVME_TEMPERATURE_WARNING);
|
|
id->cctemp = cpu_to_le16(NVME_TEMPERATURE_CRITICAL);
|
|
|
|
id->sqes = (NVME_SQES << 4) | NVME_SQES;
|
|
id->cqes = (NVME_CQES << 4) | NVME_CQES;
|
|
id->nn = cpu_to_le32(NVME_MAX_NAMESPACES);
|
|
id->oncs = cpu_to_le16(NVME_ONCS_WRITE_ZEROES | NVME_ONCS_TIMESTAMP |
|
|
NVME_ONCS_FEATURES | NVME_ONCS_DSM |
|
|
NVME_ONCS_COMPARE | NVME_ONCS_COPY |
|
|
NVME_ONCS_NVMCSA | NVME_ONCS_NVMAFC);
|
|
|
|
/*
|
|
* NOTE: If this device ever supports a command set that does NOT use 0x0
|
|
* as a Flush-equivalent operation, support for the broadcast NSID in Flush
|
|
* should probably be removed.
|
|
*
|
|
* See comment in nvme_io_cmd.
|
|
*/
|
|
id->vwc = NVME_VWC_NSID_BROADCAST_SUPPORT | NVME_VWC_PRESENT;
|
|
|
|
id->ocfs = cpu_to_le16(NVME_OCFS_COPY_FORMAT_0 | NVME_OCFS_COPY_FORMAT_1 |
|
|
NVME_OCFS_COPY_FORMAT_2 | NVME_OCFS_COPY_FORMAT_3);
|
|
id->sgls = cpu_to_le32(NVME_CTRL_SGLS_SUPPORT_NO_ALIGN |
|
|
NVME_CTRL_SGLS_MPTR_SGL);
|
|
|
|
nvme_init_subnqn(n);
|
|
|
|
id->psd[0].mp = cpu_to_le16(0x9c4);
|
|
id->psd[0].enlat = cpu_to_le32(0x10);
|
|
id->psd[0].exlat = cpu_to_le32(0x4);
|
|
|
|
if (n->subsys) {
|
|
id->cmic |= NVME_CMIC_MULTI_CTRL;
|
|
ctratt |= NVME_CTRATT_ENDGRPS;
|
|
|
|
id->endgidmax = cpu_to_le16(0x1);
|
|
|
|
if (n->subsys->endgrp.fdp.enabled) {
|
|
ctratt |= NVME_CTRATT_FDPS;
|
|
}
|
|
}
|
|
|
|
id->ctratt = cpu_to_le32(ctratt);
|
|
|
|
NVME_CAP_SET_MQES(cap, n->params.mqes);
|
|
NVME_CAP_SET_CQR(cap, 1);
|
|
NVME_CAP_SET_TO(cap, 0xf);
|
|
NVME_CAP_SET_CSS(cap, NVME_CAP_CSS_NVM);
|
|
NVME_CAP_SET_CSS(cap, NVME_CAP_CSS_CSI_SUPP);
|
|
NVME_CAP_SET_CSS(cap, NVME_CAP_CSS_ADMIN_ONLY);
|
|
NVME_CAP_SET_MPSMAX(cap, 4);
|
|
NVME_CAP_SET_CMBS(cap, n->params.cmb_size_mb ? 1 : 0);
|
|
NVME_CAP_SET_PMRS(cap, n->pmr.dev ? 1 : 0);
|
|
stq_le_p(&n->bar.cap, cap);
|
|
|
|
stl_le_p(&n->bar.vs, NVME_SPEC_VER);
|
|
n->bar.intmc = n->bar.intms = 0;
|
|
|
|
if (pci_is_vf(pci_dev) && !sctrl->scs) {
|
|
stl_le_p(&n->bar.csts, NVME_CSTS_FAILED);
|
|
}
|
|
}
|
|
|
|
static int nvme_init_subsys(NvmeCtrl *n, Error **errp)
|
|
{
|
|
int cntlid;
|
|
|
|
if (!n->subsys) {
|
|
return 0;
|
|
}
|
|
|
|
cntlid = nvme_subsys_register_ctrl(n, errp);
|
|
if (cntlid < 0) {
|
|
return -1;
|
|
}
|
|
|
|
n->cntlid = cntlid;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void nvme_attach_ns(NvmeCtrl *n, NvmeNamespace *ns)
|
|
{
|
|
uint32_t nsid = ns->params.nsid;
|
|
assert(nsid && nsid <= NVME_MAX_NAMESPACES);
|
|
|
|
n->namespaces[nsid] = ns;
|
|
ns->attached++;
|
|
|
|
n->dmrsl = MIN_NON_ZERO(n->dmrsl,
|
|
BDRV_REQUEST_MAX_BYTES / nvme_l2b(ns, 1));
|
|
}
|
|
|
|
static void nvme_realize(PCIDevice *pci_dev, Error **errp)
|
|
{
|
|
NvmeCtrl *n = NVME(pci_dev);
|
|
DeviceState *dev = DEVICE(pci_dev);
|
|
NvmeNamespace *ns;
|
|
NvmeCtrl *pn = NVME(pcie_sriov_get_pf(pci_dev));
|
|
|
|
if (pci_is_vf(pci_dev)) {
|
|
/*
|
|
* VFs derive settings from the parent. PF's lifespan exceeds
|
|
* that of VF's.
|
|
*/
|
|
memcpy(&n->params, &pn->params, sizeof(NvmeParams));
|
|
|
|
/*
|
|
* Set PF's serial value to a new string memory to prevent 'serial'
|
|
* property object release of PF when a VF is removed from the system.
|
|
*/
|
|
n->params.serial = g_strdup(pn->params.serial);
|
|
n->subsys = pn->subsys;
|
|
}
|
|
|
|
if (!nvme_check_params(n, errp)) {
|
|
return;
|
|
}
|
|
|
|
qbus_init(&n->bus, sizeof(NvmeBus), TYPE_NVME_BUS, dev, dev->id);
|
|
|
|
if (nvme_init_subsys(n, errp)) {
|
|
return;
|
|
}
|
|
nvme_init_state(n);
|
|
if (!nvme_init_pci(n, pci_dev, errp)) {
|
|
return;
|
|
}
|
|
nvme_init_ctrl(n, pci_dev);
|
|
|
|
/* setup a namespace if the controller drive property was given */
|
|
if (n->namespace.blkconf.blk) {
|
|
ns = &n->namespace;
|
|
ns->params.nsid = 1;
|
|
|
|
if (nvme_ns_setup(ns, errp)) {
|
|
return;
|
|
}
|
|
|
|
nvme_attach_ns(n, ns);
|
|
}
|
|
}
|
|
|
|
static void nvme_exit(PCIDevice *pci_dev)
|
|
{
|
|
NvmeCtrl *n = NVME(pci_dev);
|
|
NvmeNamespace *ns;
|
|
int i;
|
|
|
|
nvme_ctrl_reset(n, NVME_RESET_FUNCTION);
|
|
|
|
if (n->subsys) {
|
|
for (i = 1; i <= NVME_MAX_NAMESPACES; i++) {
|
|
ns = nvme_ns(n, i);
|
|
if (ns) {
|
|
ns->attached--;
|
|
}
|
|
}
|
|
|
|
nvme_subsys_unregister_ctrl(n->subsys, n);
|
|
}
|
|
|
|
g_free(n->cq);
|
|
g_free(n->sq);
|
|
g_free(n->aer_reqs);
|
|
|
|
if (n->params.cmb_size_mb) {
|
|
g_free(n->cmb.buf);
|
|
}
|
|
|
|
if (pci_dev->doe_spdm.spdm_socket > 0) {
|
|
spdm_socket_close(pci_dev->doe_spdm.spdm_socket,
|
|
SPDM_SOCKET_TRANSPORT_TYPE_PCI_DOE);
|
|
}
|
|
|
|
if (n->pmr.dev) {
|
|
host_memory_backend_set_mapped(n->pmr.dev, false);
|
|
}
|
|
|
|
if (!pci_is_vf(pci_dev) && n->params.sriov_max_vfs) {
|
|
pcie_sriov_pf_exit(pci_dev);
|
|
}
|
|
|
|
msix_uninit(pci_dev, &n->bar0, &n->bar0);
|
|
memory_region_del_subregion(&n->bar0, &n->iomem);
|
|
}
|
|
|
|
static Property nvme_props[] = {
|
|
DEFINE_BLOCK_PROPERTIES(NvmeCtrl, namespace.blkconf),
|
|
DEFINE_PROP_LINK("pmrdev", NvmeCtrl, pmr.dev, TYPE_MEMORY_BACKEND,
|
|
HostMemoryBackend *),
|
|
DEFINE_PROP_LINK("subsys", NvmeCtrl, subsys, TYPE_NVME_SUBSYS,
|
|
NvmeSubsystem *),
|
|
DEFINE_PROP_STRING("serial", NvmeCtrl, params.serial),
|
|
DEFINE_PROP_UINT32("cmb_size_mb", NvmeCtrl, params.cmb_size_mb, 0),
|
|
DEFINE_PROP_UINT32("num_queues", NvmeCtrl, params.num_queues, 0),
|
|
DEFINE_PROP_UINT32("max_ioqpairs", NvmeCtrl, params.max_ioqpairs, 64),
|
|
DEFINE_PROP_UINT16("msix_qsize", NvmeCtrl, params.msix_qsize, 65),
|
|
DEFINE_PROP_UINT8("aerl", NvmeCtrl, params.aerl, 3),
|
|
DEFINE_PROP_UINT32("aer_max_queued", NvmeCtrl, params.aer_max_queued, 64),
|
|
DEFINE_PROP_UINT8("mdts", NvmeCtrl, params.mdts, 7),
|
|
DEFINE_PROP_UINT8("vsl", NvmeCtrl, params.vsl, 7),
|
|
DEFINE_PROP_BOOL("use-intel-id", NvmeCtrl, params.use_intel_id, false),
|
|
DEFINE_PROP_BOOL("legacy-cmb", NvmeCtrl, params.legacy_cmb, false),
|
|
DEFINE_PROP_BOOL("ioeventfd", NvmeCtrl, params.ioeventfd, false),
|
|
DEFINE_PROP_UINT8("zoned.zasl", NvmeCtrl, params.zasl, 0),
|
|
DEFINE_PROP_BOOL("zoned.auto_transition", NvmeCtrl,
|
|
params.auto_transition_zones, true),
|
|
DEFINE_PROP_UINT16("sriov_max_vfs", NvmeCtrl, params.sriov_max_vfs, 0),
|
|
DEFINE_PROP_UINT16("sriov_vq_flexible", NvmeCtrl,
|
|
params.sriov_vq_flexible, 0),
|
|
DEFINE_PROP_UINT16("sriov_vi_flexible", NvmeCtrl,
|
|
params.sriov_vi_flexible, 0),
|
|
DEFINE_PROP_UINT32("sriov_max_vi_per_vf", NvmeCtrl,
|
|
params.sriov_max_vi_per_vf, 0),
|
|
DEFINE_PROP_UINT32("sriov_max_vq_per_vf", NvmeCtrl,
|
|
params.sriov_max_vq_per_vf, 0),
|
|
DEFINE_PROP_BOOL("msix-exclusive-bar", NvmeCtrl, params.msix_exclusive_bar,
|
|
false),
|
|
DEFINE_PROP_UINT16("mqes", NvmeCtrl, params.mqes, 0x7ff),
|
|
DEFINE_PROP_UINT16("spdm_port", PCIDevice, spdm_port, 0),
|
|
DEFINE_PROP_BOOL("ctratt.mem", NvmeCtrl, params.ctratt.mem, false),
|
|
DEFINE_PROP_BOOL("atomic.dn", NvmeCtrl, params.atomic_dn, 0),
|
|
DEFINE_PROP_UINT16("atomic.awun", NvmeCtrl, params.atomic_awun, 0),
|
|
DEFINE_PROP_UINT16("atomic.awupf", NvmeCtrl, params.atomic_awupf, 0),
|
|
DEFINE_PROP_END_OF_LIST(),
|
|
};
|
|
|
|
static void nvme_get_smart_warning(Object *obj, Visitor *v, const char *name,
|
|
void *opaque, Error **errp)
|
|
{
|
|
NvmeCtrl *n = NVME(obj);
|
|
uint8_t value = n->smart_critical_warning;
|
|
|
|
visit_type_uint8(v, name, &value, errp);
|
|
}
|
|
|
|
static void nvme_set_smart_warning(Object *obj, Visitor *v, const char *name,
|
|
void *opaque, Error **errp)
|
|
{
|
|
NvmeCtrl *n = NVME(obj);
|
|
uint8_t value, old_value, cap = 0, index, event;
|
|
|
|
if (!visit_type_uint8(v, name, &value, errp)) {
|
|
return;
|
|
}
|
|
|
|
cap = NVME_SMART_SPARE | NVME_SMART_TEMPERATURE | NVME_SMART_RELIABILITY
|
|
| NVME_SMART_MEDIA_READ_ONLY | NVME_SMART_FAILED_VOLATILE_MEDIA;
|
|
if (NVME_CAP_PMRS(ldq_le_p(&n->bar.cap))) {
|
|
cap |= NVME_SMART_PMR_UNRELIABLE;
|
|
}
|
|
|
|
if ((value & cap) != value) {
|
|
error_setg(errp, "unsupported smart critical warning bits: 0x%x",
|
|
value & ~cap);
|
|
return;
|
|
}
|
|
|
|
old_value = n->smart_critical_warning;
|
|
n->smart_critical_warning = value;
|
|
|
|
/* only inject new bits of smart critical warning */
|
|
for (index = 0; index < NVME_SMART_WARN_MAX; index++) {
|
|
event = 1 << index;
|
|
if (value & ~old_value & event)
|
|
nvme_smart_event(n, event);
|
|
}
|
|
}
|
|
|
|
static void nvme_pci_reset(DeviceState *qdev)
|
|
{
|
|
PCIDevice *pci_dev = PCI_DEVICE(qdev);
|
|
NvmeCtrl *n = NVME(pci_dev);
|
|
|
|
trace_pci_nvme_pci_reset();
|
|
nvme_ctrl_reset(n, NVME_RESET_FUNCTION);
|
|
}
|
|
|
|
static void nvme_sriov_post_write_config(PCIDevice *dev, uint16_t old_num_vfs)
|
|
{
|
|
NvmeCtrl *n = NVME(dev);
|
|
NvmeSecCtrlEntry *sctrl;
|
|
int i;
|
|
|
|
for (i = pcie_sriov_num_vfs(dev); i < old_num_vfs; i++) {
|
|
sctrl = &n->sec_ctrl_list[i];
|
|
nvme_virt_set_state(n, le16_to_cpu(sctrl->scid), false);
|
|
}
|
|
}
|
|
|
|
static void nvme_pci_write_config(PCIDevice *dev, uint32_t address,
|
|
uint32_t val, int len)
|
|
{
|
|
uint16_t old_num_vfs = pcie_sriov_num_vfs(dev);
|
|
|
|
if (pcie_find_capability(dev, PCI_EXT_CAP_ID_DOE)) {
|
|
pcie_doe_write_config(&dev->doe_spdm, address, val, len);
|
|
}
|
|
pci_default_write_config(dev, address, val, len);
|
|
pcie_cap_flr_write_config(dev, address, val, len);
|
|
nvme_sriov_post_write_config(dev, old_num_vfs);
|
|
}
|
|
|
|
static uint32_t nvme_pci_read_config(PCIDevice *dev, uint32_t address, int len)
|
|
{
|
|
uint32_t val;
|
|
if (dev->spdm_port && pcie_find_capability(dev, PCI_EXT_CAP_ID_DOE)) {
|
|
if (pcie_doe_read_config(&dev->doe_spdm, address, len, &val)) {
|
|
return val;
|
|
}
|
|
}
|
|
return pci_default_read_config(dev, address, len);
|
|
}
|
|
|
|
static const VMStateDescription nvme_vmstate = {
|
|
.name = "nvme",
|
|
.unmigratable = 1,
|
|
};
|
|
|
|
static void nvme_class_init(ObjectClass *oc, void *data)
|
|
{
|
|
DeviceClass *dc = DEVICE_CLASS(oc);
|
|
PCIDeviceClass *pc = PCI_DEVICE_CLASS(oc);
|
|
|
|
pc->realize = nvme_realize;
|
|
pc->config_write = nvme_pci_write_config;
|
|
pc->config_read = nvme_pci_read_config;
|
|
pc->exit = nvme_exit;
|
|
pc->class_id = PCI_CLASS_STORAGE_EXPRESS;
|
|
pc->revision = 2;
|
|
|
|
set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
|
|
dc->desc = "Non-Volatile Memory Express";
|
|
device_class_set_props(dc, nvme_props);
|
|
dc->vmsd = &nvme_vmstate;
|
|
device_class_set_legacy_reset(dc, nvme_pci_reset);
|
|
}
|
|
|
|
static void nvme_instance_init(Object *obj)
|
|
{
|
|
NvmeCtrl *n = NVME(obj);
|
|
|
|
device_add_bootindex_property(obj, &n->namespace.blkconf.bootindex,
|
|
"bootindex", "/namespace@1,0",
|
|
DEVICE(obj));
|
|
|
|
object_property_add(obj, "smart_critical_warning", "uint8",
|
|
nvme_get_smart_warning,
|
|
nvme_set_smart_warning, NULL, NULL);
|
|
}
|
|
|
|
static const TypeInfo nvme_info = {
|
|
.name = TYPE_NVME,
|
|
.parent = TYPE_PCI_DEVICE,
|
|
.instance_size = sizeof(NvmeCtrl),
|
|
.instance_init = nvme_instance_init,
|
|
.class_init = nvme_class_init,
|
|
.interfaces = (InterfaceInfo[]) {
|
|
{ INTERFACE_PCIE_DEVICE },
|
|
{ }
|
|
},
|
|
};
|
|
|
|
static const TypeInfo nvme_bus_info = {
|
|
.name = TYPE_NVME_BUS,
|
|
.parent = TYPE_BUS,
|
|
.instance_size = sizeof(NvmeBus),
|
|
};
|
|
|
|
static void nvme_register_types(void)
|
|
{
|
|
type_register_static(&nvme_info);
|
|
type_register_static(&nvme_bus_info);
|
|
}
|
|
|
|
type_init(nvme_register_types)
|