nvme_disk: Implement TRIM.
Only lightly tested (in QEMU with qcow2 images: it seems to work and not to discard actual data.) Use with caution!
This commit is contained in:
@@ -853,6 +853,77 @@ nvme_disk_flush(nvme_disk_driver_info* info)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static status_t
|
||||||
|
nvme_disk_trim(nvme_disk_driver_info* info, fs_trim_data* trimData)
|
||||||
|
{
|
||||||
|
CALLED();
|
||||||
|
trimData->trimmed_size = 0;
|
||||||
|
|
||||||
|
const off_t deviceSize = info->capacity * info->block_size; // in bytes
|
||||||
|
if (deviceSize < 0)
|
||||||
|
return B_BAD_VALUE;
|
||||||
|
|
||||||
|
STATIC_ASSERT(sizeof(deviceSize) <= sizeof(uint64));
|
||||||
|
ASSERT(deviceSize >= 0);
|
||||||
|
|
||||||
|
// Do not trim past device end.
|
||||||
|
for (uint32 i = 0; i < trimData->range_count; i++) {
|
||||||
|
uint64 offset = trimData->ranges[i].offset;
|
||||||
|
uint64& size = trimData->ranges[i].size;
|
||||||
|
|
||||||
|
if (offset >= (uint64)deviceSize)
|
||||||
|
return B_BAD_VALUE;
|
||||||
|
size = std::min(size, (uint64)deviceSize - offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need contiguous memory for the DSM ranges.
|
||||||
|
nvme_dsm_range* dsmRanges = (nvme_dsm_range*)nvme_mem_alloc_node(
|
||||||
|
trimData->range_count * sizeof(nvme_dsm_range), 0, 0, NULL);
|
||||||
|
if (dsmRanges == NULL)
|
||||||
|
return B_NO_MEMORY;
|
||||||
|
CObjectDeleter<void, void, nvme_free> dsmRangesDeleter(dsmRanges);
|
||||||
|
|
||||||
|
uint64 trimmingSize = 0;
|
||||||
|
for (uint32 i = 0; i < trimData->range_count; i++) {
|
||||||
|
uint64 offset = trimData->ranges[i].offset;
|
||||||
|
uint64 length = trimData->ranges[i].size;
|
||||||
|
|
||||||
|
// Round up offset and length to the block size.
|
||||||
|
// (Some space at the beginning and end may thus not be trimmed.)
|
||||||
|
offset = ROUNDUP(offset, info->block_size);
|
||||||
|
length -= offset - trimData->ranges[i].offset;
|
||||||
|
length = ROUNDDOWN(length, info->block_size);
|
||||||
|
|
||||||
|
if (length == 0)
|
||||||
|
continue;
|
||||||
|
if ((length / info->block_size) > UINT32_MAX)
|
||||||
|
length = uint64(UINT32_MAX) * info->block_size;
|
||||||
|
// TODO: Break into smaller trim ranges!
|
||||||
|
|
||||||
|
TRACE("trim %" B_PRIu64 " bytes from %" B_PRIu64 "\n", length, offset);
|
||||||
|
|
||||||
|
dsmRanges[i].attributes = 0;
|
||||||
|
dsmRanges[i].length = length / info->block_size;
|
||||||
|
dsmRanges[i].starting_lba = offset / info->block_size;
|
||||||
|
|
||||||
|
trimmingSize += dsmRanges[i].length;
|
||||||
|
}
|
||||||
|
|
||||||
|
status_t status = EINPROGRESS;
|
||||||
|
qpair_info* qpair = get_qpair(info);
|
||||||
|
if (nvme_ns_deallocate(info->ns, qpair->qpair, dsmRanges, trimData->range_count,
|
||||||
|
(nvme_cmd_cb)io_finished_callback, &status) != 0)
|
||||||
|
return B_IO_ERROR;
|
||||||
|
|
||||||
|
await_status(info, qpair->qpair, status);
|
||||||
|
if (status != B_OK)
|
||||||
|
return status;
|
||||||
|
|
||||||
|
trimData->trimmed_size = trimmingSize;
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static status_t
|
static status_t
|
||||||
nvme_disk_ioctl(void* cookie, uint32 op, void* buffer, size_t length)
|
nvme_disk_ioctl(void* cookie, uint32 op, void* buffer, size_t length)
|
||||||
{
|
{
|
||||||
@@ -914,6 +985,10 @@ nvme_disk_ioctl(void* cookie, uint32 op, void* buffer, size_t length)
|
|||||||
|
|
||||||
case B_FLUSH_DRIVE_CACHE:
|
case B_FLUSH_DRIVE_CACHE:
|
||||||
return nvme_disk_flush(info);
|
return nvme_disk_flush(info);
|
||||||
|
|
||||||
|
case B_TRIM_DEVICE:
|
||||||
|
ASSERT(IS_KERNEL_ADDRESS(buffer));
|
||||||
|
return nvme_disk_trim(info, (fs_trim_data*)buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
return B_DEV_INVALID_IOCTL;
|
return B_DEV_INVALID_IOCTL;
|
||||||
|
|||||||
Reference in New Issue
Block a user