From: Clay Mayers <clay.mayers@kioxia.com>
If a namespace's param.zoned.finish_time is non-zero,
controllers register with the namespace to be notified
when entries are added to its zone-descriptor-changed
log page. If the zone-descriptor-changed aen is enabled,
this will cause an AEN to be sent from that controller.
Signed-off-by: Clay Mayers <clay.mayers@kioxia.com>
---
hw/nvme/ctrl.c | 63 +++++++++++++++++++++++++++++++++++++++++++-
hw/nvme/ns.c | 1 +
hw/nvme/nvme.h | 11 ++++++++
include/block/nvme.h | 2 ++
4 files changed, 76 insertions(+), 1 deletion(-)
diff --git a/hw/nvme/ctrl.c b/hw/nvme/ctrl.c
index 4fb85160cc..be6147363d 100644
--- a/hw/nvme/ctrl.c
+++ b/hw/nvme/ctrl.c
@@ -1519,6 +1519,52 @@ static void nvme_clear_events(NvmeCtrl *n, uint8_t event_type)
}
}
+static void nvme_zdc_watch(NvmeCtrl *n, NvmeNamespace *ns, NvmeNotifyFnc fnc)
+{
+ NvmeZdcNotify *watcher = g_malloc0(sizeof(*watcher));
+
+ watcher->n = n;
+ watcher->notify = fnc;
+ QTAILQ_INSERT_TAIL(&ns->zdc_watchers, watcher, entry);
+}
+
+static void nvme_zdc_unwatch(NvmeCtrl *n, NvmeNamespace *ns)
+{
+ NvmeZdcNotify *watcher;
+
+ QTAILQ_FOREACH(watcher, &ns->zdc_watchers, entry) {
+ if (watcher->n == n) {
+ QTAILQ_REMOVE(&ns->zdc_watchers, watcher, entry);
+ break;
+ }
+ }
+}
+
+static void nvme_zdc_notify(NvmeNamespace *ns)
+{
+ NvmeZdcNotify *watcher;
+
+ QTAILQ_FOREACH(watcher, &ns->zdc_watchers, entry) {
+ (*watcher->notify)(watcher->n, ns);
+ }
+}
+
+static void nvme_zdc_aen(NvmeCtrl *n, NvmeNamespace *ns)
+{
+ g_assert(n->id_ctrl.oaes & (1 << 27));
+
+ if (!NVME_AEC_ZONE_CHANGED(n->features.async_config)) {
+ return;
+ }
+
+ if (!n->zdc_event_queued) {
+ n->zdc_event_queued = true;
+ nvme_enqueue_event(n, NVME_AER_TYPE_NOTICE,
+ NVME_AER_INFO_NOTICE_ZONE_DESC_CHANGED,
+ NVME_LOG_CHANGED_ZONE, ns->params.nsid);
+ }
+}
+
static void nvme_zdc_list(NvmeNamespace *ns, NvmeZoneIdList *zlist, bool reset)
{
NvmeZdc *zdc;
@@ -1554,6 +1600,7 @@ static void nvme_check_finish(NvmeNamespace *ns, NvmeZoneListHead *list)
zdc->zone = zone;
zone->zdc_entry = zdc;
QTAILQ_INSERT_TAIL(&ns->zdc_list, zdc, entry);
+ nvme_zdc_notify(ns);
}
} else if (zone->finish_ms != INT64_MAX) {
timer_mod_anticipate(ns->active_timer, zone->finish_ms);
@@ -4727,6 +4774,14 @@ static uint16_t nvme_changed_zones(NvmeCtrl *n, uint8_t rae, uint32_t buf_len,
return NVME_INVALID_NSID | NVME_DNR;
}
nvme_zdc_list(ns, &zlist, !rae);
+ if (!rae) {
+ n->zdc_event_queued = false;
+ nvme_clear_events(n, NVME_AER_TYPE_NOTICE);
+ /* send a new aen if there are still zdc entries */
+ if (!QTAILQ_EMPTY(&ns->zdc_list)) {
+ nvme_zdc_notify(ns);
+ }
+ }
trans_len = MIN(sizeof(zlist) - off, buf_len);
@@ -5815,6 +5870,7 @@ static uint16_t nvme_ns_attachment(NvmeCtrl *n, NvmeRequest *req)
return NVME_NS_NOT_ATTACHED | NVME_DNR;
}
+ nvme_zdc_unwatch(n, ns);
ctrl->namespaces[nsid] = NULL;
ns->attached--;
@@ -7542,7 +7598,7 @@ static void nvme_init_ctrl(NvmeCtrl *n, PCIDevice *pci_dev)
id->cntlid = cpu_to_le16(n->cntlid);
- id->oaes = cpu_to_le32(NVME_OAES_NS_ATTR);
+ id->oaes = cpu_to_le32(NVME_OAES_NS_ATTR | NVME_OAES_ZDC);
id->ctratt |= cpu_to_le32(NVME_CTRATT_ELBAS);
id->rab = 6;
@@ -7659,6 +7715,10 @@ void nvme_attach_ns(NvmeCtrl *n, NvmeNamespace *ns)
n->dmrsl = MIN_NON_ZERO(n->dmrsl,
BDRV_REQUEST_MAX_BYTES / nvme_l2b(ns, 1));
+
+ if (ns->params.fto) {
+ nvme_zdc_watch(n, ns, nvme_zdc_aen);
+ }
}
static void nvme_realize(PCIDevice *pci_dev, Error **errp)
@@ -7721,6 +7781,7 @@ static void nvme_exit(PCIDevice *pci_dev)
for (i = 1; i <= NVME_MAX_NAMESPACES; i++) {
ns = nvme_ns(n, i);
if (ns) {
+ nvme_zdc_unwatch(n, ns);
ns->attached--;
}
}
diff --git a/hw/nvme/ns.c b/hw/nvme/ns.c
index 25cd490c99..5629b61302 100644
--- a/hw/nvme/ns.c
+++ b/hw/nvme/ns.c
@@ -241,6 +241,7 @@ static void nvme_ns_zoned_init_state(NvmeNamespace *ns)
QTAILQ_INIT(&ns->closed_zones);
QTAILQ_INIT(&ns->full_zones);
QTAILQ_INIT(&ns->zdc_list);
+ QTAILQ_INIT(&ns->zdc_watchers);
zone = ns->zone_array;
for (i = 0; i < ns->num_zones; i++, zone++) {
diff --git a/hw/nvme/nvme.h b/hw/nvme/nvme.h
index ae65226150..5499105e7b 100644
--- a/hw/nvme/nvme.h
+++ b/hw/nvme/nvme.h
@@ -91,6 +91,14 @@ static inline NvmeNamespace *nvme_subsys_ns(NvmeSubsystem *subsys,
#define NVME_NS(obj) \
OBJECT_CHECK(NvmeNamespace, (obj), TYPE_NVME_NS)
+typedef void (*NvmeNotifyFnc)(NvmeCtrl *n, NvmeNamespace *ns);
+
+typedef struct NvmeZdcNotify {
+ QTAILQ_ENTRY(NvmeZdcNotify) entry;
+ NvmeNotifyFnc notify;
+ NvmeCtrl *n;
+} NvmeZdcNotify;
+
typedef struct NvmeZdc {
QTAILQ_ENTRY(NvmeZdc) entry;
NvmeZone *zone;
@@ -179,6 +187,7 @@ typedef struct NvmeNamespace {
int64_t fto_ms;
QEMUTimer *active_timer;
+ QTAILQ_HEAD(, NvmeZdcNotify) zdc_watchers;
QTAILQ_HEAD(, NvmeZdc) zdc_list;
NvmeNamespaceParams params;
@@ -477,6 +486,8 @@ typedef struct NvmeCtrl {
uint64_t dbbuf_eis;
bool dbbuf_enabled;
+ bool zdc_event_queued;
+
struct {
MemoryRegion mem;
uint8_t *buf;
diff --git a/include/block/nvme.h b/include/block/nvme.h
index 9467d4b939..1662046c0d 100644
--- a/include/block/nvme.h
+++ b/include/block/nvme.h
@@ -830,6 +830,7 @@ enum NvmeAsyncEventRequest {
NVME_AER_INFO_SMART_TEMP_THRESH = 1,
NVME_AER_INFO_SMART_SPARE_THRESH = 2,
NVME_AER_INFO_NOTICE_NS_ATTR_CHANGED = 0,
+ NVME_AER_INFO_NOTICE_ZONE_DESC_CHANGED = 0xef,
};
typedef struct QEMU_PACKED NvmeAerResult {
@@ -1133,6 +1134,7 @@ typedef struct NvmeIdCtrlNvm {
enum NvmeIdCtrlOaes {
NVME_OAES_NS_ATTR = 1 << 8,
+ NVME_OAES_ZDC = 1 << 27,
};
enum NvmeIdCtrlCtratt {
--
2.27.0
© 2016 - 2026 Red Hat, Inc.