mirror of
https://github.com/izzy2lost/xemu.git
synced 2026-07-06 00:20:22 -07:00
hw/block/nvme: support multiple namespaces
This adds support for multiple namespaces by introducing a new 'nvme-ns'
device model. The nvme device creates a bus named from the device name
('id'). The nvme-ns devices then connect to this and registers
themselves with the nvme device.
This changes how an nvme device is created. Example with two namespaces:
-drive file=nvme0n1.img,if=none,id=disk1
-drive file=nvme0n2.img,if=none,id=disk2
-device nvme,serial=deadbeef,id=nvme0
-device nvme-ns,drive=disk1,bus=nvme0,nsid=1
-device nvme-ns,drive=disk2,bus=nvme0,nsid=2
The drive property is kept on the nvme device to keep the change
backward compatible, but the property is now optional. Specifying a
drive for the nvme device will always create the namespace with nsid 1.
Signed-off-by: Klaus Jensen <k.jensen@samsung.com>
Reviewed-by: Keith Busch <kbusch@kernel.org>
Reviewed-by: Minwoo Im <minwoo.im.dev@gmail.com>
This commit is contained in:
@@ -13,7 +13,7 @@ softmmu_ss.add(when: 'CONFIG_SSI_M25P80', if_true: files('m25p80.c'))
|
||||
softmmu_ss.add(when: 'CONFIG_SWIM', if_true: files('swim.c'))
|
||||
softmmu_ss.add(when: 'CONFIG_XEN', if_true: files('xen-block.c'))
|
||||
softmmu_ss.add(when: 'CONFIG_SH4', if_true: files('tc58128.c'))
|
||||
softmmu_ss.add(when: 'CONFIG_NVME_PCI', if_true: files('nvme.c'))
|
||||
softmmu_ss.add(when: 'CONFIG_NVME_PCI', if_true: files('nvme.c', 'nvme-ns.c'))
|
||||
|
||||
specific_ss.add(when: 'CONFIG_VIRTIO_BLK', if_true: files('virtio-blk.c'))
|
||||
specific_ss.add(when: 'CONFIG_VHOST_USER_BLK', if_true: files('vhost-user-blk.c'))
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
* QEMU NVM Express Virtual Namespace
|
||||
*
|
||||
* Copyright (c) 2019 CNEX Labs
|
||||
* Copyright (c) 2020 Samsung Electronics
|
||||
*
|
||||
* Authors:
|
||||
* Klaus Jensen <k.jensen@samsung.com>
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2. See the
|
||||
* COPYING file in the top-level directory.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu/units.h"
|
||||
#include "qemu/cutils.h"
|
||||
#include "qemu/log.h"
|
||||
#include "hw/block/block.h"
|
||||
#include "hw/pci/pci.h"
|
||||
#include "sysemu/sysemu.h"
|
||||
#include "sysemu/block-backend.h"
|
||||
#include "qapi/error.h"
|
||||
|
||||
#include "hw/qdev-properties.h"
|
||||
#include "hw/qdev-core.h"
|
||||
|
||||
#include "nvme.h"
|
||||
#include "nvme-ns.h"
|
||||
|
||||
static void nvme_ns_init(NvmeNamespace *ns)
|
||||
{
|
||||
NvmeIdNs *id_ns = &ns->id_ns;
|
||||
|
||||
if (blk_get_flags(ns->blkconf.blk) & BDRV_O_UNMAP) {
|
||||
ns->id_ns.dlfeat = 0x9;
|
||||
}
|
||||
|
||||
id_ns->lbaf[0].ds = BDRV_SECTOR_BITS;
|
||||
|
||||
id_ns->nsze = cpu_to_le64(nvme_ns_nlbas(ns));
|
||||
|
||||
/* no thin provisioning */
|
||||
id_ns->ncap = id_ns->nsze;
|
||||
id_ns->nuse = id_ns->ncap;
|
||||
}
|
||||
|
||||
static int nvme_ns_init_blk(NvmeCtrl *n, NvmeNamespace *ns, Error **errp)
|
||||
{
|
||||
if (!blkconf_blocksizes(&ns->blkconf, errp)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!blkconf_apply_backend_options(&ns->blkconf,
|
||||
blk_is_read_only(ns->blkconf.blk),
|
||||
false, errp)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
ns->size = blk_getlength(ns->blkconf.blk);
|
||||
if (ns->size < 0) {
|
||||
error_setg_errno(errp, -ns->size, "could not get blockdev size");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (blk_enable_write_cache(ns->blkconf.blk)) {
|
||||
n->features.vwc = 0x1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int nvme_ns_check_constraints(NvmeNamespace *ns, Error **errp)
|
||||
{
|
||||
if (!ns->blkconf.blk) {
|
||||
error_setg(errp, "block backend not configured");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int nvme_ns_setup(NvmeCtrl *n, NvmeNamespace *ns, Error **errp)
|
||||
{
|
||||
if (nvme_ns_check_constraints(ns, errp)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (nvme_ns_init_blk(n, ns, errp)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
nvme_ns_init(ns);
|
||||
if (nvme_register_namespace(n, ns, errp)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void nvme_ns_drain(NvmeNamespace *ns)
|
||||
{
|
||||
blk_drain(ns->blkconf.blk);
|
||||
}
|
||||
|
||||
void nvme_ns_flush(NvmeNamespace *ns)
|
||||
{
|
||||
blk_flush(ns->blkconf.blk);
|
||||
}
|
||||
|
||||
static void nvme_ns_realize(DeviceState *dev, Error **errp)
|
||||
{
|
||||
NvmeNamespace *ns = NVME_NS(dev);
|
||||
BusState *s = qdev_get_parent_bus(dev);
|
||||
NvmeCtrl *n = NVME(s->parent);
|
||||
Error *local_err = NULL;
|
||||
|
||||
if (nvme_ns_setup(n, ns, &local_err)) {
|
||||
error_propagate_prepend(errp, local_err,
|
||||
"could not setup namespace: ");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static Property nvme_ns_props[] = {
|
||||
DEFINE_BLOCK_PROPERTIES(NvmeNamespace, blkconf),
|
||||
DEFINE_PROP_UINT32("nsid", NvmeNamespace, params.nsid, 0),
|
||||
DEFINE_PROP_END_OF_LIST(),
|
||||
};
|
||||
|
||||
static void nvme_ns_class_init(ObjectClass *oc, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(oc);
|
||||
|
||||
set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
|
||||
|
||||
dc->bus_type = TYPE_NVME_BUS;
|
||||
dc->realize = nvme_ns_realize;
|
||||
device_class_set_props(dc, nvme_ns_props);
|
||||
dc->desc = "Virtual NVMe namespace";
|
||||
}
|
||||
|
||||
static void nvme_ns_instance_init(Object *obj)
|
||||
{
|
||||
NvmeNamespace *ns = NVME_NS(obj);
|
||||
char *bootindex = g_strdup_printf("/namespace@%d,0", ns->params.nsid);
|
||||
|
||||
device_add_bootindex_property(obj, &ns->bootindex, "bootindex",
|
||||
bootindex, DEVICE(obj));
|
||||
|
||||
g_free(bootindex);
|
||||
}
|
||||
|
||||
static const TypeInfo nvme_ns_info = {
|
||||
.name = TYPE_NVME_NS,
|
||||
.parent = TYPE_DEVICE,
|
||||
.class_init = nvme_ns_class_init,
|
||||
.instance_size = sizeof(NvmeNamespace),
|
||||
.instance_init = nvme_ns_instance_init,
|
||||
};
|
||||
|
||||
static void nvme_ns_register_types(void)
|
||||
{
|
||||
type_register_static(&nvme_ns_info);
|
||||
}
|
||||
|
||||
type_init(nvme_ns_register_types)
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* QEMU NVM Express Virtual Namespace
|
||||
*
|
||||
* Copyright (c) 2019 CNEX Labs
|
||||
* Copyright (c) 2020 Samsung Electronics
|
||||
*
|
||||
* Authors:
|
||||
* Klaus Jensen <k.jensen@samsung.com>
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2. See the
|
||||
* COPYING file in the top-level directory.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NVME_NS_H
|
||||
#define NVME_NS_H
|
||||
|
||||
#define TYPE_NVME_NS "nvme-ns"
|
||||
#define NVME_NS(obj) \
|
||||
OBJECT_CHECK(NvmeNamespace, (obj), TYPE_NVME_NS)
|
||||
|
||||
typedef struct NvmeNamespaceParams {
|
||||
uint32_t nsid;
|
||||
} NvmeNamespaceParams;
|
||||
|
||||
typedef struct NvmeNamespace {
|
||||
DeviceState parent_obj;
|
||||
BlockConf blkconf;
|
||||
int32_t bootindex;
|
||||
int64_t size;
|
||||
NvmeIdNs id_ns;
|
||||
|
||||
NvmeNamespaceParams params;
|
||||
} NvmeNamespace;
|
||||
|
||||
static inline uint32_t nvme_nsid(NvmeNamespace *ns)
|
||||
{
|
||||
if (ns) {
|
||||
return ns->params.nsid;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static inline NvmeLBAF *nvme_ns_lbaf(NvmeNamespace *ns)
|
||||
{
|
||||
NvmeIdNs *id_ns = &ns->id_ns;
|
||||
return &id_ns->lbaf[NVME_ID_NS_FLBAS_INDEX(id_ns->flbas)];
|
||||
}
|
||||
|
||||
static inline uint8_t nvme_ns_lbads(NvmeNamespace *ns)
|
||||
{
|
||||
return nvme_ns_lbaf(ns)->ds;
|
||||
}
|
||||
|
||||
/* calculate the number of LBAs that the namespace can accomodate */
|
||||
static inline uint64_t nvme_ns_nlbas(NvmeNamespace *ns)
|
||||
{
|
||||
return ns->size >> nvme_ns_lbads(ns);
|
||||
}
|
||||
|
||||
/* convert an LBA to the equivalent in bytes */
|
||||
static inline size_t nvme_l2b(NvmeNamespace *ns, uint64_t lba)
|
||||
{
|
||||
return lba << nvme_ns_lbads(ns);
|
||||
}
|
||||
|
||||
typedef struct NvmeCtrl NvmeCtrl;
|
||||
|
||||
int nvme_ns_setup(NvmeCtrl *n, NvmeNamespace *ns, Error **errp);
|
||||
void nvme_ns_drain(NvmeNamespace *ns);
|
||||
void nvme_ns_flush(NvmeNamespace *ns);
|
||||
|
||||
#endif /* NVME_NS_H */
|
||||
+162
-87
File diff suppressed because it is too large
Load Diff
+21
-25
@@ -2,6 +2,9 @@
|
||||
#define HW_NVME_H
|
||||
|
||||
#include "block/nvme.h"
|
||||
#include "nvme-ns.h"
|
||||
|
||||
#define NVME_MAX_NAMESPACES 256
|
||||
|
||||
typedef struct NvmeParams {
|
||||
char *serial;
|
||||
@@ -90,26 +93,12 @@ typedef struct NvmeCQueue {
|
||||
QTAILQ_HEAD(, NvmeRequest) req_list;
|
||||
} NvmeCQueue;
|
||||
|
||||
typedef struct NvmeNamespace {
|
||||
NvmeIdNs id_ns;
|
||||
} NvmeNamespace;
|
||||
#define TYPE_NVME_BUS "nvme-bus"
|
||||
#define NVME_BUS(obj) OBJECT_CHECK(NvmeBus, (obj), TYPE_NVME_BUS)
|
||||
|
||||
static inline NvmeLBAF *nvme_ns_lbaf(NvmeNamespace *ns)
|
||||
{
|
||||
NvmeIdNs *id_ns = &ns->id_ns;
|
||||
return &id_ns->lbaf[NVME_ID_NS_FLBAS_INDEX(id_ns->flbas)];
|
||||
}
|
||||
|
||||
static inline uint8_t nvme_ns_lbads(NvmeNamespace *ns)
|
||||
{
|
||||
return nvme_ns_lbaf(ns)->ds;
|
||||
}
|
||||
|
||||
/* convert an LBA to the equivalent in bytes */
|
||||
static inline size_t nvme_l2b(NvmeNamespace *ns, uint64_t lba)
|
||||
{
|
||||
return lba << nvme_ns_lbads(ns);
|
||||
}
|
||||
typedef struct NvmeBus {
|
||||
BusState parent_bus;
|
||||
} NvmeBus;
|
||||
|
||||
#define TYPE_NVME "nvme"
|
||||
#define NVME(obj) \
|
||||
@@ -121,6 +110,7 @@ typedef struct NvmeFeatureVal {
|
||||
uint16_t temp_thresh_low;
|
||||
};
|
||||
uint32_t async_config;
|
||||
uint32_t vwc;
|
||||
} NvmeFeatureVal;
|
||||
|
||||
typedef struct NvmeCtrl {
|
||||
@@ -128,8 +118,9 @@ typedef struct NvmeCtrl {
|
||||
MemoryRegion iomem;
|
||||
MemoryRegion ctrl_mem;
|
||||
NvmeBar bar;
|
||||
BlockConf conf;
|
||||
NvmeParams params;
|
||||
NvmeBus bus;
|
||||
BlockConf conf;
|
||||
|
||||
bool qs_created;
|
||||
uint32_t page_size;
|
||||
@@ -140,7 +131,6 @@ typedef struct NvmeCtrl {
|
||||
uint32_t reg_size;
|
||||
uint32_t num_namespaces;
|
||||
uint32_t max_q_ents;
|
||||
uint64_t ns_size;
|
||||
uint8_t outstanding_aers;
|
||||
uint8_t *cmbuf;
|
||||
uint32_t irq_status;
|
||||
@@ -156,7 +146,8 @@ typedef struct NvmeCtrl {
|
||||
QTAILQ_HEAD(, NvmeAsyncEvent) aer_queue;
|
||||
int aer_queued;
|
||||
|
||||
NvmeNamespace *namespaces;
|
||||
NvmeNamespace namespace;
|
||||
NvmeNamespace *namespaces[NVME_MAX_NAMESPACES];
|
||||
NvmeSQueue **sq;
|
||||
NvmeCQueue **cq;
|
||||
NvmeSQueue admin_sq;
|
||||
@@ -165,10 +156,13 @@ typedef struct NvmeCtrl {
|
||||
NvmeFeatureVal features;
|
||||
} NvmeCtrl;
|
||||
|
||||
/* calculate the number of LBAs that the namespace can accomodate */
|
||||
static inline uint64_t nvme_ns_nlbas(NvmeCtrl *n, NvmeNamespace *ns)
|
||||
static inline NvmeNamespace *nvme_ns(NvmeCtrl *n, uint32_t nsid)
|
||||
{
|
||||
return n->ns_size >> nvme_ns_lbads(ns);
|
||||
if (!nsid || nsid > n->num_namespaces) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return n->namespaces[nsid - 1];
|
||||
}
|
||||
|
||||
static inline NvmeCQueue *nvme_cq(NvmeRequest *req)
|
||||
@@ -185,4 +179,6 @@ static inline NvmeCtrl *nvme_ctrl(NvmeRequest *req)
|
||||
return sq->ctrl;
|
||||
}
|
||||
|
||||
int nvme_register_namespace(NvmeCtrl *n, NvmeNamespace *ns, Error **errp);
|
||||
|
||||
#endif /* HW_NVME_H */
|
||||
|
||||
@@ -29,6 +29,7 @@ hd_geometry_guess(void *blk, uint32_t cyls, uint32_t heads, uint32_t secs, int t
|
||||
|
||||
# nvme.c
|
||||
# nvme traces for successful events
|
||||
pci_nvme_register_namespace(uint32_t nsid) "nsid %"PRIu32""
|
||||
pci_nvme_irq_msix(uint32_t vector) "raising MSI-X IRQ vector %u"
|
||||
pci_nvme_irq_pin(void) "pulsing IRQ pin"
|
||||
pci_nvme_irq_masked(void) "IRQ is masked"
|
||||
@@ -39,9 +40,9 @@ pci_nvme_map_prp(uint64_t trans_len, uint32_t len, uint64_t prp1, uint64_t prp2,
|
||||
pci_nvme_map_sgl(uint16_t cid, uint8_t typ, uint64_t len) "cid %"PRIu16" type 0x%"PRIx8" len %"PRIu64""
|
||||
pci_nvme_io_cmd(uint16_t cid, uint32_t nsid, uint16_t sqid, uint8_t opcode, const char *opname) "cid %"PRIu16" nsid %"PRIu32" sqid %"PRIu16" opc 0x%"PRIx8" opname '%s'"
|
||||
pci_nvme_admin_cmd(uint16_t cid, uint16_t sqid, uint8_t opcode, const char *opname) "cid %"PRIu16" sqid %"PRIu16" opc 0x%"PRIx8" opname '%s'"
|
||||
pci_nvme_rw(uint16_t cid, const char *verb, uint32_t nlb, uint64_t count, uint64_t lba) "cid %"PRIu16" '%s' nlb %"PRIu32" count %"PRIu64" lba 0x%"PRIx64""
|
||||
pci_nvme_rw(uint16_t cid, const char *verb, uint32_t nsid, uint32_t nlb, uint64_t count, uint64_t lba) "cid %"PRIu16" opname '%s' nsid %"PRIu32" nlb %"PRIu32" count %"PRIu64" lba 0x%"PRIx64""
|
||||
pci_nvme_rw_cb(uint16_t cid, const char *blkname) "cid %"PRIu16" blk '%s'"
|
||||
pci_nvme_write_zeroes(uint16_t cid, uint64_t slba, uint32_t nlb) "cid %"PRIu16" slba %"PRIu64" nlb %"PRIu32""
|
||||
pci_nvme_write_zeroes(uint16_t cid, uint32_t nsid, uint64_t slba, uint32_t nlb) "cid %"PRIu16" nsid %"PRIu32" slba %"PRIu64" nlb %"PRIu32""
|
||||
pci_nvme_do_aio(uint16_t cid, uint8_t opc, const char *opname, const char *blkname, int64_t offset, size_t len) "cid %"PRIu16" opc 0x%"PRIx8" opname '%s' blk '%s' offset %"PRId64" len %zu"
|
||||
pci_nvme_create_sq(uint64_t addr, uint16_t sqid, uint16_t cqid, uint16_t qsize, uint16_t qflags) "create submission queue, addr=0x%"PRIx64", sqid=%"PRIu16", cqid=%"PRIu16", qsize=%"PRIu16", qflags=%"PRIu16""
|
||||
pci_nvme_create_cq(uint64_t addr, uint16_t cqid, uint16_t vector, uint16_t size, uint16_t qflags, int ien) "create completion queue, addr=0x%"PRIx64", cqid=%"PRIu16", vector=%"PRIu16", qsize=%"PRIu16", qflags=%"PRIu16", ien=%d"
|
||||
@@ -100,7 +101,6 @@ pci_nvme_err_invalid_prplist_ent(uint64_t prplist) "PRP list entry is null or no
|
||||
pci_nvme_err_invalid_prp2_align(uint64_t prp2) "PRP2 is not page aligned: 0x%"PRIx64""
|
||||
pci_nvme_err_invalid_prp2_missing(void) "PRP2 is null and more data to be transferred"
|
||||
pci_nvme_err_invalid_prp(void) "invalid PRP"
|
||||
pci_nvme_err_invalid_ns(uint32_t ns, uint32_t limit) "invalid namespace %u not within 1-%u"
|
||||
pci_nvme_err_invalid_opc(uint8_t opc) "invalid opcode 0x%"PRIx8""
|
||||
pci_nvme_err_invalid_admin_opc(uint8_t opc) "invalid admin opcode 0x%"PRIx8""
|
||||
pci_nvme_err_invalid_lba_range(uint64_t start, uint64_t len, uint64_t limit) "Invalid LBA start=%"PRIu64" len=%"PRIu64" limit=%"PRIu64""
|
||||
|
||||
Reference in New Issue
Block a user