From: Clay Mayers <clay.may...@kioxia.com> Adds ns.param.zoned.finish_time, which sets the number of seconds a zone can remain active before the zone attribute ZONE_FINISH_RECOMMENDED is set.
This requires scanning the exp open, imp open and closed lists of zones whenever a zone is marked as requiring finishing. The expectation is these lists will be short (10s of items) allowing a simpler implementation than keeping the lists sorted. It also keeps the overhead during the exception of a timeout instead of when zones change state between open and closed. For use cases where this isn't true, finish_time should be 0 to disable this feature (the default). Signed-off-by: Clay Mayers <clay.may...@kioxia.com> --- docs/system/devices/nvme.rst | 5 +++++ hw/nvme/ctrl.c | 35 +++++++++++++++++++++++++++++++++++ hw/nvme/ns.c | 8 ++++++++ hw/nvme/nvme.h | 18 ++++++++++++++---- 4 files changed, 62 insertions(+), 4 deletions(-) diff --git a/docs/system/devices/nvme.rst b/docs/system/devices/nvme.rst index 30f841ef62..1cb0ef844c 100644 --- a/docs/system/devices/nvme.rst +++ b/docs/system/devices/nvme.rst @@ -212,6 +212,11 @@ The namespace may be configured with additional parameters the minimum memory page size (CAP.MPSMIN). The default value (``0``) has this property inherit the ``mdts`` value. +``zoned.finish_time=UINT32`` (default: ``0``) + Set the time in seconds for how long a zone can be active before setting the + zone attribute ``Zone Finish Recommended``. The default value (``0``) + disables this feature. + Metadata -------- diff --git a/hw/nvme/ctrl.c b/hw/nvme/ctrl.c index 87aeba0564..d7e9fae0b0 100644 --- a/hw/nvme/ctrl.c +++ b/hw/nvme/ctrl.c @@ -1516,6 +1516,38 @@ static void nvme_clear_events(NvmeCtrl *n, uint8_t event_type) } } +static void nvme_check_finish(NvmeNamespace *ns, NvmeZoneListHead *list) +{ + int64_t now = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL); + NvmeZone *zone; + + QTAILQ_FOREACH(zone, list, entry) { + if (zone->finish_ms <= now) { + zone->finish_ms = INT64_MAX; + zone->d.za |= NVME_ZA_FINISH_RECOMMENDED; + } else if (zone->finish_ms != INT64_MAX) { + timer_mod_anticipate(ns->active_timer, zone->finish_ms); + } + } +} + +void nvme_finish_needed(void *opaque) +{ + NvmeNamespace *ns = opaque; + + nvme_check_finish(ns, &ns->exp_open_zones); + nvme_check_finish(ns, &ns->imp_open_zones); + nvme_check_finish(ns, &ns->closed_zones); +} + +void nvme_set_active_timeout(NvmeNamespace *ns, NvmeZone *zone) +{ + if (ns->fto_ms) { + zone->finish_ms = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + ns->fto_ms; + timer_mod_anticipate(ns->active_timer, zone->finish_ms); + } +} + static inline uint16_t nvme_check_mdts(NvmeCtrl *n, size_t len) { uint8_t mdts = n->params.mdts; @@ -1791,6 +1823,7 @@ static uint16_t nvme_zrm_finish(NvmeNamespace *ns, NvmeZone *zone) /* fallthrough */ case NVME_ZONE_STATE_EMPTY: + zone->d.za &= ~NVME_ZA_FINISH_RECOMMENDED; nvme_assign_zone_state(ns, zone, NVME_ZONE_STATE_FULL); return NVME_SUCCESS; @@ -1891,6 +1924,7 @@ static uint16_t nvme_zrm_open_flags(NvmeCtrl *n, NvmeNamespace *ns, if (act) { nvme_aor_inc_active(ns); + nvme_set_active_timeout(ns, zone); } nvme_aor_inc_open(ns); @@ -3619,6 +3653,7 @@ static uint16_t nvme_set_zd_ext(NvmeNamespace *ns, NvmeZone *zone) return status; } nvme_aor_inc_active(ns); + nvme_set_active_timeout(ns, zone); zone->d.za |= NVME_ZA_ZD_EXT_VALID; nvme_assign_zone_state(ns, zone, NVME_ZONE_STATE_CLOSED); return NVME_SUCCESS; diff --git a/hw/nvme/ns.c b/hw/nvme/ns.c index 62a1f97be0..b577f2d8e0 100644 --- a/hw/nvme/ns.c +++ b/hw/nvme/ns.c @@ -322,6 +322,11 @@ static void nvme_ns_init_zoned(NvmeNamespace *ns) ns->id_ns.nsfeat &= ~0x4; } + ns->fto_ms = ns->params.fto * INT64_C(1000); + if (ns->fto_ms) { + ns->active_timer = timer_new_ms(QEMU_CLOCK_VIRTUAL, nvme_finish_needed, + ns); + } ns->id_ns_zoned = id_ns_z; } @@ -338,6 +343,7 @@ static void nvme_clear_zone(NvmeNamespace *ns, NvmeZone *zone) nvme_set_zone_state(zone, NVME_ZONE_STATE_CLOSED); } nvme_aor_inc_active(ns); + nvme_set_active_timeout(ns, zone); QTAILQ_INSERT_HEAD(&ns->closed_zones, zone, entry); } else { trace_pci_nvme_clear_ns_reset(state, zone->d.zslba); @@ -521,6 +527,7 @@ void nvme_ns_shutdown(NvmeNamespace *ns) void nvme_ns_cleanup(NvmeNamespace *ns) { if (ns->params.zoned) { + timer_free(ns->active_timer); g_free(ns->id_ns_zoned); g_free(ns->zone_array); g_free(ns->zd_extensions); @@ -644,6 +651,7 @@ static Property nvme_ns_props[] = { DEFINE_PROP_SIZE("zoned.zrwafg", NvmeNamespace, params.zrwafg, -1), DEFINE_PROP_BOOL("eui64-default", NvmeNamespace, params.eui64_default, false), + DEFINE_PROP_UINT32("zoned.finish_time", NvmeNamespace, params.fto, 0), DEFINE_PROP_END_OF_LIST(), }; diff --git a/hw/nvme/nvme.h b/hw/nvme/nvme.h index 79f5c281c2..9a54dcdb32 100644 --- a/hw/nvme/nvme.h +++ b/hw/nvme/nvme.h @@ -93,6 +93,7 @@ static inline NvmeNamespace *nvme_subsys_ns(NvmeSubsystem *subsys, typedef struct NvmeZone { NvmeZoneDescr d; uint64_t w_ptr; + int64_t finish_ms; QTAILQ_ENTRY(NvmeZone) entry; } NvmeZone; @@ -121,12 +122,15 @@ typedef struct NvmeNamespaceParams { uint32_t max_active_zones; uint32_t max_open_zones; uint32_t zd_extension_size; + uint32_t fto; uint32_t numzrwa; uint64_t zrwas; uint64_t zrwafg; } NvmeNamespaceParams; +typedef QTAILQ_HEAD(, NvmeZone) NvmeZoneListHead; + typedef struct NvmeNamespace { DeviceState parent_obj; BlockConf blkconf; @@ -154,10 +158,10 @@ typedef struct NvmeNamespace { NvmeIdNsZoned *id_ns_zoned; NvmeZone *zone_array; - QTAILQ_HEAD(, NvmeZone) exp_open_zones; - QTAILQ_HEAD(, NvmeZone) imp_open_zones; - QTAILQ_HEAD(, NvmeZone) closed_zones; - QTAILQ_HEAD(, NvmeZone) full_zones; + NvmeZoneListHead exp_open_zones; + NvmeZoneListHead imp_open_zones; + NvmeZoneListHead closed_zones; + NvmeZoneListHead full_zones; uint32_t num_zones; uint64_t zone_size; uint64_t zone_capacity; @@ -166,6 +170,9 @@ typedef struct NvmeNamespace { int32_t nr_open_zones; int32_t nr_active_zones; + int64_t fto_ms; + QEMUTimer *active_timer; + NvmeNamespaceParams params; struct { @@ -274,6 +281,9 @@ static inline void nvme_aor_dec_active(NvmeNamespace *ns) assert(ns->nr_active_zones >= 0); } +void nvme_set_active_timeout(NvmeNamespace *ns, NvmeZone *zone); +void nvme_finish_needed(void *opaque); + void nvme_ns_init_format(NvmeNamespace *ns); int nvme_ns_setup(NvmeNamespace *ns, Error **errp); void nvme_ns_drain(NvmeNamespace *ns); -- 2.27.0