From 9ec42398568e344e7ba3c920b46f2a0cf30edca8 Mon Sep 17 00:00:00 2001 From: wnsah814 Date: Mon, 14 Sep 2026 18:04:25 +0900 Subject: [PATCH] femu/nand: let a read preempt an in-flight program or erase The media layer carries a program/erase suspend model: a read that finds its LUN mid-P/E starts after a suspend overhead instead of waiting the operation out, and the suspended operation resumes after the read. Nothing could use it. The two fields, policy.pe_suspend and timing.tsusp_ns, had no device property and no controller set them, and the branch sat only in the staged channel path, which is opt-in and off in the default configuration. Every read on a busy LUN queued behind whatever the LUN was doing. For the plain gates that is a full line of garbage collection. bbssd puts a line's copies and erases on all its LUNs at once, so one GC holds every LUN for the length of the burst (about 90 ms with a 64-LUN line at 40/200 us and 2 ms erase) and a host read arriving during it waits for the rest of the burst. Real controllers localize collection and suspend the array for host reads, and the model FEMU already had written down does exactly that. Apply the same arithmetic on the plain gates: on the lock-free LUN gate inside the CAS loop, and on the plane and lun+plane gates by pushing back each timeline that was busy at the read's arrival. Only reads preempt; programs and erases still queue. Expose the knobs as pe_suspend / tsusp_ns (bbssd) and zns_pe_suspend / zns_tsusp_ns (ZNS), named like the other timing knobs, both off by default, so existing configurations stay bit-identical. A negative ZNS overhead is refused at realize like the other ZNS timing values. In an F2FS multi-device YCSB-A run whose CNS share put the device into sustained line GC, the same run with reads preempting GC kept the GC copy volume (263-318 GB per run) and recovered throughput from -24% to -2% of the no-GC configuration, with the mean read latency back at its no-GC value. The unit test covers off, the LUN gate, the plane gate, that a program does not preempt, and the staged path. --- README.md | 4 ++ hw/femu/bbssd/ftl-geom.c | 2 + hw/femu/bbssd/ftl-media.c | 2 + hw/femu/bbssd/ftl.h | 2 + hw/femu/femu.c | 4 ++ hw/femu/nand/nand-media.c | 63 ++++++++++++++++-- hw/femu/nand/nand-media.h | 6 +- hw/femu/nvme.h | 5 ++ hw/femu/tests/test-nand-media | Bin 0 -> 22168 bytes hw/femu/tests/unit/test-nand-media.c | 93 +++++++++++++++++++++++++++ hw/femu/zns/zftl.c | 2 + hw/femu/zns/zns.c | 5 +- hw/femu/zns/zns.h | 3 + 13 files changed, 183 insertions(+), 8 deletions(-) create mode 100755 hw/femu/tests/test-nand-media diff --git a/README.md b/README.md index d3ea9c168e3..66d1454476e 100644 --- a/README.md +++ b/README.md @@ -356,6 +356,8 @@ op_pcent=0 # Over-provisioning withheld from the host, percent pls_per_lun=1 # Planes per LUN; above one, a line erases across planes nand_bad_blocks=0 # Blocks marked bad at init, reflected in available spare trim_lat_ns=0 # Latency charged per TRIM range +pe_suspend=0 # Reads preempt a program or erase in flight on their LUN +tsusp_ns=0 # (program/erase suspend); overhead each such read pays, ns # Wear, disturb and retention (optional; all default off) read_reclaim_limit=0 # Reads a block may take before its line is refreshed @@ -573,6 +575,8 @@ zns_blk_er_lat=0 zns_cmd_addr_lat=0 # Channel bus phases (ns): command/address cycle, zns_pg_xfer_lat=0 # page data transfer, status read. Any non-zero zns_status_lat=0 # value adds a shared per-channel bus to the model +zns_pe_suspend=0 # Reads preempt a program or erase in flight on their plane +zns_tsusp_ns=0 # (program/erase suspend); overhead each such read pays, ns zns_zrwa_size=0 # ZRWA window in LBAs (0 = ZRWA disabled) zns_zrwafg_size=0 # ZRWA flush granularity in LBAs zns_zrwa_num=0 # Zones that may hold a ZRWA at once diff --git a/hw/femu/bbssd/ftl-geom.c b/hw/femu/bbssd/ftl-geom.c index 18c06ff986b..38730b209f8 100644 --- a/hw/femu/bbssd/ftl-geom.c +++ b/hw/femu/bbssd/ftl-geom.c @@ -184,6 +184,8 @@ void ssd_init_params(struct ssdparams *spp, FemuCtrl *n) spp->tplrbsy = n->bb_params.tplrbsy; spp->tplebsy = n->bb_params.tplebsy; spp->trcbsy = n->bb_params.trcbsy; + spp->pe_suspend = n->bb_params.pe_suspend; + spp->tsusp_ns = n->bb_params.tsusp_ns; spp->trim_lat_ns = n->bb_params.trim_lat_ns; /* DRAM write buffer */ diff --git a/hw/femu/bbssd/ftl-media.c b/hw/femu/bbssd/ftl-media.c index f173f5b11c4..ea34638352e 100644 --- a/hw/femu/bbssd/ftl-media.c +++ b/hw/femu/bbssd/ftl-media.c @@ -102,6 +102,8 @@ void bb_nand_media_init(struct ssd *ssd) cfg.timing.tplrbsy_ns = spp->tplrbsy; cfg.timing.tplebsy_ns = spp->tplebsy; cfg.timing.trcbsy_ns = spp->trcbsy; + cfg.policy.pe_suspend = (spp->pe_suspend != 0); + cfg.timing.tsusp_ns = spp->tsusp_ns; cfg.timing.ecc_step_ns = spp->ecc_step_ns; cfg.timing.ecc_pe_per_tier = FEMU_ECC_PE_PER_TIER; cfg.timing.ecc_max_tiers = FEMU_ECC_MAX_TIERS; diff --git a/hw/femu/bbssd/ftl.h b/hw/femu/bbssd/ftl.h index 094873368e0..6edded852ca 100644 --- a/hw/femu/bbssd/ftl.h +++ b/hw/femu/bbssd/ftl.h @@ -152,6 +152,8 @@ struct ssdparams { int tplebsy; /* multi-plane erase inter-plane busy (ns); 0 = off */ int trcbsy; /* cache read busy (next-page array overlap), ns; 0 = off */ int trim_lat_ns; /* modeled cost per processed DSM/TRIM range, ns; 0 = off */ + int pe_suspend; /* reads preempt an in-flight program/erase on the LUN; 0 = off */ + int tsusp_ns; /* suspend overhead per preempting read (ns) */ double gc_thres_pcent; int gc_thres_lines; diff --git a/hw/femu/femu.c b/hw/femu/femu.c index edf4ca2666b..f089ad36835 100644 --- a/hw/femu/femu.c +++ b/hw/femu/femu.c @@ -1905,6 +1905,8 @@ static const Property femu_props[] = { DEFINE_PROP_INT64("zns_cmd_addr_lat", FemuCtrl, zns_params.zns_cmd_addr_lat, 0), DEFINE_PROP_INT64("zns_pg_xfer_lat", FemuCtrl, zns_params.zns_pg_xfer_lat, 0), DEFINE_PROP_INT64("zns_status_lat", FemuCtrl, zns_params.zns_status_lat, 0), + DEFINE_PROP_INT32("zns_pe_suspend", FemuCtrl, zns_params.zns_pe_suspend, 0), + DEFINE_PROP_INT64("zns_tsusp_ns", FemuCtrl, zns_params.zns_tsusp_ns, 0), DEFINE_PROP_UINT32("zns_max_active", FemuCtrl, zns_params.zns_max_active, 0), DEFINE_PROP_UINT32("zns_max_open", FemuCtrl, zns_params.zns_max_open, 0), DEFINE_PROP_UINT32("zns_zd_ext_size", FemuCtrl, zns_params.zns_zd_ext_size, 0), @@ -1956,6 +1958,8 @@ static const Property femu_props[] = { DEFINE_PROP_INT32("tplebsy", FemuCtrl, bb_params.tplebsy, 0), DEFINE_PROP_INT32("trcbsy", FemuCtrl, bb_params.trcbsy, 0), DEFINE_PROP_INT32("trim_lat_ns", FemuCtrl, bb_params.trim_lat_ns, 0), + DEFINE_PROP_INT32("pe_suspend", FemuCtrl, bb_params.pe_suspend, 0), + DEFINE_PROP_INT32("tsusp_ns", FemuCtrl, bb_params.tsusp_ns, 0), DEFINE_PROP_UINT32("nand_bad_blocks", FemuCtrl, nand_bad_blocks, 0), DEFINE_PROP_UINT32("op_pcent", FemuCtrl, op_pcent, 0), DEFINE_PROP_BOOL("debug_ftl", FemuCtrl, debug_ftl, false), diff --git a/hw/femu/nand/nand-media.c b/hw/femu/nand/nand-media.c index 790cc8625d4..9d774d8a908 100644 --- a/hw/femu/nand/nand-media.c +++ b/hw/femu/nand/nand-media.c @@ -211,6 +211,30 @@ static uint64_t array_gate_start(NandMedia *m, const NandLoc *loc, uint64_t t) return s; } +/* + * A read that preempted an in-flight P/E: push back every gated timeline that + * was busy at t by the read's occupancy, so the suspended operation finishes + * that much later. Timelines that were already idle at t are left alone. + */ +static void array_suspend_extend(NandMedia *m, const NandLoc *loc, uint64_t t, + uint64_t shift) +{ + if (m->cfg.policy.array_gate == NAND_GATE_LUN_ONLY || + m->cfg.policy.array_gate == NAND_GATE_LUN_AND_PLANE) { + uint64_t *lun = m->cfg.timeline->lun_avail(m->cfg.timeline_opaque, loc); + if (*lun > t) { + *lun += shift; + } + } + if (m->cfg.policy.array_gate == NAND_GATE_PLANE_ONLY || + m->cfg.policy.array_gate == NAND_GATE_LUN_AND_PLANE) { + uint64_t *pl = m->cfg.timeline->plane_avail(m->cfg.timeline_opaque, loc); + if (*pl > t) { + *pl += shift; + } + } +} + static void array_commit(NandMedia *m, const NandLoc *loc, uint64_t done) { if (m->cfg.policy.array_gate == NAND_GATE_LUN_ONLY || @@ -251,16 +275,33 @@ NandOpCompletion nand_media_op(NandMedia *m, const NandLoc *loc, if (m->cfg.policy.array_gate == NAND_GATE_LUN_ONLY) { uint64_t *lun = m->cfg.timeline->lun_avail(m->cfg.timeline_opaque, loc); uint64_t old = __atomic_load_n(lun, __ATOMIC_RELAXED); + bool susp = m->cfg.policy.pe_suspend && op == NAND_MEDIA_READ; for (;;) { - uint64_t s = (t > old) ? t : old; - done = s + alat; + uint64_t s, avail; + if (susp && t < old) { + /* + * Program/erase suspend on the plain LUN gate: the read + * starts after the suspend overhead instead of waiting out + * the busy LUN, and whatever the LUN was doing resumes + * after the read, so its completion slides by the read's + * array time plus the overhead. Same arithmetic as the + * staged path below. + */ + s = t + m->cfg.timing.tsusp_ns; + done = s + alat; + avail = old + alat + m->cfg.timing.tsusp_ns; + } else { + s = (t > old) ? t : old; + done = s + alat; + avail = done; + } /* * x86-64: inlines to `lock cmpxchg` (no libatomic call). On * success `old` is unchanged and we stop; on failure the * builtin writes the observed value back into `old` and we * retry with it (recomputing done from what we actually saw). */ - if (__atomic_compare_exchange_n(lun, &old, done, false, + if (__atomic_compare_exchange_n(lun, &old, avail, false, __ATOMIC_ACQ_REL, __ATOMIC_RELAXED)) { break; @@ -275,8 +316,20 @@ NandOpCompletion nand_media_op(NandMedia *m, const NandLoc *loc, } { uint64_t s = array_gate_start(m, loc, t); - done = s + alat; - array_commit(m, loc, done); + if (m->cfg.policy.pe_suspend && op == NAND_MEDIA_READ && s > t) { + /* + * Suspend on the plane / lun+plane gate (ZNS, OCSSD): the + * read goes first, and every gated timeline that was busy + * resumes its work after the read. + */ + uint64_t shift = alat + m->cfg.timing.tsusp_ns; + s = t + m->cfg.timing.tsusp_ns; + done = s + alat; + array_suspend_extend(m, loc, t, shift); + } else { + done = s + alat; + array_commit(m, loc, done); + } } if (m->cfg.timeline->unlock_lun) { m->cfg.timeline->unlock_lun(m->cfg.timeline_opaque, loc); diff --git a/hw/femu/nand/nand-media.h b/hw/femu/nand/nand-media.h index f878f790347..994cf5b1ccd 100644 --- a/hw/femu/nand/nand-media.h +++ b/hw/femu/nand/nand-media.h @@ -88,7 +88,8 @@ typedef struct NandMediaTiming { int32_t ecc_pe_per_tier; int32_t ecc_max_tiers; int32_t ecc_retention_per_tier_sec; - /* program/erase suspend overhead (ns) for an urgent read to preempt an in-flight P/E */ + /* program/erase suspend overhead (ns) paid by a read that preempts an + * in-flight P/E on its LUN/plane (policy.pe_suspend); 0 = free suspend */ int64_t tsusp_ns; } NandMediaTiming; @@ -96,7 +97,8 @@ typedef struct NandMediaPolicy { NandArrayGate array_gate; NandChannelMode channel_mode; bool cache_read; - bool pe_suspend; /* reads preempt an in-flight program/erase on the LUN */ + bool pe_suspend; /* reads preempt an in-flight program/erase on the + * LUN/plane (all gates, staged or plain channel) */ bool ecc_on_read; bool use_flat_timing; /* true: scalar fields; false: table */ } NandMediaPolicy; diff --git a/hw/femu/nvme.h b/hw/femu/nvme.h index 021efe77d23..50af0837c8b 100644 --- a/hw/femu/nvme.h +++ b/hw/femu/nvme.h @@ -1738,6 +1738,8 @@ typedef struct BbCtrlParams { int tplebsy; /* multi-plane erase inter-plane busy (ns); 0 = off */ int trcbsy; /* cache read busy (next-page array overlap), ns; 0 = off */ int trim_lat_ns; /* modeled cost per processed DSM/TRIM range, ns; 0 = off */ + int pe_suspend; /* reads preempt an in-flight program/erase on the LUN; 0 = off */ + int tsusp_ns; /* suspend overhead per preempting read (ns) */ } BbCtrlParams; typedef struct ZNSCtrlParams { @@ -1759,6 +1761,9 @@ typedef struct ZNSCtrlParams { int64_t zns_cmd_addr_lat; int64_t zns_pg_xfer_lat; int64_t zns_status_lat; + /* program/erase suspend: reads preempt a busy plane (0 = off), overhead ns */ + int32_t zns_pe_suspend; + int64_t zns_tsusp_ns; uint32_t zns_max_active; /* max active zones (0 = unlimited) */ uint32_t zns_max_open; /* max open zones (0 = unlimited) */ uint32_t zns_zd_ext_size; /* per-zone descriptor extension bytes (0 = none) */ diff --git a/hw/femu/tests/test-nand-media b/hw/femu/tests/test-nand-media new file mode 100755 index 0000000000000000000000000000000000000000..2e93ac44e07e3ff92cb5d082a3868c6c5f53a85b GIT binary patch literal 22168 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 b-|{-8T;5B2x5D({I*vgtiming.cmd_addr_lat; cfg.timing.page_xfer_ns = zns->timing.pg_xfer_lat; cfg.timing.status_ns = zns->timing.status_lat; + cfg.policy.pe_suspend = zns->timing.pe_suspend; + cfg.timing.tsusp_ns = zns->timing.tsusp_ns; cfg.policy.channel_mode = (cfg.timing.cmd_addr_ns || cfg.timing.page_xfer_ns || cfg.timing.status_ns) ? NAND_CH_STAGED : NAND_CH_OFF; cfg.timeline = &zns_timeline_ops; diff --git a/hw/femu/zns/zns.c b/hw/femu/zns/zns.c index b4d0c1644ae..a61096393b9 100644 --- a/hw/femu/zns/zns.c +++ b/hw/femu/zns/zns.c @@ -1857,6 +1857,9 @@ static void zns_init_params(FemuCtrl *n, NvmeNamespace *ns) id_zns->timing.cmd_addr_lat = n->zns_params.zns_cmd_addr_lat; id_zns->timing.pg_xfer_lat = n->zns_params.zns_pg_xfer_lat; id_zns->timing.status_lat = n->zns_params.zns_status_lat; + /* P/E suspend: default off, so the plane gate is bit-identical to before */ + id_zns->timing.pe_suspend = (n->zns_params.zns_pe_suspend != 0); + id_zns->timing.tsusp_ns = n->zns_params.zns_tsusp_ns; /* * Optional write-fault injection. One write in N fails and takes its zone @@ -1976,7 +1979,7 @@ static bool zns_check_params(FemuCtrl *n, NvmeNamespace *ns, Error **errp) } if (p->zns_pg_rd_lat < 0 || p->zns_pg_wr_lat < 0 || p->zns_blk_er_lat < 0 || p->zns_cmd_addr_lat < 0 || p->zns_pg_xfer_lat < 0 || - p->zns_status_lat < 0) { + p->zns_status_lat < 0 || p->zns_tsusp_ns < 0) { error_setg(errp, "zns NAND timing knobs must not be negative"); return false; } diff --git a/hw/femu/zns/zns.h b/hw/femu/zns/zns.h index 4987a5cfd33..cebc3e1d13d 100644 --- a/hw/femu/zns/zns.h +++ b/hw/femu/zns/zns.h @@ -124,6 +124,9 @@ typedef struct SSDNandFlashTiming { uint64_t cmd_addr_lat; /* command and address cycles */ uint64_t pg_xfer_lat; /* page data-in (program) / data-out (read) */ uint64_t status_lat; /* status read after a read or erase */ + /* program/erase suspend: a read preempts a busy plane (0 = off) */ + bool pe_suspend; + uint64_t tsusp_ns; } SSDNandFlashTiming; struct zns_write_cache{