nvme_disk: Properly implement partial reads.

The previous version did not account for the position not being a
multiple of the block size, among other problems.

Tested by hand with DiskProbe and then "dd skip=..." to read single
bytes from partial blocks, and validated as correct.
This commit is contained in:
Augustin Cavalier
2019-04-14 20:14:53 -04:00
parent 6ed6023d01
commit c9ba44b70a
@@ -267,6 +267,9 @@ nvme_disk_free(void* cookie)
} }
// #pragma mark - I/O functions
static void static void
disk_read_callback(status_t* status, const struct nvme_cpl* cpl) disk_read_callback(status_t* status, const struct nvme_cpl* cpl)
{ {
@@ -275,30 +278,44 @@ disk_read_callback(status_t* status, const struct nvme_cpl* cpl)
static status_t static status_t
nvme_disk_read(void* cookie, off_t pos, void* buffer, size_t* _length) nvme_disk_read(void* cookie, off_t pos, void* buffer, size_t* length)
{ {
CALLED(); CALLED();
nvme_disk_handle* handle = (nvme_disk_handle*)cookie; nvme_disk_handle* handle = (nvme_disk_handle*)cookie;
const size_t block_size = handle->info->block_size;
status_t status = EINPROGRESS; status_t status = EINPROGRESS;
// libnvme does transfers in units of device sectors, so we must // libnvme does transfers in units of device sectors, so if we have to
// round up the length to the sector size, and then allocate a // round either the position or the length, we will need a bounce buffer.
// bounce buffer. off_t rounded_pos = ROUNDDOWN(pos, block_size);
const size_t block_size = handle->info->block_size; size_t rounded_len = ROUNDUP(*length, block_size);
size_t rounded_len = ROUNDUP(*_length, block_size); if (rounded_pos != pos || rounded_len != *length
void* real_buffer; || IS_USER_ADDRESS(buffer)) {
if (rounded_len == *_length && !IS_USER_ADDRESS(buffer)) void* bounceBuffer = malloc(rounded_len);
real_buffer = buffer; if (bounceBuffer == NULL)
else
real_buffer = malloc(rounded_len);
if (real_buffer == NULL)
return B_NO_MEMORY; return B_NO_MEMORY;
status = nvme_disk_read(cookie, rounded_pos, bounceBuffer,
&rounded_len);
if (status != B_OK) {
*length = 0;
return status;
}
void* offsetBuffer = ((int8*)bounceBuffer) + (pos - rounded_pos);
if (IS_USER_ADDRESS(buffer))
status = user_memcpy(buffer, offsetBuffer, *length);
else
memcpy(buffer, offsetBuffer, *length);
free(bounceBuffer);
return status;
}
// Actually perform the read.
MutexLocker _(handle->info->qpair_mtx); MutexLocker _(handle->info->qpair_mtx);
int ret = nvme_ns_read(handle->info->ns, handle->info->qpair, int ret = nvme_ns_read(handle->info->ns, handle->info->qpair,
real_buffer, pos / block_size, rounded_len / block_size, buffer, rounded_pos / block_size, rounded_len / block_size,
(nvme_cmd_cb)disk_read_callback, &status, 0); (nvme_cmd_cb)disk_read_callback, &status, 0);
if (ret != 0) if (ret != 0)
return ret; return ret;
@@ -308,19 +325,8 @@ nvme_disk_read(void* cookie, off_t pos, void* buffer, size_t* _length)
snooze(5); snooze(5);
} }
if (status != B_OK) { if (status != B_OK)
*_length = 0; *length = 0;
return status;
}
if (real_buffer != buffer) {
if (IS_USER_ADDRESS(buffer))
status = user_memcpy(buffer, real_buffer, *_length);
else
memcpy(buffer, real_buffer, *_length);
free(real_buffer);
}
return status; return status;
} }
@@ -419,8 +425,6 @@ nvme_disk_supports_device(device_node *parent)
|| sDeviceManager->get_attr_uint16(parent, B_DEVICE_SUB_TYPE, &subClass, false) != B_OK) || sDeviceManager->get_attr_uint16(parent, B_DEVICE_SUB_TYPE, &subClass, false) != B_OK)
return -1.0f; return -1.0f;
TRACE("strcmp: %d, baseclass: %d\n", strcmp(bus, "pci") != 0, baseClass != PCI_mass_storage);
if (strcmp(bus, "pci") != 0 || baseClass != PCI_mass_storage) if (strcmp(bus, "pci") != 0 || baseClass != PCI_mass_storage)
return 0.0f; return 0.0f;