diff --git a/README.md b/README.md index d3ea9c168e..27e1df8c4d 100644 --- a/README.md +++ b/README.md @@ -563,6 +563,8 @@ Emulates NVMe ZNS SSDs with zone-based interface. ```bash zns_max_active=0 # Max active zones (0 = unlimited) zns_max_open=0 # Max open zones (0 = unlimited) +zns_num_wc=0 # Write caches, one per zone being written + # (0 = zns_max_open, or 3 when that is unlimited) zns_zd_ext_size=0 # Zone-descriptor extension bytes (0 = none) zns_num_conv_zones=0 # Leading conventional zones (0 = all sequential) zns_zone_cap=0 # Usable bytes per zone (0 = the whole zone) diff --git a/hw/femu/femu.c b/hw/femu/femu.c index edf4ca2666..b5b5a39dc3 100644 --- a/hw/femu/femu.c +++ b/hw/femu/femu.c @@ -1907,6 +1907,7 @@ static const Property femu_props[] = { DEFINE_PROP_INT64("zns_status_lat", FemuCtrl, zns_params.zns_status_lat, 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_num_wc", FemuCtrl, zns_params.zns_num_wc, 0), DEFINE_PROP_UINT32("zns_zd_ext_size", FemuCtrl, zns_params.zns_zd_ext_size, 0), DEFINE_PROP_UINT32("zns_num_conv_zones", FemuCtrl, zns_params.zns_num_conv_zones, 0), diff --git a/hw/femu/nvme.h b/hw/femu/nvme.h index 021efe77d2..079e40afea 100644 --- a/hw/femu/nvme.h +++ b/hw/femu/nvme.h @@ -1761,6 +1761,7 @@ typedef struct ZNSCtrlParams { int64_t zns_status_lat; uint32_t zns_max_active; /* max active zones (0 = unlimited) */ uint32_t zns_max_open; /* max open zones (0 = unlimited) */ + uint32_t zns_num_wc; /* write caches (0 = zns_max_open, or 3 when unlimited) */ uint32_t zns_zd_ext_size; /* per-zone descriptor extension bytes (0 = none) */ uint32_t zns_num_conv_zones; /* leading conventional zones (0 = all sequential) */ uint64_t zns_zone_cap; /* usable bytes per zone; 0 = the whole zone */ diff --git a/hw/femu/zns/zftl.c b/hw/femu/zns/zftl.c index 985bc791c4..1aaafa4b00 100644 --- a/hw/femu/zns/zftl.c +++ b/hw/femu/zns/zftl.c @@ -441,11 +441,22 @@ static uint64_t zns_wc_flush(struct zns_ssd* zns, int wcidx, int type,uint64_t s while(i < zns->cache.write_cache[wcidx].used) { for(p = 0;pnum_plane;p++){ + /* + * A partial cache (evicted before its stripe filled) programs + * only the pages that hold data; the untouched planes and + * pages of the stripe stay free for the zone's later writes. + */ + if (i >= zns->cache.write_cache[wcidx].used) { + break; + } /* new write */ ppa = get_new_page(zns, zone_idx); ppa.g.pl = p; for(j = 0; j < flash_type ;j++) { + if (i >= zns->cache.write_cache[wcidx].used) { + break; + } ppa.g.pg = get_blk(zns,&ppa)->page_wp; get_blk(zns,&ppa)->page_wp++; for(subpage = 0;subpage < ZNS_PAGE_SIZE/LOGICAL_PAGE_SIZE;subpage++) @@ -503,19 +514,16 @@ static uint64_t zns_write(struct zns_ssd *zns, NvmeRequest *req) if(wcidx==-1) { - //need flush - wcidx = 0; - uint64_t t_used = zns->cache.write_cache[wcidx].used; - for(i = 1;i < zns->cache.num_wc;i++) - { - if(zns->cache.write_cache[i].used==0) - { + /* take an empty cache; failing that, evict the fullest one */ + uint64_t t_used = 0; + wcidx = -1; + for (i = 0; i < zns->cache.num_wc; i++) { + if (zns->cache.write_cache[i].used == 0) { t_used = 0; - wcidx = i; //free wc! + wcidx = i; break; } - if(zns->cache.write_cache[i].used > t_used) - { + if (wcidx < 0 || zns->cache.write_cache[i].used > t_used) { t_used = zns->cache.write_cache[i].used; wcidx = i; } diff --git a/hw/femu/zns/zns.c b/hw/femu/zns/zns.c index b4d0c1644a..59162cd25a 100644 --- a/hw/femu/zns/zns.c +++ b/hw/femu/zns/zns.c @@ -1794,7 +1794,20 @@ static void zns_init_params(FemuCtrl *n, NvmeNamespace *ns) /* one program covers every plane of a die; the flush walks the same set */ id_zns->program_unit = ZNS_PAGE_SIZE * id_zns->flash_type * id_zns->num_plane; id_zns->stripe_unit = id_zns->program_unit*id_zns->num_ch*id_zns->num_lun; - id_zns->cache.num_wc = ZNS_DEFAULT_NUM_WRITE_CACHE; + /* + * One write cache stages the partial stripe of one zone, so a device + * needs as many as it lets the host keep open: an open-zone limit + * exists to bound exactly these per-zone buffers (Bjorling et al., + * ATC'21). Fewer caches than concurrent writers makes every write + * evict another zone's cache and pay a synchronous flush. + */ + if (n->zns_params.zns_num_wc) { + id_zns->cache.num_wc = n->zns_params.zns_num_wc; + } else if (n->zns_params.zns_max_open) { + id_zns->cache.num_wc = n->zns_params.zns_max_open; + } else { + id_zns->cache.num_wc = ZNS_DEFAULT_NUM_WRITE_CACHE; + } id_zns->cache.write_cache = g_malloc0(sizeof(struct zns_write_cache) * id_zns->cache.num_wc); for(i =0; i < id_zns->cache.num_wc; i++) {