| /* |
| * QEMU NVM Express |
| * |
| * Copyright (c) 2012 Intel Corporation |
| * Copyright (c) 2021 Minwoo Im |
| * Copyright (c) 2021 Samsung Electronics Co., Ltd. |
| * |
| * Authors: |
| * Keith Busch <kbusch@kernel.org> |
| * Klaus Jensen <k.jensen@samsung.com> |
| * Gollu Appalanaidu <anaidu.gollu@samsung.com> |
| * Dmitry Fomichev <dmitry.fomichev@wdc.com> |
| * Minwoo Im <minwoo.im.dev@gmail.com> |
| * |
| * This code is licensed under the GNU GPL v2 or later. |
| */ |
| |
| #ifndef HW_NVME_NVME_H |
| #define HW_NVME_NVME_H |
| |
| #include "qemu/uuid.h" |
| #include "hw/pci/pci_device.h" |
| #include "hw/block/block.h" |
| |
| #include "block/nvme.h" |
| |
| #define NVME_MAX_CONTROLLERS 256 |
| #define NVME_MAX_NAMESPACES 256 |
| #define NVME_EUI64_DEFAULT ((uint64_t)0x5254000000000000) |
| |
| QEMU_BUILD_BUG_ON(NVME_MAX_NAMESPACES > NVME_NSID_BROADCAST - 1); |
| |
| typedef struct NvmeCtrl NvmeCtrl; |
| typedef struct NvmeNamespace NvmeNamespace; |
| |
| #define TYPE_NVME_BUS "nvme-bus" |
| OBJECT_DECLARE_SIMPLE_TYPE(NvmeBus, NVME_BUS) |
| |
| typedef struct NvmeBus { |
| BusState parent_bus; |
| } NvmeBus; |
| |
| #define TYPE_NVME_SUBSYS "nvme-subsys" |
| #define NVME_SUBSYS(obj) \ |
| OBJECT_CHECK(NvmeSubsystem, (obj), TYPE_NVME_SUBSYS) |
| #define SUBSYS_SLOT_RSVD (void *)0xFFFF |
| |
| typedef struct NvmeSubsystem { |
| DeviceState parent_obj; |
| NvmeBus bus; |
| uint8_t subnqn[256]; |
| char *serial; |
| |
| NvmeCtrl *ctrls[NVME_MAX_CONTROLLERS]; |
| NvmeNamespace *namespaces[NVME_MAX_NAMESPACES + 1]; |
| |
| struct { |
| char *nqn; |
| } params; |
| } NvmeSubsystem; |
| |
| int nvme_subsys_register_ctrl(NvmeCtrl *n, Error **errp); |
| void nvme_subsys_unregister_ctrl(NvmeSubsystem *subsys, NvmeCtrl *n); |
| |
| static inline NvmeCtrl *nvme_subsys_ctrl(NvmeSubsystem *subsys, |
| uint32_t cntlid) |
| { |
| if (!subsys || cntlid >= NVME_MAX_CONTROLLERS) { |
| return NULL; |
| } |
| |
| if (subsys->ctrls[cntlid] == SUBSYS_SLOT_RSVD) { |
| return NULL; |
| } |
| |
| return subsys->ctrls[cntlid]; |
| } |
| |
| static inline NvmeNamespace *nvme_subsys_ns(NvmeSubsystem *subsys, |
| uint32_t nsid) |
| { |
| if (!subsys || !nsid || nsid > NVME_MAX_NAMESPACES) { |
| return NULL; |
| } |
| |
| return subsys->namespaces[nsid]; |
| } |
| |
| #define TYPE_NVME_NS "nvme-ns" |
| #define NVME_NS(obj) \ |
| OBJECT_CHECK(NvmeNamespace, (obj), TYPE_NVME_NS) |
| |
| typedef struct NvmeZone { |
| NvmeZoneDescr d; |
| uint64_t w_ptr; |
| QTAILQ_ENTRY(NvmeZone) entry; |
| } NvmeZone; |
| |
| typedef struct NvmeNamespaceParams { |
| bool detached; |
| bool shared; |
| uint32_t nsid; |
| QemuUUID uuid; |
| uint64_t eui64; |
| bool eui64_default; |
| |
| uint16_t ms; |
| uint8_t mset; |
| uint8_t pi; |
| uint8_t pil; |
| uint8_t pif; |
| |
| uint16_t mssrl; |
| uint32_t mcl; |
| uint8_t msrc; |
| |
| bool zoned; |
| bool cross_zone_read; |
| uint64_t zone_size_bs; |
| uint64_t zone_cap_bs; |
| uint32_t max_active_zones; |
| uint32_t max_open_zones; |
| uint32_t zd_extension_size; |
| |
| uint32_t numzrwa; |
| uint64_t zrwas; |
| uint64_t zrwafg; |
| } NvmeNamespaceParams; |
| |
| typedef struct NvmeNamespace { |
| DeviceState parent_obj; |
| BlockConf blkconf; |
| int32_t bootindex; |
| int64_t size; |
| int64_t moff; |
| NvmeIdNs id_ns; |
| NvmeIdNsNvm id_ns_nvm; |
| NvmeLBAF lbaf; |
| unsigned int nlbaf; |
| size_t lbasz; |
| const uint32_t *iocs; |
| uint8_t csi; |
| uint16_t status; |
| int attached; |
| uint8_t pif; |
| |
| struct { |
| uint16_t zrwas; |
| uint16_t zrwafg; |
| uint32_t numzrwa; |
| } zns; |
| |
| QTAILQ_ENTRY(NvmeNamespace) entry; |
| |
| NvmeIdNsZoned *id_ns_zoned; |
| NvmeZone *zone_array; |
| QTAILQ_HEAD(, NvmeZone) exp_open_zones; |
| QTAILQ_HEAD(, NvmeZone) imp_open_zones; |
| QTAILQ_HEAD(, NvmeZone) closed_zones; |
| QTAILQ_HEAD(, NvmeZone) full_zones; |
| uint32_t num_zones; |
| uint64_t zone_size; |
| uint64_t zone_capacity; |
| uint32_t zone_size_log2; |
| uint8_t *zd_extensions; |
| int32_t nr_open_zones; |
| int32_t nr_active_zones; |
| |
| NvmeNamespaceParams params; |
| |
| struct { |
| uint32_t err_rec; |
| } features; |
| } NvmeNamespace; |
| |
| static inline uint32_t nvme_nsid(NvmeNamespace *ns) |
| { |
| if (ns) { |
| return ns->params.nsid; |
| } |
| |
| return 0; |
| } |
| |
| static inline size_t nvme_l2b(NvmeNamespace *ns, uint64_t lba) |
| { |
| return lba << ns->lbaf.ds; |
| } |
| |
| static inline size_t nvme_m2b(NvmeNamespace *ns, uint64_t lba) |
| { |
| return ns->lbaf.ms * lba; |
| } |
| |
| static inline int64_t nvme_moff(NvmeNamespace *ns, uint64_t lba) |
| { |
| return ns->moff + nvme_m2b(ns, lba); |
| } |
| |
| static inline bool nvme_ns_ext(NvmeNamespace *ns) |
| { |
| return !!NVME_ID_NS_FLBAS_EXTENDED(ns->id_ns.flbas); |
| } |
| |
| static inline NvmeZoneState nvme_get_zone_state(NvmeZone *zone) |
| { |
| return zone->d.zs >> 4; |
| } |
| |
| static inline void nvme_set_zone_state(NvmeZone *zone, NvmeZoneState state) |
| { |
| zone->d.zs = state << 4; |
| } |
| |
| static inline uint64_t nvme_zone_rd_boundary(NvmeNamespace *ns, NvmeZone *zone) |
| { |
| return zone->d.zslba + ns->zone_size; |
| } |
| |
| static inline uint64_t nvme_zone_wr_boundary(NvmeZone *zone) |
| { |
| return zone->d.zslba + zone->d.zcap; |
| } |
| |
| static inline bool nvme_wp_is_valid(NvmeZone *zone) |
| { |
| uint8_t st = nvme_get_zone_state(zone); |
| |
| return st != NVME_ZONE_STATE_FULL && |
| st != NVME_ZONE_STATE_READ_ONLY && |
| st != NVME_ZONE_STATE_OFFLINE; |
| } |
| |
| static inline uint8_t *nvme_get_zd_extension(NvmeNamespace *ns, |
| uint32_t zone_idx) |
| { |
| return &ns->zd_extensions[zone_idx * ns->params.zd_extension_size]; |
| } |
| |
| static inline void nvme_aor_inc_open(NvmeNamespace *ns) |
| { |
| assert(ns->nr_open_zones >= 0); |
| if (ns->params.max_open_zones) { |
| ns->nr_open_zones++; |
| assert(ns->nr_open_zones <= ns->params.max_open_zones); |
| } |
| } |
| |
| static inline void nvme_aor_dec_open(NvmeNamespace *ns) |
| { |
| if (ns->params.max_open_zones) { |
| assert(ns->nr_open_zones > 0); |
| ns->nr_open_zones--; |
| } |
| assert(ns->nr_open_zones >= 0); |
| } |
| |
| static inline void nvme_aor_inc_active(NvmeNamespace *ns) |
| { |
| assert(ns->nr_active_zones >= 0); |
| if (ns->params.max_active_zones) { |
| ns->nr_active_zones++; |
| assert(ns->nr_active_zones <= ns->params.max_active_zones); |
| } |
| } |
| |
| static inline void nvme_aor_dec_active(NvmeNamespace *ns) |
| { |
| if (ns->params.max_active_zones) { |
| assert(ns->nr_active_zones > 0); |
| ns->nr_active_zones--; |
| assert(ns->nr_active_zones >= ns->nr_open_zones); |
| } |
| assert(ns->nr_active_zones >= 0); |
| } |
| |
| void nvme_ns_init_format(NvmeNamespace *ns); |
| int nvme_ns_setup(NvmeNamespace *ns, Error **errp); |
| void nvme_ns_drain(NvmeNamespace *ns); |
| void nvme_ns_shutdown(NvmeNamespace *ns); |
| void nvme_ns_cleanup(NvmeNamespace *ns); |
| |
| typedef struct NvmeAsyncEvent { |
| QTAILQ_ENTRY(NvmeAsyncEvent) entry; |
| NvmeAerResult result; |
| } NvmeAsyncEvent; |
| |
| enum { |
| NVME_SG_ALLOC = 1 << 0, |
| NVME_SG_DMA = 1 << 1, |
| }; |
| |
| typedef struct NvmeSg { |
| int flags; |
| |
| union { |
| QEMUSGList qsg; |
| QEMUIOVector iov; |
| }; |
| } NvmeSg; |
| |
| typedef enum NvmeTxDirection { |
| NVME_TX_DIRECTION_TO_DEVICE = 0, |
| NVME_TX_DIRECTION_FROM_DEVICE = 1, |
| } NvmeTxDirection; |
| |
| typedef struct NvmeRequest { |
| struct NvmeSQueue *sq; |
| struct NvmeNamespace *ns; |
| BlockAIOCB *aiocb; |
| uint16_t status; |
| void *opaque; |
| NvmeCqe cqe; |
| NvmeCmd cmd; |
| BlockAcctCookie acct; |
| NvmeSg sg; |
| QTAILQ_ENTRY(NvmeRequest)entry; |
| } NvmeRequest; |
| |
| typedef struct NvmeBounceContext { |
| NvmeRequest *req; |
| |
| struct { |
| QEMUIOVector iov; |
| uint8_t *bounce; |
| } data, mdata; |
| } NvmeBounceContext; |
| |
| static inline const char *nvme_adm_opc_str(uint8_t opc) |
| { |
| switch (opc) { |
| case NVME_ADM_CMD_DELETE_SQ: return "NVME_ADM_CMD_DELETE_SQ"; |
| case NVME_ADM_CMD_CREATE_SQ: return "NVME_ADM_CMD_CREATE_SQ"; |
| case NVME_ADM_CMD_GET_LOG_PAGE: return "NVME_ADM_CMD_GET_LOG_PAGE"; |
| case NVME_ADM_CMD_DELETE_CQ: return "NVME_ADM_CMD_DELETE_CQ"; |
| case NVME_ADM_CMD_CREATE_CQ: return "NVME_ADM_CMD_CREATE_CQ"; |
| case NVME_ADM_CMD_IDENTIFY: return "NVME_ADM_CMD_IDENTIFY"; |
| case NVME_ADM_CMD_ABORT: return "NVME_ADM_CMD_ABORT"; |
| case NVME_ADM_CMD_SET_FEATURES: return "NVME_ADM_CMD_SET_FEATURES"; |
| case NVME_ADM_CMD_GET_FEATURES: return "NVME_ADM_CMD_GET_FEATURES"; |
| case NVME_ADM_CMD_ASYNC_EV_REQ: return "NVME_ADM_CMD_ASYNC_EV_REQ"; |
| case NVME_ADM_CMD_NS_ATTACHMENT: return "NVME_ADM_CMD_NS_ATTACHMENT"; |
| case NVME_ADM_CMD_VIRT_MNGMT: return "NVME_ADM_CMD_VIRT_MNGMT"; |
| case NVME_ADM_CMD_DBBUF_CONFIG: return "NVME_ADM_CMD_DBBUF_CONFIG"; |
| case NVME_ADM_CMD_FORMAT_NVM: return "NVME_ADM_CMD_FORMAT_NVM"; |
| default: return "NVME_ADM_CMD_UNKNOWN"; |
| } |
| } |
| |
| static inline const char *nvme_io_opc_str(uint8_t opc) |
| { |
| switch (opc) { |
| case NVME_CMD_FLUSH: return "NVME_NVM_CMD_FLUSH"; |
| case NVME_CMD_WRITE: return "NVME_NVM_CMD_WRITE"; |
| case NVME_CMD_READ: return "NVME_NVM_CMD_READ"; |
| case NVME_CMD_COMPARE: return "NVME_NVM_CMD_COMPARE"; |
| case NVME_CMD_WRITE_ZEROES: return "NVME_NVM_CMD_WRITE_ZEROES"; |
| case NVME_CMD_DSM: return "NVME_NVM_CMD_DSM"; |
| case NVME_CMD_VERIFY: return "NVME_NVM_CMD_VERIFY"; |
| case NVME_CMD_COPY: return "NVME_NVM_CMD_COPY"; |
| case NVME_CMD_ZONE_MGMT_SEND: return "NVME_ZONED_CMD_MGMT_SEND"; |
| case NVME_CMD_ZONE_MGMT_RECV: return "NVME_ZONED_CMD_MGMT_RECV"; |
| case NVME_CMD_ZONE_APPEND: return "NVME_ZONED_CMD_ZONE_APPEND"; |
| default: return "NVME_NVM_CMD_UNKNOWN"; |
| } |
| } |
| |
| typedef struct NvmeSQueue { |
| struct NvmeCtrl *ctrl; |
| uint16_t sqid; |
| uint16_t cqid; |
| uint32_t head; |
| uint32_t tail; |
| uint32_t size; |
| uint64_t dma_addr; |
| uint64_t db_addr; |
| uint64_t ei_addr; |
| QEMUBH *bh; |
| EventNotifier notifier; |
| bool ioeventfd_enabled; |
| NvmeRequest *io_req; |
| QTAILQ_HEAD(, NvmeRequest) req_list; |
| QTAILQ_HEAD(, NvmeRequest) out_req_list; |
| QTAILQ_ENTRY(NvmeSQueue) entry; |
| } NvmeSQueue; |
| |
| typedef struct NvmeCQueue { |
| struct NvmeCtrl *ctrl; |
| uint8_t phase; |
| uint16_t cqid; |
| uint16_t irq_enabled; |
| uint32_t head; |
| uint32_t tail; |
| uint32_t vector; |
| uint32_t size; |
| uint64_t dma_addr; |
| uint64_t db_addr; |
| uint64_t ei_addr; |
| QEMUBH *bh; |
| EventNotifier notifier; |
| bool ioeventfd_enabled; |
| QTAILQ_HEAD(, NvmeSQueue) sq_list; |
| QTAILQ_HEAD(, NvmeRequest) req_list; |
| } NvmeCQueue; |
| |
| #define TYPE_NVME "nvme" |
| #define NVME(obj) \ |
| OBJECT_CHECK(NvmeCtrl, (obj), TYPE_NVME) |
| |
| typedef struct NvmeParams { |
| char *serial; |
| uint32_t num_queues; /* deprecated since 5.1 */ |
| uint32_t max_ioqpairs; |
| uint16_t msix_qsize; |
| uint32_t cmb_size_mb; |
| uint8_t aerl; |
| uint32_t aer_max_queued; |
| uint8_t mdts; |
| uint8_t vsl; |
| bool use_intel_id; |
| uint8_t zasl; |
| bool auto_transition_zones; |
| bool legacy_cmb; |
| bool ioeventfd; |
| uint8_t sriov_max_vfs; |
| uint16_t sriov_vq_flexible; |
| uint16_t sriov_vi_flexible; |
| uint8_t sriov_max_vq_per_vf; |
| uint8_t sriov_max_vi_per_vf; |
| } NvmeParams; |
| |
| typedef struct NvmeCtrl { |
| PCIDevice parent_obj; |
| MemoryRegion bar0; |
| MemoryRegion iomem; |
| NvmeBar bar; |
| NvmeParams params; |
| NvmeBus bus; |
| |
| uint16_t cntlid; |
| bool qs_created; |
| uint32_t page_size; |
| uint16_t page_bits; |
| uint16_t max_prp_ents; |
| uint16_t cqe_size; |
| uint16_t sqe_size; |
| uint32_t max_q_ents; |
| uint8_t outstanding_aers; |
| uint32_t irq_status; |
| int cq_pending; |
| uint64_t host_timestamp; /* Timestamp sent by the host */ |
| uint64_t timestamp_set_qemu_clock_ms; /* QEMU clock time */ |
| uint64_t starttime_ms; |
| uint16_t temperature; |
| uint8_t smart_critical_warning; |
| uint32_t conf_msix_qsize; |
| uint32_t conf_ioqpairs; |
| uint64_t dbbuf_dbs; |
| uint64_t dbbuf_eis; |
| bool dbbuf_enabled; |
| |
| struct { |
| MemoryRegion mem; |
| uint8_t *buf; |
| bool cmse; |
| hwaddr cba; |
| } cmb; |
| |
| struct { |
| HostMemoryBackend *dev; |
| bool cmse; |
| hwaddr cba; |
| } pmr; |
| |
| uint8_t aer_mask; |
| NvmeRequest **aer_reqs; |
| QTAILQ_HEAD(, NvmeAsyncEvent) aer_queue; |
| int aer_queued; |
| |
| uint32_t dmrsl; |
| |
| /* Namespace ID is started with 1 so bitmap should be 1-based */ |
| #define NVME_CHANGED_NSID_SIZE (NVME_MAX_NAMESPACES + 1) |
| DECLARE_BITMAP(changed_nsids, NVME_CHANGED_NSID_SIZE); |
| |
| NvmeSubsystem *subsys; |
| |
| NvmeNamespace namespace; |
| NvmeNamespace *namespaces[NVME_MAX_NAMESPACES + 1]; |
| NvmeSQueue **sq; |
| NvmeCQueue **cq; |
| NvmeSQueue admin_sq; |
| NvmeCQueue admin_cq; |
| NvmeIdCtrl id_ctrl; |
| |
| struct { |
| struct { |
| uint16_t temp_thresh_hi; |
| uint16_t temp_thresh_low; |
| }; |
| |
| uint32_t async_config; |
| NvmeHostBehaviorSupport hbs; |
| } features; |
| |
| NvmePriCtrlCap pri_ctrl_cap; |
| NvmeSecCtrlList sec_ctrl_list; |
| struct { |
| uint16_t vqrfap; |
| uint16_t virfap; |
| } next_pri_ctrl_cap; /* These override pri_ctrl_cap after reset */ |
| } NvmeCtrl; |
| |
| typedef enum NvmeResetType { |
| NVME_RESET_FUNCTION = 0, |
| NVME_RESET_CONTROLLER = 1, |
| } NvmeResetType; |
| |
| static inline NvmeNamespace *nvme_ns(NvmeCtrl *n, uint32_t nsid) |
| { |
| if (!nsid || nsid > NVME_MAX_NAMESPACES) { |
| return NULL; |
| } |
| |
| return n->namespaces[nsid]; |
| } |
| |
| static inline NvmeCQueue *nvme_cq(NvmeRequest *req) |
| { |
| NvmeSQueue *sq = req->sq; |
| NvmeCtrl *n = sq->ctrl; |
| |
| return n->cq[sq->cqid]; |
| } |
| |
| static inline NvmeCtrl *nvme_ctrl(NvmeRequest *req) |
| { |
| NvmeSQueue *sq = req->sq; |
| return sq->ctrl; |
| } |
| |
| static inline uint16_t nvme_cid(NvmeRequest *req) |
| { |
| if (!req) { |
| return 0xffff; |
| } |
| |
| return le16_to_cpu(req->cqe.cid); |
| } |
| |
| static inline NvmeSecCtrlEntry *nvme_sctrl(NvmeCtrl *n) |
| { |
| PCIDevice *pci_dev = &n->parent_obj; |
| NvmeCtrl *pf = NVME(pcie_sriov_get_pf(pci_dev)); |
| |
| if (pci_is_vf(pci_dev)) { |
| return &pf->sec_ctrl_list.sec[pcie_sriov_vf_number(pci_dev)]; |
| } |
| |
| return NULL; |
| } |
| |
| static inline NvmeSecCtrlEntry *nvme_sctrl_for_cntlid(NvmeCtrl *n, |
| uint16_t cntlid) |
| { |
| NvmeSecCtrlList *list = &n->sec_ctrl_list; |
| uint8_t i; |
| |
| for (i = 0; i < list->numcntl; i++) { |
| if (le16_to_cpu(list->sec[i].scid) == cntlid) { |
| return &list->sec[i]; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| void nvme_attach_ns(NvmeCtrl *n, NvmeNamespace *ns); |
| uint16_t nvme_bounce_data(NvmeCtrl *n, void *ptr, uint32_t len, |
| NvmeTxDirection dir, NvmeRequest *req); |
| uint16_t nvme_bounce_mdata(NvmeCtrl *n, void *ptr, uint32_t len, |
| NvmeTxDirection dir, NvmeRequest *req); |
| void nvme_rw_complete_cb(void *opaque, int ret); |
| uint16_t nvme_map_dptr(NvmeCtrl *n, NvmeSg *sg, size_t len, |
| NvmeCmd *cmd); |
| |
| #endif /* HW_NVME_NVME_H */ |