[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Qemu-devel] [PATCH] hw/block/nvme: Add doorbell buffer config support
From: |
Huaicheng Li |
Subject: |
[Qemu-devel] [PATCH] hw/block/nvme: Add doorbell buffer config support |
Date: |
Mon, 5 Mar 2018 13:49:10 -0600 |
User-agent: |
Mutt/1.9.1 (2017-09-22) |
This patch adds Doorbell Buffer Config support (NVMe 1.3) to QEMU NVMe,
based on Mihai Rusu / Lin Ming's Google vendor extension patch [1]. The
basic idea of this optimization is to use a shared buffer between guest
OS and QEMU to reduce # of MMIO operations (doorbell writes). This patch
ports the original code to work under current QEMU and make it also
work with SPDK.
Unlike Linux kernel NVMe driver which builds the shadow buffer first and
then creates SQ/CQ, SPDK first creates SQ/CQ and then issues this command
to create shadow buffer. Thus, in this implementation, we also try to
associate shadow buffer entry with each SQ/CQ during queue initialization.
[1] http://lists.nongnu.org/archive/html/qemu-devel/2015-11/msg04127.html
Peroformance results using a **ramdisk** backed virtual NVMe device in guest
Linux 4.14 is as below:
Note: "QEMU" represent stock QEMU and "+dbbuf" is QEMU with this patch.
For psync, QD represents # of threads being used.
IOPS (Linux kernel NVMe driver)
psync libaio
QD QEMU +dbbuf QEMU +dbbuf
1 47k 50k 45k 47k
4 86k 107k 59k 143k
16 95k 198k 58k 185k
64 97k 259k 59k 216k
IOPS (SPDK)
QD QEMU +dbbuf
1 62k 71k
4 61k 191k
16 60k 319k
64 62k 364k
We can see that this patch can greatly increase the IOPS (and lower the
latency, not shown) (2.7x for psync, 3.7x for libaio and 5.9x for SPDK).
==Setup==:
(1) VM script:
x86_64-softmmu/qemu-system-x86_64 \
-name "nvme-FEMU-test" \
-enable-kvm \
-cpu host \
-smp 4 \
-m 8G \
-drive file=$IMGDIR/u14s.qcow2,if=ide,aio=native,cache=none,format=qcow2,id=hd0
\
-drive file=/mnt/tmpfs/test1.raw,if=none,aio=threads,format=raw,id=id0 \
-device nvme,drive=id0,serial=serial0,id=nvme0 \
-net user,hostfwd=tcp::8080-:22 \
-net nic,model=virtio \
-nographic \
(2) FIO configuration:
[global]
ioengine=libaio
filename=/dev/nvme0n1
thread=1
group_reporting=1
direct=1
verify=0
time_based=1
ramp_time=0
runtime=30
;size=1G
iodepth=16
rw=randread
bs=4k
[test]
numjobs=1
Signed-off-by: Huaicheng Li <address@hidden>
---
hw/block/nvme.c | 97 +++++++++++++++++++++++++++++++++++++++++++++++++---
hw/block/nvme.h | 7 ++++
include/block/nvme.h | 2 ++
3 files changed, 102 insertions(+), 4 deletions(-)
diff --git a/hw/block/nvme.c b/hw/block/nvme.c
index 85d2406400..3882037e36 100644
--- a/hw/block/nvme.c
+++ b/hw/block/nvme.c
@@ -9,7 +9,7 @@
*/
/**
- * Reference Specs: http://www.nvmexpress.org, 1.2, 1.1, 1.0e
+ * Reference Specs: http://www.nvmexpress.org, 1.3, 1.2, 1.1, 1.0e
*
* http://www.nvmexpress.org/resources/
*/
@@ -33,6 +33,7 @@
#include "qapi/error.h"
#include "qapi/visitor.h"
#include "sysemu/block-backend.h"
+#include "exec/memory.h"
#include "qemu/log.h"
#include "trace.h"
@@ -244,6 +245,14 @@ static uint16_t nvme_dma_read_prp(NvmeCtrl *n, uint8_t
*ptr, uint32_t len,
return status;
}
+static void nvme_update_cq_head(NvmeCQueue *cq)
+{
+ if (cq->db_addr) {
+ pci_dma_read(&cq->ctrl->parent_obj, cq->db_addr, &cq->head,
+ sizeof(cq->head));
+ }
+}
+
static void nvme_post_cqes(void *opaque)
{
NvmeCQueue *cq = opaque;
@@ -254,6 +263,8 @@ static void nvme_post_cqes(void *opaque)
NvmeSQueue *sq;
hwaddr addr;
+ nvme_update_cq_head(cq);
+
if (nvme_cq_full(cq)) {
break;
}
@@ -461,6 +472,7 @@ static uint16_t nvme_del_sq(NvmeCtrl *n, NvmeCmd *cmd)
static void nvme_init_sq(NvmeSQueue *sq, NvmeCtrl *n, uint64_t dma_addr,
uint16_t sqid, uint16_t cqid, uint16_t size)
{
+ uint32_t stride = 4 << NVME_CAP_DSTRD(n->bar.cap);
int i;
NvmeCQueue *cq;
@@ -480,6 +492,11 @@ static void nvme_init_sq(NvmeSQueue *sq, NvmeCtrl *n,
uint64_t dma_addr,
}
sq->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, nvme_process_sq, sq);
+ if (sqid && n->dbbuf_dbs && n->dbbuf_eis) {
+ sq->db_addr = n->dbbuf_dbs + 2 * sqid * stride;
+ sq->ei_addr = n->dbbuf_eis + 2 * sqid * stride;
+ }
+
assert(n->cq[cqid]);
cq = n->cq[cqid];
QTAILQ_INSERT_TAIL(&(cq->sq_list), sq, entry);
@@ -559,6 +576,8 @@ static uint16_t nvme_del_cq(NvmeCtrl *n, NvmeCmd *cmd)
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)
{
+ uint32_t stride = 4 << NVME_CAP_DSTRD(n->bar.cap);
+
cq->ctrl = n;
cq->cqid = cqid;
cq->size = size;
@@ -569,11 +588,51 @@ static void nvme_init_cq(NvmeCQueue *cq, NvmeCtrl *n,
uint64_t dma_addr,
cq->head = cq->tail = 0;
QTAILQ_INIT(&cq->req_list);
QTAILQ_INIT(&cq->sq_list);
+ if (cqid && n->dbbuf_dbs && n->dbbuf_eis) {
+ cq->db_addr = n->dbbuf_dbs + (2 * cqid + 1) * stride;
+ cq->ei_addr = n->dbbuf_eis + (2 * cqid + 1) * stride;
+ }
msix_vector_use(&n->parent_obj, cq->vector);
n->cq[cqid] = cq;
cq->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, nvme_post_cqes, cq);
}
+static uint16_t nvme_dbbuf_config(NvmeCtrl *n, const NvmeCmd *cmd)
+{
+ uint32_t stride = 4 << NVME_CAP_DSTRD(n->bar.cap);
+ uint64_t dbs_addr = le64_to_cpu(cmd->prp1);
+ uint64_t eis_addr = le64_to_cpu(cmd->prp2);
+ int i;
+
+ /* Address should not be NULL and should be page aligned */
+ if (dbs_addr == 0 || dbs_addr & (n->page_size - 1) ||
+ eis_addr == 0 || 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;
+
+ for (i = 1; i < n->num_queues; i++) {
+ NvmeSQueue *sq = n->sq[i];
+ NvmeCQueue *cq = n->cq[i];
+
+ if (sq) {
+ /* Submission queue tail pointer location, 2 * QID * stride */
+ sq->db_addr = dbs_addr + 2 * i * stride;
+ sq->ei_addr = eis_addr + 2 * i * stride;
+ }
+
+ if (cq) {
+ /* Completion queue head pointer location, (2 * QID + 1) * stride
*/
+ cq->db_addr = dbs_addr + (2 * i + 1) * stride;
+ cq->ei_addr = eis_addr + (2 * i + 1) * stride;
+ }
+ }
+ return NVME_SUCCESS;
+}
+
static uint16_t nvme_create_cq(NvmeCtrl *n, NvmeCmd *cmd)
{
NvmeCQueue *cq;
@@ -753,12 +812,30 @@ 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_DBBUF_CONFIG:
+ return nvme_dbbuf_config(n, cmd);
default:
trace_nvme_err_invalid_admin_opc(cmd->opcode);
return NVME_INVALID_OPCODE | NVME_DNR;
}
}
+static void nvme_update_sq_eventidx(const NvmeSQueue *sq)
+{
+ if (sq->ei_addr) {
+ pci_dma_write(&sq->ctrl->parent_obj, sq->ei_addr, &sq->tail,
+ sizeof(sq->tail));
+ }
+}
+
+static void nvme_update_sq_tail(NvmeSQueue *sq)
+{
+ if (sq->db_addr) {
+ pci_dma_read(&sq->ctrl->parent_obj, sq->db_addr, &sq->tail,
+ sizeof(sq->tail));
+ }
+}
+
static void nvme_process_sq(void *opaque)
{
NvmeSQueue *sq = opaque;
@@ -770,6 +847,8 @@ static void nvme_process_sq(void *opaque)
NvmeCmd cmd;
NvmeRequest *req;
+ nvme_update_sq_tail(sq);
+
while (!(nvme_sq_empty(sq) || QTAILQ_EMPTY(&sq->req_list))) {
addr = sq->dma_addr + sq->head * n->sqe_size;
nvme_addr_read(n, addr, (void *)&cmd, sizeof(cmd));
@@ -787,6 +866,9 @@ static void nvme_process_sq(void *opaque)
req->status = status;
nvme_enqueue_req_completion(cq, req);
}
+
+ nvme_update_sq_eventidx(sq);
+ nvme_update_sq_tail(sq);
}
}
@@ -1105,7 +1187,9 @@ static void nvme_process_db(NvmeCtrl *n, hwaddr addr, int
val)
}
start_sqs = nvme_cq_full(cq) ? 1 : 0;
- cq->head = new_head;
+ if (!cq->db_addr) {
+ cq->head = new_head;
+ }
if (start_sqs) {
NvmeSQueue *sq;
QTAILQ_FOREACH(sq, &cq->sq_list, entry) {
@@ -1142,7 +1226,9 @@ static void nvme_process_db(NvmeCtrl *n, hwaddr addr, int
val)
return;
}
- sq->tail = new_tail;
+ if (!sq->db_addr) {
+ sq->tail = new_tail;
+ }
timer_mod(sq->timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + 500);
}
}
@@ -1256,7 +1342,7 @@ static void nvme_realize(PCIDevice *pci_dev, Error **errp)
id->ieee[0] = 0x00;
id->ieee[1] = 0x02;
id->ieee[2] = 0xb3;
- id->oacs = cpu_to_le16(0);
+ id->oacs = cpu_to_le16(NVME_OACS_DBBUF);
id->frmw = 7 << 1;
id->lpa = 1 << 0;
id->sqes = (0x6 << 4) | 0x6;
@@ -1320,6 +1406,9 @@ static void nvme_realize(PCIDevice *pci_dev, Error **errp)
cpu_to_le64(n->ns_size >>
id_ns->lbaf[NVME_ID_NS_FLBAS_INDEX(ns->id_ns.flbas)].ds);
}
+
+ n->dbbuf_dbs = 0;
+ n->dbbuf_eis = 0;
}
static void nvme_exit(PCIDevice *pci_dev)
diff --git a/hw/block/nvme.h b/hw/block/nvme.h
index 8f3981121d..b532dbe160 100644
--- a/hw/block/nvme.h
+++ b/hw/block/nvme.h
@@ -33,6 +33,8 @@ typedef struct NvmeSQueue {
QTAILQ_HEAD(sq_req_list, NvmeRequest) req_list;
QTAILQ_HEAD(out_req_list, NvmeRequest) out_req_list;
QTAILQ_ENTRY(NvmeSQueue) entry;
+ uint64_t db_addr;
+ uint64_t ei_addr;
} NvmeSQueue;
typedef struct NvmeCQueue {
@@ -48,6 +50,8 @@ typedef struct NvmeCQueue {
QEMUTimer *timer;
QTAILQ_HEAD(sq_list, NvmeSQueue) sq_list;
QTAILQ_HEAD(cq_req_list, NvmeRequest) req_list;
+ uint64_t db_addr;
+ uint64_t ei_addr;
} NvmeCQueue;
typedef struct NvmeNamespace {
@@ -88,6 +92,9 @@ typedef struct NvmeCtrl {
NvmeSQueue admin_sq;
NvmeCQueue admin_cq;
NvmeIdCtrl id_ctrl;
+
+ uint64_t dbbuf_dbs;
+ uint64_t dbbuf_eis;
} NvmeCtrl;
#endif /* HW_NVME_H */
diff --git a/include/block/nvme.h b/include/block/nvme.h
index 849a6f3fa3..4890aaf491 100644
--- a/include/block/nvme.h
+++ b/include/block/nvme.h
@@ -235,6 +235,7 @@ enum NvmeAdminCommands {
NVME_ADM_CMD_ASYNC_EV_REQ = 0x0c,
NVME_ADM_CMD_ACTIVATE_FW = 0x10,
NVME_ADM_CMD_DOWNLOAD_FW = 0x11,
+ NVME_ADM_CMD_DBBUF_CONFIG = 0x7c,
NVME_ADM_CMD_FORMAT_NVM = 0x80,
NVME_ADM_CMD_SECURITY_SEND = 0x81,
NVME_ADM_CMD_SECURITY_RECV = 0x82,
@@ -572,6 +573,7 @@ enum NvmeIdCtrlOacs {
NVME_OACS_SECURITY = 1 << 0,
NVME_OACS_FORMAT = 1 << 1,
NVME_OACS_FW = 1 << 2,
+ NVME_OACS_DBBUF = 1 << 8,
};
enum NvmeIdCtrlOncs {
--
2.16.1
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [Qemu-devel] [PATCH] hw/block/nvme: Add doorbell buffer config support,
Huaicheng Li <=