nvme: Import the driver code.

Only one qpair is used for reading, which is rather inefficient.
We currently allocate a bounce buffer for every allocation, which is
also inefficient, due to the fact that we must read an integer multiple
of LBAs.

But it does work, and it is actually reasonably fast, even on an emulated
machine using a spinning-disk-backed NVMe device (88MB/s.)

I wasn't able to get it working in non-packaged, though; the device manager
called supports_device() on a number of PCI devices, but not the NVMe
device, so I have a different version with a hack that grabs the PCI info
manually. I didn't test inside haiku.hpkg yet; perhaps it will work in there.
This commit is contained in:
Augustin Cavalier 2019-04-14 15:07:31 -04:00
parent 4b88e72350
commit 2338299bcb
3 changed files with 580 additions and 2 deletions

View File

@ -1,8 +1,9 @@
SubDir HAIKU_TOP src add-ons kernel drivers disk ; SubDir HAIKU_TOP src add-ons kernel drivers disk ;
SubInclude HAIKU_TOP src add-ons kernel drivers disk floppy ; SubInclude HAIKU_TOP src add-ons kernel drivers disk floppy ;
SubInclude HAIKU_TOP src add-ons kernel drivers disk norflash ;
SubInclude HAIKU_TOP src add-ons kernel drivers disk mmc ; SubInclude HAIKU_TOP src add-ons kernel drivers disk mmc ;
SubInclude HAIKU_TOP src add-ons kernel drivers disk norflash ;
SubInclude HAIKU_TOP src add-ons kernel drivers disk nvme ;
SubInclude HAIKU_TOP src add-ons kernel drivers disk scsi ; SubInclude HAIKU_TOP src add-ons kernel drivers disk scsi ;
SubInclude HAIKU_TOP src add-ons kernel drivers disk usb ; SubInclude HAIKU_TOP src add-ons kernel drivers disk usb ;
SubInclude HAIKU_TOP src add-ons kernel drivers disk virtual ; SubInclude HAIKU_TOP src add-ons kernel drivers disk virtual ;

View File

@ -0,0 +1,23 @@
SubDir HAIKU_TOP src add-ons kernel drivers disk nvme ;
UsePrivateKernelHeaders ;
SubDirHdrs $(HAIKU_TOP) src system kernel device_manager ;
UseHeaders [ FDirName $(SUBDIR) ] : true ;
SEARCH_SOURCE += [ FDirName $(SUBDIR) libnvme ] ;
SEARCH_SOURCE += [ FDirName $(SUBDIR) compat ] ;
KernelAddon nvme_disk :
nvme_disk.cpp
libnvme_haiku.cpp
nvme.c
nvme_admin.c
nvme_common.c
nvme_ctrlr.c
nvme_ns.c
nvme_qpair.c
nvme_quirks.c
nvme_request.c
;

View File

@ -0,0 +1,554 @@
/*
* Copyright 2019, Haiku, Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Augustin Cavalier <waddlesplash>
*/
#include <stdio.h>
#include <stdlib.h>
#include <kernel.h>
#include <util/AutoLock.h>
#include <fs/devfs.h>
#include <bus/PCI.h>
extern "C" {
#include <libnvme/nvme.h>
}
#define TRACE_NVME_DISK
#ifdef TRACE_NVME_DISK
# define TRACE(x...) dprintf("nvme_disk: " x)
#else
# define TRACE(x...) ;
#endif
#define TRACE_ERROR(x...) dprintf("\33[33mnvme_disk:\33[0m " x)
#define CALLED() TRACE("CALLED %s\n", __PRETTY_FUNCTION__)
static const uint8 kDriveIcon[] = {
0x6e, 0x63, 0x69, 0x66, 0x08, 0x03, 0x01, 0x00, 0x00, 0x02, 0x00, 0x16,
0x02, 0x3c, 0xc7, 0xee, 0x38, 0x9b, 0xc0, 0xba, 0x16, 0x57, 0x3e, 0x39,
0xb0, 0x49, 0x77, 0xc8, 0x42, 0xad, 0xc7, 0x00, 0xff, 0xff, 0xd3, 0x02,
0x00, 0x06, 0x02, 0x3c, 0x96, 0x32, 0x3a, 0x4d, 0x3f, 0xba, 0xfc, 0x01,
0x3d, 0x5a, 0x97, 0x4b, 0x57, 0xa5, 0x49, 0x84, 0x4d, 0x00, 0x47, 0x47,
0x47, 0xff, 0xa5, 0xa0, 0xa0, 0x02, 0x00, 0x16, 0x02, 0xbc, 0x59, 0x2f,
0xbb, 0x29, 0xa7, 0x3c, 0x0c, 0xe4, 0xbd, 0x0b, 0x7c, 0x48, 0x92, 0xc0,
0x4b, 0x79, 0x66, 0x00, 0x7d, 0xff, 0xd4, 0x02, 0x00, 0x06, 0x02, 0x38,
0xdb, 0xb4, 0x39, 0x97, 0x33, 0xbc, 0x4a, 0x33, 0x3b, 0xa5, 0x42, 0x48,
0x6e, 0x66, 0x49, 0xee, 0x7b, 0x00, 0x59, 0x67, 0x56, 0xff, 0xeb, 0xb2,
0xb2, 0x03, 0xa7, 0xff, 0x00, 0x03, 0xff, 0x00, 0x00, 0x04, 0x01, 0x80,
0x07, 0x0a, 0x06, 0x22, 0x3c, 0x22, 0x49, 0x44, 0x5b, 0x5a, 0x3e, 0x5a,
0x31, 0x39, 0x25, 0x0a, 0x04, 0x22, 0x3c, 0x44, 0x4b, 0x5a, 0x31, 0x39,
0x25, 0x0a, 0x04, 0x44, 0x4b, 0x44, 0x5b, 0x5a, 0x3e, 0x5a, 0x31, 0x0a,
0x04, 0x22, 0x3c, 0x22, 0x49, 0x44, 0x5b, 0x44, 0x4b, 0x08, 0x02, 0x27,
0x43, 0xb8, 0x14, 0xc1, 0xf1, 0x08, 0x02, 0x26, 0x43, 0x29, 0x44, 0x0a,
0x05, 0x44, 0x5d, 0x49, 0x5d, 0x60, 0x3e, 0x5a, 0x3b, 0x5b, 0x3f, 0x08,
0x0a, 0x07, 0x01, 0x06, 0x00, 0x0a, 0x00, 0x01, 0x00, 0x10, 0x01, 0x17,
0x84, 0x00, 0x04, 0x0a, 0x01, 0x01, 0x01, 0x00, 0x0a, 0x02, 0x01, 0x02,
0x00, 0x0a, 0x03, 0x01, 0x03, 0x00, 0x0a, 0x04, 0x01, 0x04, 0x10, 0x01,
0x17, 0x85, 0x20, 0x04, 0x0a, 0x06, 0x01, 0x05, 0x30, 0x24, 0xb3, 0x99,
0x01, 0x17, 0x82, 0x00, 0x04, 0x0a, 0x05, 0x01, 0x05, 0x30, 0x20, 0xb2,
0xe6, 0x01, 0x17, 0x82, 0x00, 0x04
};
#define NVME_DISK_DRIVER_MODULE_NAME "drivers/disk/nvme_disk/driver_v1"
#define NVME_DISK_DEVICE_MODULE_NAME "drivers/disk/nvme_disk/device_v1"
#define NVME_DISK_DEVICE_ID_GENERATOR "nvme_disk/device_id"
static device_manager_info* sDeviceManager;
typedef struct {
device_node* node;
pci_device_module_info* pci;
pci_device* device;
pci_info info;
struct nvme_ctrlr* ctrlr;
struct nvme_ns* ns;
uint64 capacity;
uint32 block_size;
status_t media_status;
struct nvme_qpair* qpair;
mutex qpair_mtx;
} nvme_disk_driver_info;
typedef struct {
nvme_disk_driver_info* info;
} nvme_disk_handle;
static status_t
get_geometry(nvme_disk_handle* handle, device_geometry* geometry)
{
nvme_disk_driver_info* info = handle->info;
devfs_compute_geometry_size(geometry, info->capacity, info->block_size);
geometry->device_type = B_DISK;
geometry->removable = false;
geometry->read_only = true; /* TODO: Write support! */
geometry->write_once = false;
TRACE("get_geometry(): %" B_PRId32 ", %" B_PRId32 ", %" B_PRId32 ", %" B_PRId32 ", %d, %d, %d, %d\n",
geometry->bytes_per_sector, geometry->sectors_per_track,
geometry->cylinder_count, geometry->head_count, geometry->device_type,
geometry->removable, geometry->read_only, geometry->write_once);
return B_OK;
}
static int
log2(uint32 x)
{
int y;
for (y = 31; y >= 0; --y) {
if (x == ((uint32)1 << y))
break;
}
return y;
}
static void
nvme_disk_set_capacity(nvme_disk_driver_info* info, uint64 capacity,
uint32 blockSize)
{
TRACE("set_capacity(device = %p, capacity = %" B_PRIu64 ", blockSize = %" B_PRIu32 ")\n",
info, capacity, blockSize);
// get log2, if possible
uint32 blockShift = log2(blockSize);
if ((1UL << blockShift) != blockSize)
blockShift = 0;
info->capacity = capacity;
info->block_size = blockSize;
}
// #pragma mark - device module API
static status_t
nvme_disk_init_device(void* _info, void** _cookie)
{
CALLED();
nvme_disk_driver_info* info = (nvme_disk_driver_info*)_info;
device_node* parent = sDeviceManager->get_parent_node(info->node);
device_node* pciParent = sDeviceManager->get_parent_node(parent);
sDeviceManager->get_driver(pciParent, (driver_module_info**)&info->pci,
(void**)&info->device);
sDeviceManager->put_node(pciParent);
sDeviceManager->put_node(parent);
info->pci->get_pci_info(info->device, &info->info);
// construct the libnvme pci_device struct
pci_device* device = new pci_device;
*device = {
.vendor_id = info->info.vendor_id,
.device_id = info->info.device_id,
.subvendor_id = 0,
.subdevice_id = 0,
.domain = 0,
.bus = info->info.bus,
.dev = info->info.device,
.func = info->info.function,
.pci_info = &info->info,
};
// open the controller
info->ctrlr = nvme_ctrlr_open(device, NULL);
if (info->ctrlr == NULL) {
TRACE_ERROR("failed to open the controller!\n");
return B_ERROR;
}
struct nvme_ctrlr_stat cstat;
int err = nvme_ctrlr_stat(info->ctrlr, &cstat);
if (err != 0) {
TRACE_ERROR("failed to get controller information!\n");
return -err;
}
// TODO: export more than just the first namespace!
info->ns = nvme_ns_open(info->ctrlr, cstat.ns_ids[0]);
if (info->ns == NULL) {
TRACE_ERROR("failed to open namespace!\n");
return B_ERROR;
}
struct nvme_ns_stat nsstat;
err = nvme_ns_stat(info->ns, &nsstat);
if (err != 0) {
TRACE_ERROR("failed to get namespace information!\n");
return -err;
}
// store capacity information
nvme_disk_set_capacity(info, nsstat.sectors, nsstat.sector_size);
TRACE("capacity: %" B_PRIu64 ", block_size %" B_PRIu32 "\n",
info->capacity, info->block_size);
// allocate a qpair
// TODO: allocate more than one qpair
info->qpair = nvme_ioqp_get(info->ctrlr, (enum nvme_qprio)0, 0);
if (info->qpair == NULL) {
TRACE_ERROR("failed to allocate qpair!\n");
return B_ERROR;
}
mutex_init(&info->qpair_mtx, "qpair mtx");
*_cookie = info;
return B_OK;
}
static void
nvme_disk_uninit_device(void* _cookie)
{
CALLED();
nvme_disk_driver_info* info = (nvme_disk_driver_info*)_cookie;
}
static status_t
nvme_disk_open(void* _info, const char* path, int openMode, void** _cookie)
{
CALLED();
nvme_disk_driver_info* info = (nvme_disk_driver_info*)_info;
nvme_disk_handle* handle = (nvme_disk_handle*)malloc(
sizeof(nvme_disk_handle));
if (handle == NULL)
return B_NO_MEMORY;
handle->info = info;
*_cookie = handle;
return B_OK;
}
static status_t
nvme_disk_close(void* cookie)
{
CALLED();
nvme_disk_handle* handle = (nvme_disk_handle*)cookie;
return B_OK;
}
static status_t
nvme_disk_free(void* cookie)
{
CALLED();
nvme_disk_handle* handle = (nvme_disk_handle*)cookie;
free(handle);
return B_OK;
}
static void
disk_read_callback(bool* done, const struct nvme_cpl*)
{
*done = true;
}
static status_t
nvme_disk_read(void* cookie, off_t pos, void* buffer, size_t* _length)
{
CALLED();
nvme_disk_handle* handle = (nvme_disk_handle*)cookie;
bool done = false;
size_t rounded_len = ROUNDUP(*_length, handle->info->block_size);
void* real_buffer;
if (rounded_len == *_length && !IS_USER_ADDRESS(buffer))
real_buffer = buffer;
else
real_buffer = malloc(rounded_len);
MutexLocker _(handle->info->qpair_mtx);
int ret = nvme_ns_read(handle->info->ns, handle->info->qpair,
real_buffer, pos / handle->info->block_size, rounded_len / handle->info->block_size,
(nvme_cmd_cb)disk_read_callback, &done, 0);
if (ret != 0)
return ret;
while (!done) {
nvme_ioqp_poll(handle->info->qpair, 1);
snooze(1);
}
status_t status = B_OK;
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;
}
static status_t
nvme_disk_write(void* cookie, off_t pos, const void* buffer,
size_t* _length)
{
CALLED();
nvme_disk_handle* handle = (nvme_disk_handle*)cookie;
return B_NOT_SUPPORTED;
}
static status_t
nvme_disk_ioctl(void* cookie, uint32 op, void* buffer, size_t length)
{
CALLED();
nvme_disk_handle* handle = (nvme_disk_handle*)cookie;
nvme_disk_driver_info* info = handle->info;
TRACE("ioctl(op = %" B_PRId32 ")\n", op);
switch (op) {
case B_GET_MEDIA_STATUS:
{
*(status_t *)buffer = info->media_status;
info->media_status = B_OK;
return B_OK;
break;
}
case B_GET_DEVICE_SIZE:
{
size_t size = info->capacity * info->block_size;
return user_memcpy(buffer, &size, sizeof(size_t));
}
case B_GET_GEOMETRY:
{
if (buffer == NULL /*|| length != sizeof(device_geometry)*/)
return B_BAD_VALUE;
device_geometry geometry;
status_t status = get_geometry(handle, &geometry);
if (status != B_OK)
return status;
return user_memcpy(buffer, &geometry, sizeof(device_geometry));
}
case B_GET_ICON_NAME:
return user_strlcpy((char*)buffer, "devices/drive-harddisk",
B_FILE_NAME_LENGTH);
case B_GET_VECTOR_ICON:
{
device_icon iconData;
if (length != sizeof(device_icon))
return B_BAD_VALUE;
if (user_memcpy(&iconData, buffer, sizeof(device_icon)) != B_OK)
return B_BAD_ADDRESS;
if (iconData.icon_size >= (int32)sizeof(kDriveIcon)) {
if (user_memcpy(iconData.icon_data, kDriveIcon,
sizeof(kDriveIcon)) != B_OK)
return B_BAD_ADDRESS;
}
iconData.icon_size = sizeof(kDriveIcon);
return user_memcpy(buffer, &iconData, sizeof(device_icon));
}
/*case B_FLUSH_DRIVE_CACHE:
return synchronize_cache(info);*/
}
return B_DEV_INVALID_IOCTL;
}
// #pragma mark - driver module API
static float
nvme_disk_supports_device(device_node *parent)
{
CALLED();
const char* bus;
uint16 baseClass, subClass;
if (sDeviceManager->get_attr_string(parent, B_DEVICE_BUS, &bus, false) != B_OK
|| sDeviceManager->get_attr_uint16(parent, B_DEVICE_TYPE, &baseClass, false) != B_OK
|| sDeviceManager->get_attr_uint16(parent, B_DEVICE_SUB_TYPE, &subClass, false) != B_OK)
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)
return 0.0f;
if (subClass != PCI_nvm)
return 0.0f;
TRACE("NVMe device found!\n");
return 1.0f;
}
static status_t
nvme_disk_register_device(device_node *node)
{
CALLED();
// ready to register
device_attr attrs[] = {
{ NULL }
};
return sDeviceManager->register_node(node, NVME_DISK_DRIVER_MODULE_NAME,
attrs, NULL, NULL);
}
static status_t
nvme_disk_init_driver(device_node* node, void** cookie)
{
CALLED();
int ret = nvme_lib_init((enum nvme_log_level)0, (enum nvme_log_facility)0, NULL);
if (ret != 0) {
TRACE_ERROR("libnvme initialization failed!\n");
return ret;
}
nvme_disk_driver_info* info = (nvme_disk_driver_info*)malloc(
sizeof(nvme_disk_driver_info));
if (info == NULL)
return B_NO_MEMORY;
memset(info, 0, sizeof(*info));
info->media_status = B_OK;
info->node = node;
*cookie = info;
return B_OK;
}
static void
nvme_disk_uninit_driver(void* _cookie)
{
CALLED();
nvme_disk_driver_info* info = (nvme_disk_driver_info*)_cookie;
free(info);
}
static status_t
nvme_disk_register_child_devices(void* _cookie)
{
CALLED();
nvme_disk_driver_info* info = (nvme_disk_driver_info*)_cookie;
status_t status;
int32 id = sDeviceManager->create_id(NVME_DISK_DEVICE_ID_GENERATOR);
if (id < 0)
return id;
char name[64];
snprintf(name, sizeof(name), "disk/nvme/%" B_PRId32 "/raw",
id);
status = sDeviceManager->publish_device(info->node, name,
NVME_DISK_DEVICE_MODULE_NAME);
return status;
}
// #pragma mark -
module_dependency module_dependencies[] = {
{B_DEVICE_MANAGER_MODULE_NAME, (module_info**)&sDeviceManager},
{}
};
struct device_module_info sNvmeDiskDevice = {
{
NVME_DISK_DEVICE_MODULE_NAME,
0,
NULL
},
nvme_disk_init_device,
nvme_disk_uninit_device,
NULL, // remove,
nvme_disk_open,
nvme_disk_close,
nvme_disk_free,
nvme_disk_read,
nvme_disk_write,
NULL,
nvme_disk_ioctl,
NULL, // select
NULL, // deselect
};
struct driver_module_info sNvmeDiskDriver = {
{
NVME_DISK_DRIVER_MODULE_NAME,
0,
NULL
},
nvme_disk_supports_device,
nvme_disk_register_device,
nvme_disk_init_driver,
nvme_disk_uninit_driver,
nvme_disk_register_child_devices,
NULL, // rescan
NULL, // removed
};
module_info* modules[] = {
(module_info*)&sNvmeDiskDriver,
(module_info*)&sNvmeDiskDevice,
NULL
};