Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 86 additions & 1 deletion hw/femu/bbssd/ftl.c
Original file line number Diff line number Diff line change
Expand Up @@ -858,6 +858,89 @@ static uint64_t ssd_write(struct ssd *ssd, NvmeRequest *req)
return maxlat;
}

static uint64_t ssd_trim(struct ssd *ssd, NvmeRequest *req)
{
struct ssdparams *spp = &ssd->sp;
NvmeDsmRange *ranges = req->dsm_ranges;
int nr_ranges = req->dsm_nr_ranges;
// uint32_t attributes = req->dsm_attributes;

int total_trimmed_pages = 0;
int total_already_invalid = 0;
int total_out_of_bounds = 0;
Comment on lines +868 to +870
Copy link

Copilot AI Jul 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The counters total_trimmed_pages, total_already_invalid, and total_out_of_bounds are never used or logged. Remove them or emit a summary log to reduce dead code and improve observability.

Suggested change
int total_trimmed_pages = 0;
int total_already_invalid = 0;
int total_out_of_bounds = 0;
// Removed unused counters: total_trimmed_pages, total_already_invalid, total_out_of_bounds

Copilot uses AI. Check for mistakes.

if (!ranges || nr_ranges <= 0) {
printf("TRIM: Invalid ranges or count\n");
Copy link

Copilot AI Jul 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Using printf in the FTL path is inconsistent with existing ftl_err/ftl_debug logging macros. Consider switching to the project’s logging API for consistency and log filtering.

Suggested change
printf("TRIM: Invalid ranges or count\n");
ftl_err("TRIM: Invalid ranges or count\n");

Copilot uses AI. Check for mistakes.
return 0;
}

// printf("TRIM: Processing %d ranges (attributes=0x%x)\n", nr_ranges, attributes);

for (int range_idx = 0; range_idx < nr_ranges; range_idx++) {
uint64_t slba = le64_to_cpu(ranges[range_idx].slba);
uint32_t nlb = le32_to_cpu(ranges[range_idx].nlb);
// uint32_t cattr = le32_to_cpu(ranges[range_idx].cattr);

uint64_t start_lpn = slba / spp->secs_per_pg;
uint64_t end_lpn = (slba + nlb - 1) / spp->secs_per_pg;
uint64_t lpn;
struct ppa ppa;
int trimmed_pages = 0;
int already_invalid = 0;

// ftl_debug("TRIM Range %d: LBA %lu + %u sectors, LPN range %lu-%lu (%lu pages), cattr=0x%x\n",
// range_idx, slba, nlb, start_lpn, end_lpn, end_lpn - start_lpn + 1, cattr);

// Boundary check
if (end_lpn >= spp->tt_pgs) {
ftl_err("TRIM: Range %d exceeds FTL capacity - end_lpn=%lu, tt_pgs=%d\n",
range_idx, end_lpn, spp->tt_pgs);
total_out_of_bounds++;
continue; // Skip this range, continue with others
}

// Process each LPN in this range
for (lpn = start_lpn; lpn <= end_lpn; lpn++) {
ppa = get_maptbl_ent(ssd, lpn);

// Skip already unmapped/invalid pages
if (!mapped_ppa(&ppa) || !valid_ppa(ssd, &ppa)) {
already_invalid++;
continue;
}

// Invalidate the existing mapped page
mark_page_invalid(ssd, &ppa);

// Clear reverse mapping
set_rmap_ent(ssd, INVALID_LPN, &ppa);

// Set mapping table entry as unmapped
ppa.ppa = UNMAPPED_PPA;
set_maptbl_ent(ssd, lpn, &ppa);

trimmed_pages++;
}

total_trimmed_pages += trimmed_pages;
total_already_invalid += already_invalid;

// ftl_debug("TRIM Range %d: %d pages trimmed, %d already invalid\n",
// range_idx, trimmed_pages, already_invalid);
}

// ftl_debug("TRIM: Completed - %d pages trimmed, %d already invalid, %d out of bounds across %d ranges\n",
// total_trimmed_pages, total_already_invalid, total_out_of_bounds, nr_ranges);

// Free the ranges array
g_free(ranges);
req->dsm_ranges = NULL;
req->dsm_nr_ranges = 0;
req->dsm_attributes = 0;

return 0; // Assume TRIM operations have no NAND latency
}

static void *ftl_thread(void *arg)
{
FemuCtrl *n = (FemuCtrl *)arg;
Expand Down Expand Up @@ -894,7 +977,9 @@ static void *ftl_thread(void *arg)
lat = ssd_read(ssd, req);
break;
case NVME_CMD_DSM:
Copy link

Copilot AI Jul 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The latency variable (lat) is not initialized for the DSM case when req->dsm_ranges is empty, leading to undefined behavior. Consider setting lat = 0; before the conditional or restoring the default assignment.

Copilot uses AI. Check for mistakes.
lat = 0;
if (req->dsm_ranges && req->dsm_nr_ranges > 0) {
lat = ssd_trim(ssd, req);
}
break;
default:
//ftl_err("FTL received unkown request type, ERROR\n");
Expand Down
140 changes: 111 additions & 29 deletions hw/femu/nvme-io.c
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ static void nvme_process_sq_io(void *opaque, int index_poller)
req = QTAILQ_FIRST(&sq->req_list);
QTAILQ_REMOVE(&sq->req_list, req, entry);
memset(&req->cqe, 0, sizeof(req->cqe));
req->dsm_ranges = NULL;
req->dsm_nr_ranges = 0;
req->dsm_attributes = 0;
/* Coperd: record req->stime at earliest convenience */
req->expire_time = req->stime = qemu_clock_get_ns(QEMU_CLOCK_REALTIME);
req->cqe.cid = cmd.cid;
Expand All @@ -73,18 +76,29 @@ static void nvme_process_sq_io(void *opaque, int index_poller)
}

status = nvme_io_cmd(n, &cmd, req);
if (1 && status == NVME_SUCCESS) {
if (status == NVME_SUCCESS) {
req->status = status;

int rc = femu_ring_enqueue(n->to_ftl[index_poller], (void *)&req, 1);
if (rc != 1) {
femu_err("enqueue failed, ret=%d\n", rc);
// Clean up DSM ranges on enqueue failure
if (req->dsm_ranges) {
g_free(req->dsm_ranges);
req->dsm_ranges = NULL;
req->dsm_nr_ranges = 0;
}
}
} else if (status == NVME_SUCCESS) {
/* Normal I/Os that don't need delay emulation */
req->status = status;
} else {
femu_err("Error IO processed!\n");
femu_err("Error IO processed! opcode=0x%x, status=0x%x\n",
cmd.opcode, status);
req->status = status;

// Clean up DSM ranges on error
if (req->dsm_ranges) {
g_free(req->dsm_ranges);
req->dsm_ranges = NULL;
req->dsm_nr_ranges = 0;
}
}

processed++;
Expand Down Expand Up @@ -282,41 +296,109 @@ uint16_t nvme_rw(FemuCtrl *n, NvmeNamespace *ns, NvmeCmd *cmd, NvmeRequest *req)
static uint16_t nvme_dsm(FemuCtrl *n, NvmeNamespace *ns, NvmeCmd *cmd,
NvmeRequest *req)
{
uint32_t dw10 = le32_to_cpu(cmd->cdw10);
uint32_t dw11 = le32_to_cpu(cmd->cdw11);
uint32_t cdw10 = le32_to_cpu(cmd->cdw10);
uint32_t cdw11 = le32_to_cpu(cmd->cdw11);
uint64_t prp1 = le64_to_cpu(cmd->dptr.prp1);
uint64_t prp2 = le64_to_cpu(cmd->dptr.prp2);
uint16_t nr_ranges;
NvmeDsmRange *ranges = NULL;
int i;

if (dw11 & NVME_DSMGMT_AD) {
uint16_t nr = (dw10 & 0xff) + 1;
// Extract number of ranges from CDW10 (bits 7:0, 0-based)
nr_ranges = (cdw10 & 0xFF) + 1;

// Validate range count - NVMe supports up to 256 ranges
if (nr_ranges > 256) {
femu_err("DSM: Invalid range count %u (max 256)\n", nr_ranges);
nvme_set_error_page(n, req->sq->sqid, cmd->cid, NVME_INVALID_FIELD,
offsetof(NvmeCmd, cdw10), nr_ranges, ns->id);
return NVME_INVALID_FIELD | NVME_DNR;
}

// Check if any deallocate operation is requested
bool has_deallocate = (cdw11 & NVME_DSMGMT_AD) != 0;
// bool has_idr = (cdw11 & NVME_DSMGMT_IDR) != 0;
// bool has_idw = (cdw11 & NVME_DSMGMT_IDW) != 0;

// femu_debug("DSM: nr_ranges=%u, AD=%d, IDR=%d, IDW=%d\n",
// nr_ranges, has_deallocate, has_idr, has_idw);

// If no deallocate attribute, no need to process further for TRIM
if (!has_deallocate) {
femu_err("DSM: No deallocate attribute set, skipping\n");
Copy link

Copilot AI Jul 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Skipping DSM processing when no deallocate attribute is expected; logging this as an error may confuse readers. Use a debug-level log and ensure req->cmd_opcode is set before early returns to keep request routing consistent.

Suggested change
femu_err("DSM: No deallocate attribute set, skipping\n");
femu_debug("DSM: No deallocate attribute set, skipping\n");
req->cmd_opcode = cmd->opcode;

Copilot uses AI. Check for mistakes.
return NVME_SUCCESS;
}

uint64_t slba;
uint32_t nlb;
NvmeDsmRange *range = g_malloc0(sizeof(NvmeDsmRange) * nr);
// Allocate buffer for DSM ranges
size_t ranges_size = sizeof(NvmeDsmRange) * nr_ranges;
ranges = g_malloc0(ranges_size);
if (!ranges) {
femu_err("DSM: Failed to allocate memory for %u ranges\n", nr_ranges);
return NVME_INTERNAL_DEV_ERROR | NVME_DNR;
}

if (dma_write_prp(n, (uint8_t *)range, sizeof(*range), prp1, prp2)) {
nvme_set_error_page(n, req->sq->sqid, cmd->cid, NVME_INVALID_FIELD,
if (dma_write_prp(n, (uint8_t *)ranges, ranges_size, prp1, prp2)) {
nvme_set_error_page(n, req->sq->sqid, cmd->cid, NVME_INVALID_FIELD,
offsetof(NvmeCmd, dptr.prp1), 0, ns->id);
g_free(range);
g_free(ranges);
return NVME_INVALID_FIELD | NVME_DNR;
}
// Validate and process each range
uint64_t total_blocks = 0;
uint64_t ns_size = le64_to_cpu(ns->id_ns.nsze);

for (i = 0; i < nr_ranges; i++) {
uint64_t slba = le64_to_cpu(ranges[i].slba);
uint32_t nlb = le32_to_cpu(ranges[i].nlb);
// uint32_t cattr = le32_to_cpu(ranges[i].cattr);

// femu_debug(" DSM Range %d: slba=%lu, nlb=%u, cattr=0x%x\n",
// i, slba, nlb, cattr);

// Validate LBA range against namespace size
if (slba >= ns_size) {
femu_err("DSM: Range %d SLBA %lu exceeds namespace size %lu\n",
i, slba, ns_size);
nvme_set_error_page(n, req->sq->sqid, cmd->cid, NVME_LBA_RANGE,
offsetof(NvmeCmd, cdw10), slba, ns->id);
g_free(ranges);
return NVME_LBA_RANGE | NVME_DNR;
}

req->status = NVME_SUCCESS;
for (i = 0; i < nr; i++) {
slba = le64_to_cpu(range[i].slba);
nlb = le32_to_cpu(range[i].nlb);
if (slba + nlb > le64_to_cpu(ns->id_ns.nsze)) {
nvme_set_error_page(n, req->sq->sqid, cmd->cid, NVME_LBA_RANGE,
offsetof(NvmeCmd, cdw10), slba + nlb, ns->id);
g_free(range);
return NVME_LBA_RANGE | NVME_DNR;
}
if (slba + nlb > ns_size) {
femu_err("DSM: Range %d end LBA %lu exceeds namespace size %lu\n",
i, slba + nlb, ns_size);
nvme_set_error_page(n, req->sq->sqid, cmd->cid, NVME_LBA_RANGE,
offsetof(NvmeCmd, cdw10), slba + nlb, ns->id);
g_free(ranges);
return NVME_LBA_RANGE | NVME_DNR;
}

// Check for integer overflow in block count accumulation
if (total_blocks > UINT64_MAX - nlb) {
femu_err("DSM: Total block count overflow\n");
g_free(ranges);
return NVME_INVALID_FIELD | NVME_DNR;
}

total_blocks += nlb;

// Update namespace utilization bitmap for this range
if (ns->util) {
bitmap_clear(ns->util, slba, nlb);
}
g_free(range);
}

femu_debug("DSM: Total blocks to deallocate: %lu\n", total_blocks);

// Store ranges in request for FTL processing
req->dsm_ranges = ranges;
req->dsm_nr_ranges = nr_ranges;
req->dsm_attributes = cdw11;
req->cmd_opcode = NVME_CMD_DSM;

// Don't free ranges here - FTL will handle them
req->status = NVME_SUCCESS;
return NVME_SUCCESS;
}

Expand Down Expand Up @@ -368,8 +450,8 @@ static uint16_t nvme_compare(FemuCtrl *n, NvmeNamespace *ns, NvmeCmd *cmd,
return NVME_CMP_FAILURE;
}
offset += len;
free(tmp[0]);
free(tmp[1]);
g_free(tmp[0]);
g_free(tmp[1]);
}

qemu_sglist_destroy(&req->qsg);
Expand Down
5 changes: 5 additions & 0 deletions hw/femu/nvme.h
Original file line number Diff line number Diff line change
Expand Up @@ -993,6 +993,11 @@ typedef struct NvmeRequest {

/* position in the priority queue for delay emulation */
size_t pos;

// DSM (Dataset Management) related fields
NvmeDsmRange *dsm_ranges; // Array of DSM ranges
int dsm_nr_ranges; // Number of ranges
uint32_t dsm_attributes; // CDW11 attributes (AD, IDR, IDW)
} NvmeRequest;

typedef struct DMAOff {
Expand Down