@@ -18,9 +18,16 @@ void nand_media_init(NandMedia *m, const NandMediaConfig *cfg)
1818{
1919 m -> cfg = * cfg ;
2020 m -> bus_res = NULL ;
21+ m -> susp = NULL ;
2122 if (cfg -> policy .channel_mode == NAND_CH_STAGED && cfg -> nchs ) {
2223 m -> bus_res = calloc (cfg -> nchs , sizeof (* m -> bus_res ));
2324 }
25+ /* suspend needs the geometry to keep its per-position state */
26+ if (cfg -> policy .pe_suspend &&
27+ cfg -> nchs && cfg -> luns_per_ch && cfg -> planes_per_lun ) {
28+ m -> susp = calloc ((size_t )cfg -> nchs * cfg -> luns_per_ch *
29+ cfg -> planes_per_lun , sizeof (* m -> susp ));
30+ }
2431}
2532
2633/*
@@ -37,6 +44,8 @@ void nand_media_destroy(NandMedia *m)
3744 }
3845 free (m -> bus_res );
3946 m -> bus_res = NULL ;
47+ free (m -> susp );
48+ m -> susp = NULL ;
4049}
4150
4251static inline uint64_t mx (uint64_t a , uint64_t b ) { return a > b ? a : b ; }
@@ -247,6 +256,76 @@ static void array_commit(NandMedia *m, const NandLoc *loc, uint64_t done)
247256 }
248257}
249258
259+ /*
260+ * The suspend state for an array position: the plane under a plane-only gate,
261+ * otherwise the LUN, since a LUN gate makes the whole LUN one position.
262+ */
263+ static NandSuspendState * suspend_state (NandMedia * m , const NandLoc * loc )
264+ {
265+ const NandMediaConfig * c = & m -> cfg ;
266+ uint64_t idx ;
267+
268+ if (!m -> susp || loc -> ch >= c -> nchs || loc -> lun >= c -> luns_per_ch ||
269+ loc -> pl >= c -> planes_per_lun ) {
270+ return NULL ;
271+ }
272+ idx = (uint64_t )loc -> ch * c -> luns_per_ch + loc -> lun ;
273+ if (c -> policy .array_gate == NAND_GATE_PLANE_ONLY ) {
274+ idx = idx * c -> planes_per_lun + loc -> pl ;
275+ }
276+
277+ return & m -> susp [idx ];
278+ }
279+
280+ /* record what an operation that went through the ordinary gate occupies */
281+ static void suspend_note (NandMedia * m , const NandLoc * loc , NandMediaOp op ,
282+ uint64_t done )
283+ {
284+ NandSuspendState * st = suspend_state (m , loc );
285+
286+ if (!st ) {
287+ return ;
288+ }
289+ if (op == NAND_MEDIA_READ ) {
290+ st -> rd_end = mx (st -> rd_end , done );
291+ } else {
292+ st -> pe_end = mx (st -> pe_end , done );
293+ }
294+ }
295+
296+ /*
297+ * Where a read starts under program/erase suspend, given the time the array is
298+ * busy until. Only a program or erase is suspended: a read that finds the array
299+ * busy with other reads queues behind them, and one that finds a suspension
300+ * already open joins it behind the reads in it without paying the overhead a
301+ * second time. Returns false to leave the read to the ordinary gate; otherwise
302+ * sets *start, and *shift, how far the suspended work slides.
303+ *
304+ * The state is read and written without a lock, so the caller must serialize
305+ * the operations on a position -- bbssd and ZNS run them on one FTL thread.
306+ */
307+ static bool suspend_read_start (NandMedia * m , const NandLoc * loc , uint64_t t ,
308+ uint64_t busy_until , uint64_t alat ,
309+ uint64_t * start , uint64_t * shift )
310+ {
311+ NandSuspendState * st = suspend_state (m , loc );
312+
313+ if (!st || t >= busy_until || t >= st -> pe_end ) {
314+ return false;
315+ }
316+ if (st -> rd_end > t ) {
317+ * start = st -> rd_end ;
318+ * shift = alat ;
319+ } else {
320+ * start = t + m -> cfg .timing .tsusp_ns ;
321+ * shift = alat + m -> cfg .timing .tsusp_ns ;
322+ }
323+ st -> pe_end += * shift ;
324+ st -> rd_end = * start + alat ;
325+
326+ return true;
327+ }
328+
250329NandOpCompletion nand_media_op (NandMedia * m , const NandLoc * loc ,
251330 NandMediaOp op , uint64_t stime )
252331{
@@ -272,36 +351,23 @@ NandOpCompletion nand_media_op(NandMedia *m, const NandLoc *loc,
272351 * mutex. Falls through to the locked path for LUN_AND_PLANE, which is
273352 * two words and not atomically CAS-able.
274353 */
275- if (m -> cfg .policy .array_gate == NAND_GATE_LUN_ONLY ) {
354+ /*
355+ * Suspend keeps state beside the timeline that one CAS cannot update
356+ * with it, so a device with suspend on takes the general path below.
357+ */
358+ if (m -> cfg .policy .array_gate == NAND_GATE_LUN_ONLY && !m -> susp ) {
276359 uint64_t * lun = m -> cfg .timeline -> lun_avail (m -> cfg .timeline_opaque , loc );
277360 uint64_t old = __atomic_load_n (lun , __ATOMIC_RELAXED );
278- bool susp = m -> cfg .policy .pe_suspend && op == NAND_MEDIA_READ ;
279361 for (;;) {
280- uint64_t s , avail ;
281- if (susp && t < old ) {
282- /*
283- * Program/erase suspend on the plain LUN gate: the read
284- * starts after the suspend overhead instead of waiting out
285- * the busy LUN, and whatever the LUN was doing resumes
286- * after the read, so its completion slides by the read's
287- * array time plus the overhead. Same arithmetic as the
288- * staged path below.
289- */
290- s = t + m -> cfg .timing .tsusp_ns ;
291- done = s + alat ;
292- avail = old + alat + m -> cfg .timing .tsusp_ns ;
293- } else {
294- s = (t > old ) ? t : old ;
295- done = s + alat ;
296- avail = done ;
297- }
362+ uint64_t s = (t > old ) ? t : old ;
363+ done = s + alat ;
298364 /*
299365 * x86-64: inlines to `lock cmpxchg` (no libatomic call). On
300366 * success `old` is unchanged and we stop; on failure the
301367 * builtin writes the observed value back into `old` and we
302368 * retry with it (recomputing done from what we actually saw).
303369 */
304- if (__atomic_compare_exchange_n (lun , & old , avail , false,
370+ if (__atomic_compare_exchange_n (lun , & old , done , false,
305371 __ATOMIC_ACQ_REL ,
306372 __ATOMIC_RELAXED )) {
307373 break ;
@@ -316,19 +382,20 @@ NandOpCompletion nand_media_op(NandMedia *m, const NandLoc *loc,
316382 }
317383 {
318384 uint64_t s = array_gate_start (m , loc , t );
319- if (m -> cfg .policy .pe_suspend && op == NAND_MEDIA_READ && s > t ) {
385+ uint64_t rs , shift ;
386+
387+ if (op == NAND_MEDIA_READ &&
388+ suspend_read_start (m , loc , t , s , alat , & rs , & shift )) {
320389 /*
321- * Suspend on the plane / lun+plane gate (ZNS, OCSSD): the
322- * read goes first, and every gated timeline that was busy
323- * resumes its work after the read.
390+ * The read goes ahead of the program or erase, and every gated
391+ * timeline that was busy resumes its work after it.
324392 */
325- uint64_t shift = alat + m -> cfg .timing .tsusp_ns ;
326- s = t + m -> cfg .timing .tsusp_ns ;
327- done = s + alat ;
393+ done = rs + alat ;
328394 array_suspend_extend (m , loc , t , shift );
329395 } else {
330396 done = s + alat ;
331397 array_commit (m , loc , done );
398+ suspend_note (m , loc , op , done );
332399 }
333400 }
334401 if (m -> cfg .timeline -> unlock_lun ) {
@@ -351,19 +418,19 @@ NandOpCompletion nand_media_op(NandMedia *m, const NandLoc *loc,
351418 if (op == NAND_MEDIA_READ ) {
352419 t = bus_now (m , loc -> ch , stime , t , m -> cfg .timing .cmd_addr_ns );
353420 /*
354- * Program/erase suspend: if the LUN is mid-P/E when this read arrives, real NAND
355- * lets the read preempt the slow operation. Model it by starting the read after a
356- * small suspend overhead (tsusp) instead of waiting out the whole P/E, then pushing
357- * the suspended op's completion back by the read's array occupancy (it resumes after
358- * the read). Default off (pe_suspend=false) => identical to the plain gate.
421+ * Program/erase suspend: a read that finds its array mid-program or
422+ * mid-erase starts after a small suspend overhead instead of waiting
423+ * the operation out, and the suspended operation resumes after it.
424+ * Default off (pe_suspend=false) => identical to the plain gate.
359425 */
360- if (m -> cfg .policy .pe_suspend ) {
361- uint64_t * lun = m -> cfg .timeline -> lun_avail (m -> cfg .timeline_opaque , loc );
362- if (t < * lun ) {
363- uint64_t s = t + m -> cfg .timing .tsusp_ns ;
364- uint64_t done = s + alat ;
365- /* suspended P/E resumes after the read: its remaining work shifts later */
366- * lun += (alat + m -> cfg .timing .tsusp_ns );
426+ {
427+ uint64_t busy = array_gate_start (m , loc , t );
428+ uint64_t rs , shift ;
429+
430+ if (suspend_read_start (m , loc , t , busy , alat , & rs , & shift )) {
431+ uint64_t done = rs + alat ;
432+
433+ array_suspend_extend (m , loc , t , shift );
367434 if (m -> cfg .policy .cache_read && m -> cfg .timeline -> page_reg_ready ) {
368435 uint64_t * prr =
369436 m -> cfg .timeline -> page_reg_ready (m -> cfg .timeline_opaque , loc );
@@ -385,6 +452,7 @@ NandOpCompletion nand_media_op(NandMedia *m, const NandLoc *loc,
385452 uint64_t s = array_gate_start (m , loc , t );
386453 uint64_t done = s + alat ;
387454 array_commit (m , loc , done );
455+ suspend_note (m , loc , op , done );
388456 if (m -> cfg .policy .cache_read && m -> cfg .timeline -> page_reg_ready ) {
389457 uint64_t rcbsy = m -> cfg .timing .trcbsy_ns ? m -> cfg .timing .trcbsy_ns : alat ;
390458 * m -> cfg .timeline -> lun_avail (m -> cfg .timeline_opaque , loc ) = s + rcbsy ;
@@ -403,12 +471,14 @@ NandOpCompletion nand_media_op(NandMedia *m, const NandLoc *loc,
403471 uint64_t s = array_gate_start (m , loc , t );
404472 uint64_t done = s + alat ;
405473 array_commit (m , loc , done );
474+ suspend_note (m , loc , op , done );
406475 c .done_ns = done ;
407476 } else { /* erase */
408477 t = bus_now (m , loc -> ch , stime , t , m -> cfg .timing .cmd_addr_ns );
409478 uint64_t s = array_gate_start (m , loc , t );
410479 uint64_t done = s + alat ;
411480 array_commit (m , loc , done );
481+ suspend_note (m , loc , op , done );
412482 t = bus_later (m , loc -> ch , stime , done , m -> cfg .timing .status_ns );
413483 c .done_ns = t ;
414484 }
@@ -469,6 +539,7 @@ NandOpCompletion nand_media_multiplane(NandMedia *m, const NandLoc *locs, int nl
469539 uint64_t done = start + alat ;
470540 for (i = 0 ; i < nlocs ; i ++ ) {
471541 array_commit (m , & locs [i ], done );
542+ suspend_note (m , & locs [i ], op , done );
472543 }
473544
474545 t = done ;
@@ -502,8 +573,11 @@ NandOpCompletion nand_media_copyback(NandMedia *m, const NandLoc *src,
502573 */
503574 s = array_gate_start (m , src , stime ) + array_lat (m , src , NAND_MEDIA_READ );
504575 array_commit (m , src , s );
576+ /* the on-chip read is part of the program, so it is suspended with it */
577+ suspend_note (m , src , NAND_MEDIA_PROGRAM , s );
505578 s = array_gate_start (m , dst , s ) + array_lat (m , dst , NAND_MEDIA_PROGRAM );
506579 array_commit (m , dst , s );
580+ suspend_note (m , dst , NAND_MEDIA_PROGRAM , s );
507581
508582 c .done_ns = s ;
509583 c .latency_ns = 0 ; /* GC-internal; host effect is via freed channel + LUN busy */
0 commit comments