This adds support for format, asynch event, abort, and format admin commands, and helper routines to make it easier to put interesting data in the log pages.
Signed-off-by: Keith Busch <keith.bu...@intel.com> --- hw/nvme.c | 420 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- hw/nvme.h | 21 +++ 2 files changed, 440 insertions(+), 1 deletions(-) diff --git a/hw/nvme.c b/hw/nvme.c index f1e0792..eeff762 100644 --- a/hw/nvme.c +++ b/hw/nvme.c @@ -46,6 +46,8 @@ #include "hw.h" #include "pci/msix.h" #include "pci/pci.h" +#include "qemu/bitmap.h" +#include "qemu/bitops.h" #include "nvme.h" @@ -56,6 +58,9 @@ #define NVME_MAX_QUEUE_ES 0xf #define NVME_MIN_CQUEUE_ES 0x4 #define NVME_MIN_SQUEUE_ES 0x6 +#define NVME_SPARE_THRESHOLD 20 +#define NVME_TEMPERATURE 0x143 +#define NVME_OP_ABORTED 0xff static int instance; static void nvme_sq_process(void *opaque); @@ -229,16 +234,106 @@ static void nvme_enqueue_req_completion(NvmeCQueue *cq, NvmeRequest *req) qemu_mod_timer(cq->timer, qemu_get_clock_ns(vm_clock) + 500); } +static void nvme_enqueue_event(NvmeCtrl *n, uint8_t event_type, + uint8_t event_info, uint8_t log_page) +{ + AsyncEvent *event = (AsyncEvent *)g_malloc(sizeof(*event)); + event->result.event_type = event_type; + event->result.event_info = event_info; + event->result.log_page = log_page; + QSIMPLEQ_INSERT_TAIL(&(n->aer_queue), event, entry); + qemu_mod_timer(n->aer_timer, qemu_get_clock_ns(vm_clock) + 10000); +} + +static void nvme_aer_process_cb(void *param) +{ + NvmeCtrl *n = param; + NvmeRequest *req; + AerResult *result; + AsyncEvent *event, *next;; + QSIMPLEQ_FOREACH_SAFE(event, &n->aer_queue, entry, next) { + if (n->outstanding_aers <= 0) { + break; + } + if (n->aer_mask & (1 << event->result.event_type)) { + continue; + } + + QSIMPLEQ_REMOVE_HEAD(&n->aer_queue, entry); + n->aer_mask |= 1 << event->result.event_type; + n->outstanding_aers--; + + req = n->aer_reqs[n->outstanding_aers]; + result = (AerResult *)&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); + + req->cqe.status = NVME_SUCCESS << 1; + nvme_enqueue_req_completion(n->cq[0], req); + } +} + +static void nvme_update_stats(NvmeNamespace *ns, uint16_t nlb, int rw) +{ + uint64_t tmp; + if (!rw) { + if (++ns->host_write_commands[0] == 0) { + ++ns->host_write_commands[1]; + } + tmp = ns->data_units_written[0]; + ns->write_data_counter += nlb + 1; + ns->data_units_written[0] += (ns->write_data_counter / 1000); + ns->write_data_counter %= 1000; + if (tmp > ns->data_units_written[0]) { + ++ns->data_units_written[1]; + } + } else { + if (++ns->host_read_commands[0] == 0) { + ++ns->host_read_commands[1]; + } + tmp = ns->data_units_read[0]; + ns->read_data_counter += nlb + 1; + ns->data_units_read[0] += (ns->read_data_counter / 1000); + ns->read_data_counter %= 1000; + if (tmp > ns->data_units_read[0]) { + ++ns->data_units_read[1]; + } + } +} + +static void nvme_update_ns_util(NvmeNamespace *ns, uint64_t slba, uint16_t nlb) +{ + uint64_t nr; + uint64_t elba = slba + nlb; + unsigned long *addr = ns->util; + + for (nr = slba; nr <= elba; nr++) { + if (!test_and_set_bit(nr, addr)) { + assert(ns->id_ns.nuse < ns->id_ns.nsze); + ++ns->id_ns.nuse; + } + } +} + static void nvme_rw_cb(void *opaque, int ret) { NvmeRequest *req = opaque; NvmeSQueue *sq = req->sq; NvmeCtrl *n = sq->ctrl; NvmeCQueue *cq = n->cq[sq->cqid]; + NvmeNamespace *ns = req->ns; n = sq->ctrl; cq = n->cq[sq->cqid]; qemu_sglist_destroy(&req->qsg); req->aiocb = NULL; + + nvme_update_stats(ns, req->nlb, req->rw); + if (!req->rw) { + nvme_update_ns_util(ns, req->slba, req->nlb); + } + if (!ret) { req->cqe.status = NVME_SUCCESS << 1; } else { @@ -270,6 +365,10 @@ static uint16_t nvme_rw(NvmeCtrl *n, NvmeNamespace *ns, NvmeCmd *cmd, uint32_t nlb = (rw->nlb + 1) << (data_shift - 9); assert(nlb * BDRV_SECTOR_SIZE == req->qsg.size); + req->slba = rw->slba; + req->nlb = rw->nlb; + req->ns = ns; + req->rw = data_dir; req->aiocb = data_dir ? dma_bdrv_read(n->conf.bs, &req->qsg, slba, nvme_rw_cb, req) : dma_bdrv_write(n->conf.bs, &req->qsg, slba, nvme_rw_cb, req); @@ -599,6 +698,276 @@ static uint16_t nvme_set_feature(NvmeCtrl *n, NvmeCmd *cmd, NvmeRequest *req) return NVME_SUCCESS; } +static uint16_t nvme_fw_log_info(NvmeCtrl *n, NvmeCmd *cmd, uint32_t buf_len) +{ + uint32_t trans_len; + NvmeFwSlotInfoLog fw_log; + trans_len = MIN(sizeof(fw_log), buf_len); + return nvme_dma_prp(cmd->prp1, cmd->prp2, trans_len, n, (uint8_t *)&fw_log, + DMA_DIRECTION_FROM_DEVICE); +} + +static uint16_t nvme_error_log_info(NvmeCtrl *n, NvmeCmd *cmd, uint32_t buf_len) +{ + uint32_t trans_len; + trans_len = MIN(sizeof(*n->elpes) * n->elpe, buf_len); + n->aer_mask &= ~(1 << NVME_AER_TYPE_ERROR); + if (!QSIMPLEQ_EMPTY(&n->aer_queue)) { + qemu_mod_timer(n->aer_timer, qemu_get_clock_ns(vm_clock) + 10000); + } + return nvme_dma_prp(cmd->prp1, cmd->prp2, trans_len, n, (uint8_t *)n->elpes, + DMA_DIRECTION_FROM_DEVICE); +} + +static uint16_t nvme_async_req(NvmeCtrl *n, NvmeCmd *cmd, NvmeRequest *req) +{ + if (n->outstanding_aers > n->aerl + 1) { + return NVME_AER_LIMIT_EXCEEDED; + } + n->aer_reqs[n->outstanding_aers] = req; + qemu_mod_timer(n->aer_timer, qemu_get_clock_ns(vm_clock) + 10000); + n->outstanding_aers++; + return NVME_NO_COMPLETE; +} + +static uint16_t nvme_smart_info(NvmeCtrl *n, NvmeCmd *cmd, uint32_t buf_len) +{ + uint32_t trans_len; + time_t current_seconds; + NvmeSmartLog smart_log; + + trans_len = MIN(sizeof(smart_log), buf_len); + memset(&smart_log, 0x0, sizeof(smart_log)); + if (cmd->nsid == 0xffffffff || !(n->id_ctrl.lpa & 0x1)) { + int i; + uint64_t dur[2] = {0, 0}; + uint64_t duw[2] = {0, 0}; + uint64_t hrc[2] = {0, 0}; + uint64_t hwc[2] = {0, 0}; + uint64_t total_use = 0; + uint64_t total_size = 0; + for (i = 0; i < n->num_namespaces; ++i) { + uint64_t tmp; + NvmeNamespace *ns = &n->namespaces[i]; + if (ns == NULL) { + continue; + } + + tmp = dur[0]; + dur[0] += ns->data_units_read[0]; + dur[1] += ns->data_units_read[1]; + if (tmp > dur[0]) { + ++dur[1]; + } + + tmp = duw[0]; + duw[0] += ns->data_units_written[0]; + duw[1] += ns->data_units_written[1]; + if (tmp > duw[0]) { + ++duw[1]; + } + + tmp = hrc[0]; + hrc[0] += ns->host_read_commands[0]; + hrc[1] += ns->host_read_commands[1]; + if (tmp > hrc[0]) { + ++hrc[1]; + } + + tmp = hwc[0]; + hwc[0] += ns->host_write_commands[0]; + hwc[1] += ns->host_write_commands[1]; + if (tmp > hwc[0]) { + ++hwc[1]; + } + + total_size += ns->id_ns.nsze; + total_use += ns->id_ns.nuse; + } + + smart_log.data_units_read[0] = dur[0]; + smart_log.data_units_read[1] = dur[1]; + smart_log.data_units_written[0] = duw[0]; + smart_log.data_units_written[1] = duw[1]; + smart_log.host_read_commands[0] = hrc[0]; + smart_log.host_read_commands[1] = hrc[1]; + smart_log.host_write_commands[0] = hwc[0]; + smart_log.host_write_commands[1] = hwc[1]; + smart_log.available_spare = 100 - (uint32_t)((((double)total_use) / + total_size) * 100); + } else if (cmd->nsid > 0 && cmd->nsid <= n->num_namespaces && + (n->id_ctrl.lpa & 0x1)) { + NvmeNamespace *ns = &n->namespaces[cmd->nsid - 1]; + smart_log.data_units_read[0] = ns->data_units_read[0]; + smart_log.data_units_read[1] = ns->data_units_read[1]; + smart_log.data_units_written[0] = ns->data_units_written[0]; + smart_log.data_units_written[1] = ns->data_units_written[1]; + smart_log.host_read_commands[0] = ns->host_read_commands[0]; + smart_log.host_read_commands[1] = ns->host_read_commands[1]; + smart_log.host_write_commands[0] = ns->host_write_commands[0]; + smart_log.host_write_commands[1] = ns->host_write_commands[1]; + smart_log.available_spare = 100 - (uint32_t) + ((((double)ns->id_ns.nuse) / ns->id_ns.nsze) * 100); + } else { + return NVME_INVALID_NSID | NVME_DNR; + } + + smart_log.temperature[0] = n->temperature & 0xff; + smart_log.temperature[1] = (n->temperature >> 8) & 0xff; + smart_log.percentage_used = 0; + + current_seconds = time(NULL); + smart_log.power_on_hours[0] = ((current_seconds - n->start_time) / 60) / 60; + + smart_log.available_spare_threshold = NVME_SPARE_THRESHOLD; + if (smart_log.available_spare <= NVME_SPARE_THRESHOLD) { + smart_log.critical_warning |= NVME_SMART_SPARE; + } + if (n->features.temp_thresh <= n->temperature) { + smart_log.critical_warning |= NVME_SMART_TEMPERATURE; + } + + n->aer_mask &= ~(1 << NVME_AER_TYPE_SMART); + if (!QSIMPLEQ_EMPTY(&n->aer_queue)) { + qemu_mod_timer(n->aer_timer, qemu_get_clock_ns(vm_clock) + 10000); + } + return nvme_dma_prp(cmd->prp1, cmd->prp2, trans_len, n, (uint8_t *)&smart_log, + DMA_DIRECTION_FROM_DEVICE); +} + +static uint16_t nvme_get_log(NvmeCtrl *n, NvmeCmd *cmd) +{ + uint16_t lid = cmd->cdw10 & 0xffff; + uint32_t len = ((cmd->cdw10 >> 16) & 0xff) << 2; + switch (lid) { + case NVME_LOG_ERROR_INFO: + return nvme_error_log_info(n, cmd, len); + case NVME_LOG_SMART_INFO: + return nvme_smart_info(n, cmd, len); + case NVME_LOG_FW_SLOT_INFO: + return nvme_fw_log_info(n, cmd, len); + default: + return NVME_INVALID_LOG_ID | NVME_DNR; + } +} + +static uint16_t nvme_abort_req(NvmeCtrl *n, NvmeCmd *cmd, uint32_t *result) +{ + uint32_t index = 0; + uint16_t sqid = cmd->cdw10 & 0xffff; + uint16_t cid = (cmd->cdw10 >> 16) & 0xffff; + NvmeSQueue *sq; + + *result = 1; + if (nvme_check_sqid(n, sqid)) { + return NVME_SUCCESS; + } + + sq = n->sq[sqid]; + while ((sq->head + index) % sq->size != sq->tail) { + NvmeCmd abort_cmd; + hwaddr addr = sq->dma_addr + ((sq->head + index) % sq->size) * + n->sqe_size; + pci_dma_read(&n->dev, addr, (void *)&abort_cmd, sizeof(abort_cmd)); + if (abort_cmd.cid == cid) { + NvmeRequest *req = QTAILQ_FIRST(&sq->req_list); + QTAILQ_REMOVE(&sq->req_list, req, entry); + QTAILQ_INSERT_TAIL(&sq->out_req_list, req, entry); + memset(&req->cqe, 0, sizeof(req->cqe)); + req->cqe.cid = cid; + req->cqe.status = NVME_CMD_ABORT_REQ << 1; + abort_cmd.opcode = NVME_OP_ABORTED; + pci_dma_write(&n->dev, addr, (void *)&abort_cmd, sizeof(abort_cmd)); + nvme_enqueue_req_completion(n->cq[sq->cqid], req); + *result = 0; + break; + } + ++index; + } + return NVME_SUCCESS; +} + +static uint16_t nvme_format_namespace(NvmeNamespace *ns, uint8_t lba_idx, + uint8_t meta_loc, uint8_t pil, uint8_t pi, uint8_t sec_erase) +{ + uint64_t old_size; + uint8_t lbaf = NVME_ID_NS_FLBAS_INDEX(ns->id_ns.flbas); + + if (lba_idx > ns->id_ns.nlbaf) { + return NVME_INVALID_FORMAT | NVME_DNR; + } + if (pi) { + if (pil && !NVME_ID_NS_DPC_LAST_EIGHT(ns->id_ns.dpc)) { + return NVME_INVALID_FORMAT | NVME_DNR; + } + if (!pil && !NVME_ID_NS_DPC_FIRST_EIGHT(ns->id_ns.dpc)) { + return NVME_INVALID_FORMAT | NVME_DNR; + } + if (!((ns->id_ns.dpc & 0x7) & (1 << (pi - 1)))) { + return NVME_INVALID_FORMAT | NVME_DNR; + } + } + if (meta_loc && ns->id_ns.lbaf[lba_idx].ms && + !NVME_ID_NS_MC_EXTENDED(ns->id_ns.mc)) { + return NVME_INVALID_FORMAT | NVME_DNR; + } + if (!meta_loc && ns->id_ns.lbaf[lba_idx].ms && + !NVME_ID_NS_MC_SEPARATE(ns->id_ns.mc)) { + return NVME_INVALID_FORMAT | NVME_DNR; + } + + g_free(ns->util); + old_size = ns->id_ns.nsze * (1 << ns->id_ns.lbaf[lbaf].ds); + ns->id_ns.nuse = 0; + ns->id_ns.flbas = lba_idx | meta_loc; + ns->id_ns.nsze = old_size >> ns->id_ns.lbaf[lba_idx].ds; + ns->id_ns.ncap = ns->id_ns.nsze; + ns->id_ns.dps = pil | pi; + ns->util = bitmap_new(ns->id_ns.nsze); + + if (sec_erase) { + /* TODO: write zeros, complete asynchronously */ + ; + } + + return NVME_SUCCESS; +} + +static uint16_t nvme_format(NvmeCtrl *n, NvmeCmd *cmd) +{ + NvmeNamespace *ns; + uint32_t dw10 = cmd->cdw10; + uint32_t nsid = cmd->nsid; + uint8_t lba_idx = dw10 & 0xf; + uint8_t meta_loc = dw10 & 0x10; + uint8_t pil = (dw10 >> 5) & 0x8; + uint8_t pi = (dw10 >> 5) & 0x7; + uint8_t sec_erase = (dw10 >> 8) & 0x7; + + if (nsid == 0xffffffff) { + uint32_t i; + uint16_t ret; + + for (i = 0; i < n->num_namespaces; ++i) { + ns = &n->namespaces[i]; + ret = nvme_format_namespace(ns, lba_idx, meta_loc, pil, pi, + sec_erase); + if (ret != NVME_SUCCESS) { + return ret; + } + } + return ret; + } + + if (nsid == 0 || nsid > n->num_namespaces) { + return NVME_INVALID_NSID | NVME_DNR; + } + + ns = &n->namespaces[cmd->nsid - 1]; + return nvme_format_namespace(ns, lba_idx, meta_loc, pil, pi, + sec_erase); +} + static uint16_t nvme_admin_cmd(NvmeCtrl *n, NvmeCmd *cmd, NvmeRequest *req) { switch (cmd->opcode) { @@ -616,6 +985,18 @@ static uint16_t nvme_admin_cmd(NvmeCtrl *n, NvmeCmd *cmd, NvmeRequest *req) return nvme_set_feature(n, cmd, req); case NVME_ADM_CMD_GET_FEATURES: return nvme_get_feature(n, cmd, req); + case NVME_ADM_CMD_GET_LOG_PAGE: + return nvme_get_log(n, cmd); + case NVME_ADM_CMD_ASYNC_EV_REQ: + return nvme_async_req(n, cmd, req); + case NVME_ADM_CMD_FORMAT_NVM: + return nvme_format(n, cmd); + case NVME_ADM_CMD_ABORT: + return nvme_abort_req(n, cmd, &req->cqe.result); + case NVME_ADM_CMD_ACTIVATE_FW: + case NVME_ADM_CMD_DOWNLOAD_FW: + case NVME_ADM_CMD_SECURITY_SEND: + case NVME_ADM_CMD_SECURITY_RECV: default: return NVME_INVALID_OPCODE | NVME_DNR; } @@ -661,6 +1042,8 @@ static void nvme_sq_process(void *opaque) static void nvme_clear_ctrl(NvmeCtrl *n) { int i; + AsyncEvent *event; + for (i = 0; i < n->num_queues; i++) { if (n->sq[i] != NULL) { nvme_free_sq(n->sq[i], n); @@ -671,6 +1054,15 @@ static void nvme_clear_ctrl(NvmeCtrl *n) nvme_free_cq(n->cq[i], n); } } + if (n->aer_timer) { + qemu_del_timer(n->aer_timer); + qemu_free_timer(n->aer_timer); + n->aer_timer = NULL; + } + while ((event = QSIMPLEQ_FIRST(&n->aer_queue)) != NULL) { + QSIMPLEQ_REMOVE_HEAD(&n->aer_queue, entry); + g_free(event); + } n->bar.cc = 0; } @@ -718,6 +1110,9 @@ static int nvme_start_ctrl(NvmeCtrl *n) nvme_init_sq(&n->admin_sq, n, n->bar.asq, 0, 0, NVME_AQA_ASQS(n->bar.aqa) + 1, NVME_Q_PRIO_HIGH); + n->aer_timer = qemu_new_timer_ns(vm_clock, nvme_aer_process_cb, n); + QSIMPLEQ_INIT(&n->aer_queue); + return 0; } @@ -777,7 +1172,6 @@ static uint64_t nvme_mmio_read(void *opaque, hwaddr addr, unsigned size) NvmeCtrl *n = (NvmeCtrl *)opaque; uint8_t *ptr = (uint8_t *)&n->bar; uint64_t val = 0; - if (addr < sizeof(n->bar)) { memcpy(&val, ptr + addr, size); } @@ -788,6 +1182,8 @@ static void nvme_process_db(NvmeCtrl *n, hwaddr addr, int val) { uint32_t qid; if (addr & ((1 << (2 + n->db_stride)) - 1)) { + nvme_enqueue_event(n, NVME_AER_TYPE_ERROR, + NVME_AER_INFO_ERR_INVALID_DB, NVME_LOG_ERROR_INFO); return; } @@ -799,11 +1195,15 @@ static void nvme_process_db(NvmeCtrl *n, hwaddr addr, int val) qid = (addr - (0x1000 + (1 << (2 + n->db_stride)))) >> (3 + n->db_stride); if (nvme_check_cqid(n, qid)) { + nvme_enqueue_event(n, NVME_AER_TYPE_ERROR, + NVME_AER_INFO_ERR_INVALID_DB, NVME_LOG_ERROR_INFO); return; } cq = n->cq[qid]; if (new_head >= cq->size) { + nvme_enqueue_event(n, NVME_AER_TYPE_ERROR, + NVME_AER_INFO_ERR_INVALID_DB, NVME_LOG_ERROR_INFO); return; } @@ -825,11 +1225,15 @@ static void nvme_process_db(NvmeCtrl *n, hwaddr addr, int val) qid = (addr - 0x1000) >> (3 + n->db_stride); if (nvme_check_sqid(n, qid)) { + nvme_enqueue_event(n, NVME_AER_TYPE_ERROR, + NVME_AER_INFO_ERR_INVALID_SQ, NVME_LOG_ERROR_INFO); return; } sq = n->sq[qid]; if (new_tail >= sq->size) { + nvme_enqueue_event(n, NVME_AER_TYPE_ERROR, + NVME_AER_INFO_ERR_INVALID_DB, NVME_LOG_ERROR_INFO); return; } sq->tail = new_tail; @@ -905,6 +1309,7 @@ static int nvme_init(PCIDevice *pci_dev) pci_config_set_prog_interface(pci_dev->config, 0x2); pci_config_set_class(pci_dev->config, PCI_CLASS_STORAGE_EXPRESS); + n->start_time = time(NULL); n->reg_size = 1 << qemu_fls(0x1004 + 2 * (n->num_queues + 1) * 4); n->ns_size = bs_size / n->num_namespaces; n->instance = instance++; @@ -913,6 +1318,9 @@ static int nvme_init(PCIDevice *pci_dev) n->cq = g_malloc0(sizeof(*n->cq)*n->num_queues); n->features.int_vector_config = g_malloc(n->num_queues * sizeof(*n->features.int_vector_config)); + n->aer_reqs = g_malloc0((n->aerl + 1) * sizeof(*n->aer_reqs)); + n->elpes = g_malloc0((n->elpe + 1) * sizeof(*n->elpes)); + n->temperature = NVME_TEMPERATURE; memory_region_init_io(&n->iomem, &nvme_mmio_ops, n, "nvme-mmio", n->reg_size); @@ -928,8 +1336,11 @@ static int nvme_init(PCIDevice *pci_dev) id->ieee[1] = 0x02; id->ieee[2] = 0xb3; id->mdts = n->mdts; + id->oacs = NVME_OACS_FORMAT; id->acl = n->acl; id->aerl = n->aerl; + id->frmw = 7 << 1; + id->lpa = 1 << 0; id->elpe = n->elpe; id->sqes = n->max_sqes << 4 | 0x6; id->cqes = n->max_cqes << 4 | 0x4; @@ -983,6 +1394,7 @@ static int nvme_init(PCIDevice *pci_dev) ns->id = i + 1; ns->ctrl = n; ns->start_block = id_ns->nsze * i; + ns->util = bitmap_new(id_ns->nsze); } return 0; @@ -990,12 +1402,18 @@ static int nvme_init(PCIDevice *pci_dev) static void nvme_exit(PCIDevice *pci_dev) { + int i; NvmeCtrl *n = DO_UPCAST(NvmeCtrl, dev, pci_dev); nvme_clear_ctrl(n); + for (i = 0; i < n->num_namespaces; i++) { + g_free(n->namespaces[i].util); + } g_free(n->namespaces); g_free(n->cq); g_free(n->sq); g_free(n->features.int_vector_config); + g_free(n->aer_reqs); + g_free(n->elpes); msix_uninit_exclusive_bar(pci_dev); memory_region_destroy(&n->iomem); } diff --git a/hw/nvme.h b/hw/nvme.h index 6296f04..964e91d 100644 --- a/hw/nvme.h +++ b/hw/nvme.h @@ -589,7 +589,11 @@ static inline void _nvme_check_size(void) typedef struct NvmeRequest { struct NvmeSQueue *sq; + struct NvmeNamespace *ns; BlockDriverAIOCB *aiocb; + uint64_t slba; + uint16_t rw; + uint16_t nlb; NvmeCqe cqe; QEMUSGList qsg; QTAILQ_ENTRY(NvmeRequest)entry; @@ -634,6 +638,14 @@ typedef struct NvmeNamespace { NvmeIdNs id_ns; NvmeRangeType lba_range[64]; uint32_t id; + uint32_t write_data_counter; + uint32_t read_data_counter; + uint64_t data_units_read[2]; + uint64_t data_units_written[2]; + uint64_t host_read_commands[2]; + uint64_t host_write_commands[2]; + unsigned long *util; + unsigned long *uncorrectable; uint64_t start_block; } NvmeNamespace; @@ -643,7 +655,9 @@ typedef struct NvmeCtrl { NvmeBar bar; BlockConf conf; + time_t start_time; int instance; + uint16_t temperature; uint16_t page_size; uint16_t page_bits; uint16_t max_prp_ents; @@ -665,7 +679,10 @@ typedef struct NvmeCtrl { uint8_t meta; uint8_t vwc; uint8_t lba_index; + uint8_t outstanding_aers; + NvmeErrorLog *elpes; + NvmeRequest **aer_reqs; NvmeNamespace *namespaces; NvmeSQueue **sq; NvmeCQueue **cq; @@ -673,6 +690,10 @@ typedef struct NvmeCtrl { NvmeSQueue admin_sq; NvmeCQueue admin_cq; NvmeIdCtrl id_ctrl; + + QSIMPLEQ_HEAD(aer_queue, AsyncEvent) aer_queue; + QEMUTimer *aer_timer; + uint8_t aer_mask; } NvmeCtrl; #endif -- 1.7.0.4