Patchwork [04/11] QEMU NVMe: Implement additional admin commands

login
register
mail settings
Submitter Keith Busch
Date Feb. 27, 2013, 12:47 a.m.
Message ID <1361926034-21824-5-git-send-email-keith.busch@intel.com>
Download mbox | patch
Permalink /patch/223460/
State New
Headers show

Comments

Keith Busch - Feb. 27, 2013, 12:47 a.m.
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.busch@intel.com>
---
 hw/nvme.c |  420 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 hw/nvme.h |   21 +++
 2 files changed, 440 insertions(+), 1 deletions(-)

Patch

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