nvme_drv: add driver for NVMe storage devices

This driver component provides support for using consumer NVMe storage
devices, i.e. it omits name space managment and will always use the
first name space, on Genode. For now it defaults to a reasonable low
configuration:

  -    1 I/O queue (completion/submission tuple)
  -  128 entries in the I/O queue
  - 4096 as the only I/O transaction memory page size

Fixes #2747.
This commit is contained in:
Josef Söntgen 2018-04-09 16:13:54 +02:00 committed by Christian Helmuth
parent 372e426ec7
commit 04516a0d39
10 changed files with 2297 additions and 0 deletions

View File

@ -0,0 +1,2 @@
SRC_DIR = src/drivers/nvme
include $(GENODE_DIR)/repos/base/recipes/src/content.inc

View File

@ -0,0 +1 @@
2018-03-27 fcf9749c441d830aa4666f70e04cd1560c783b2f

View File

@ -0,0 +1,6 @@
base
os
platform_session
block_session
report_session
timer_session

189
repos/os/run/nvme.run Normal file
View File

@ -0,0 +1,189 @@
assert_spec x86
# perform write tests when requested
if {[info exists env(GENODE_TEST_WRITE)]} {
set test_write 1
} else {
set test_write 0
}
set is_qemu [have_include power_on/qemu]
set is_old [expr [have_spec fiasco] || [have_spec okl4] || [have_spec pistachio]]
set is_32bit_x86_hw [expr !$is_qemu && [have_spec 32bit]]
#
# Only run tests on supported platforms
#
if {[expr [have_spec linux] || $is_32bit_x86_hw || [expr $is_qemu && $is_old]]} {
puts "This run script is not supported on this platform."
exit 0
}
#
# Qemu and on certain platforms only use the small set of tests
#
set small_test [expr $is_qemu || [have_spec foc] || [have_spec sel4]]
#
# Check used commands
#
set dd [check_installed dd]
#
# Build
#
set build_components {
core init
drivers/nvme
drivers/timer
app/block_tester
}
source ${genode_dir}/repos/base/run/platform_drv.inc
append_platform_drv_build_components
build $build_components
#
# Create raw image
#
catch { exec $dd if=/dev/zero of=bin/nvme.raw bs=1M count=0 seek=32768 }
create_boot_directory
#
# Generate config
#
append config {
<config verbose="no">
<parent-provides>
<service name="ROM"/>
<service name="RAM"/>
<service name="IRQ"/>
<service name="IO_MEM"/>
<service name="IO_PORT"/>
<service name="CAP"/>
<service name="PD"/>
<service name="RM"/>
<service name="CPU"/>
<service name="LOG"/>
<service name="SIGNAL" />
</parent-provides>
<default-route>
<any-service> <parent/> <any-child/> </any-service>
</default-route>
<default caps="100"/>
<start name="timer">
<resource name="RAM" quantum="1M"/>
<provides><service name="Timer"/></provides>
</start>}
append_platform_drv_config
append config {
<start name="nvme_drv">
<resource name="RAM" quantum="16M"/>
<provides> <service name="Block"/> </provides>
<config>
<policy label_prefix="block_tester" writeable="no"/>
</config>
</start>
<start name="block_tester" caps="200">
<resource name="RAM" quantum="64M"/>
<config verbose="no" report="no" log="yes" stop_on_error="no">
<tests>}
append_if $small_test config {
<sequential length="256M" size="64K" synchronous="yes"/>
<random length="256M" size="64K" seed="0xdeadbeef"/>}
append_if [expr !$small_test] config {
<sequential length="1G" size="4K" synchronous="no"/>
<sequential length="1G" size="8K" synchronous="no"/>
<sequential length="1G" size="64K" synchronous="yes"/>
<sequential length="3G" size="1M" synchronous="no"/>
<random length="1G" size="16K" seed="0xdeadbeef" synchronous="yes"/>
<random length="3G" size="512K" seed="0xc0ffee" synchronous="yes"/>
<ping_pong length="1G" size="16K"/>}
append_if $test_write config {
<sequential length="256M" size="64K" synchronous="no" write="yes"/>
<replay bulk="yes">
<request type="read" lba="0" count="1"/>
<request type="read" lba="0" count="1"/>
<request type="read" lba="0" count="1"/>
<request type="read" lba="2048" count="1016"/>
<request type="read" lba="0" count="1"/>
<request type="read" lba="0" count="1"/>
<request type="read" lba="0" count="1"/>
<request type="read" lba="2048" count="1016"/>
<request type="read" lba="0" count="1"/>
<request type="read" lba="0" count="1"/>
<request type="read" lba="0" count="1"/>
<request type="read" lba="2048" count="1016"/>
<request type="read" lba="4096" count="1"/>
<request type="read" lba="51881" count="1"/>
<request type="read" lba="51890" count="1"/>
<request type="read" lba="114184" count="14"/>
<request type="read" lba="114198" count="1"/>
<request type="read" lba="114033" count="127"/>
<request type="read" lba="114160" count="24"/>
<request type="write" lba="0" count="1"/>
<request type="read" lba="12288" count="2048"/>
<request type="write" lba="4096" count="2048"/>
<request type="write" lba="0" count="1"/>
<request type="write" lba="2048" count="1"/>
<request type="write" lba="5696" count="1"/>
<request type="write" lba="5696" count="1"/>
<request type="write" lba="5696" count="1"/>
<request type="read" lba="4096" count="1"/>
<request type="read" lba="61440" count="16"/>
<request type="read" lba="158777" count="127"/>
<request type="write" lba="40960" count="2048"/>
<request type="write" lba="0" count="1"/>
<request type="write" lba="2073" count="1"/>
<request type="read" lba="190483" count="64"/>
<request type="read" lba="190411" count="53"/>
<request type="read" lba="190464" count="11"/>
<request type="read" lba="106074" count="64"/>
<request type="read" lba="105954" count="56"/>
<request type="read" lba="122802" count="24"/>
<request type="read" lba="123594" count="64"/>
<request type="read" lba="123722" count="64"/>
</replay>}
append config {
</tests>
</config>
<route>
<service name="Block"><child name="nvme_drv"/></service>
<any-service> <parent/> <any-child /> </any-service>
</route>
</start>
</config>}
install_config $config
#
# Boot modules
#
set boot_modules {
core init timer nvme_drv
ld.lib.so block_tester
}
append_platform_drv_boot_modules
build_boot_image $boot_modules
append qemu_args " -nographic -m 512 "
append qemu_args " -drive id=nvme0,file=bin/nvme.raw,format=raw,if=none "
append qemu_args " -device nvme,drive=nvme0,serial=fnord,id=nvme0n1 "
run_genode_until {.*child "block_tester" exited with exit value 0.*\n} 300
exec rm -f bin/nvme.raw

View File

@ -0,0 +1,40 @@
This directory contains the implementation of a NVMe driver component.
Brief
=====
The driver supports PCIe NVMe devices matching at least revision 1.1 of
the NVMe specification. For now it only supports one name space and uses
one completion and one submission queue to handle all I/O requests; one
request is limited to 1MiB of data. It lacks any name space management
functionality.
Configuration
=============
The following config illustrates how the driver is configured:
!<start name="nvme_drv">
! <resource name="ram" quantum="8M"/>
! <provides><service name="Block"/></provides>
! <config>
! <policy label_prefix="client1" writeable="yes"/>
! </config>
!</start>
Report
======
The driver supports reporting of active name spaces, which can be enabled
via the configuration 'report' sub-node:
!<report namespace="yes"/>
The report structure is depicted by the following example:
!<controller model="QEMU NVMe Ctrl" serial="FNRD">
! <namespace id="0" block_count="32768" block_size="512"/>
!</controller>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,149 @@
/*
* \brief NVMe PCIe backend
* \author Josef Soentgen
* \date 2018-03-05
*/
/*
* Copyright (C) 2018 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
#ifndef _NVME_PCI_H_
#define _NVME_PCI_H_
/* Genode includes */
#include <irq_session/connection.h>
#include <platform_device/client.h>
#include <platform_session/connection.h>
namespace Nvme {
using namespace Genode;
struct Pci;
}
struct Nvme::Pci : Platform::Connection,
Util::Dma_allocator
{
struct Missing_controller : Genode::Exception { };
enum {
CLASS_MASS_STORAGE = 0x010000u,
CLASS_MASK = 0xffff00u,
SUBCLASS_NVME = 0x000800u,
NVME_DEVICE = CLASS_MASS_STORAGE | SUBCLASS_NVME,
NVME_PCI = 0x02,
NVME_BASE_ID = 0,
};
enum Pci_config { IRQ = 0x3c, CMD = 0x4, CMD_IO = 0x1,
CMD_MEMORY = 0x2, CMD_MASTER = 0x4 };
Platform::Device::Resource _res { };
Platform::Device_capability _device_cap { };
Genode::Constructible<Platform::Device_client> _device { };
Genode::Constructible<Genode::Irq_session_client> _irq { };
/**
* Constructor
*/
Pci(Genode::Env &env) : Platform::Connection(env)
{
upgrade_ram(2*4096u);
upgrade_caps(8);
_device_cap = with_upgrade([&] () {
return next_device(_device_cap,
NVME_DEVICE, CLASS_MASK);
});
if (!_device_cap.valid()) { throw Missing_controller(); }
_device.construct(_device_cap);
_res = _device->resource(NVME_BASE_ID);
uint16_t cmd = _device->config_read(Pci_config::CMD, Platform::Device::ACCESS_16BIT);
cmd |= 0x2; /* respond to memory space accesses */
cmd |= 0x4; /* enable bus master */
_device->config_write(Pci_config::CMD, cmd, Platform::Device::ACCESS_16BIT);
_irq.construct(_device->irq(0));
Genode::log("NVMe PCIe controller found (",
Genode::Hex(_device->vendor_id()), ":",
Genode::Hex(_device->device_id()), ")");
}
/**
* Return base address of controller MMIO region
*/
addr_t base() const { return _res.base(); }
/**
* Return size of controller MMIO region
*/
size_t size() const { return _res.size(); }
/**
* Set interrupt signal handler
*
* \parm sigh signal capability
*/
void sigh_irq(Genode::Signal_context_capability sigh)
{
_irq->sigh(sigh);
_irq->ack_irq();
}
/**
* Acknowledge interrupt
*/
void ack_irq() { _irq->ack_irq(); }
/*****************************
** Dma_allocator interface **
*****************************/
/**
* Allocator DMA buffer
*
* \param size size of the buffer
*
* \return Ram_dataspace_capability
*/
Genode::Ram_dataspace_capability alloc(size_t size) override
{
size_t donate = size;
return retry<Out_of_ram>(
[&] () {
return retry<Out_of_caps>(
[&] () { return Pci::Connection::alloc_dma_buffer(size); },
[&] () { upgrade_caps(2); });
},
[&] () {
upgrade_ram(donate);
donate = donate * 2 > size ? 4096 : donate * 2;
});
}
/**
* Free DMA buffer
*
* \param cap RAM dataspace capability
*/
void free(Genode::Ram_dataspace_capability cap) override
{
Pci::Connection::free_dma_buffer(cap);
}
};
#endif /* _NVME_PCI_H_ */

View File

@ -0,0 +1,5 @@
TARGET = nvme_drv
SRC_CC = main.cc
INC_DIR += $(PRG_DIR)
LIBS += base
REQUIRES = pci

View File

@ -0,0 +1,152 @@
/*
* \brief Utilitize used by the NVMe driver
* \author Josef Soentgen
* \date 2018-03-05
*/
/*
* Copyright (C) 2018 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
#ifndef _NVME_UTIL_H_
#define _NVME_UTIL_H_
/* Genode includes */
#include <base/fixed_stdint.h>
namespace Util {
using namespace Genode;
/*
* DMA allocator helper
*/
struct Dma_allocator : Genode::Interface
{
virtual Genode::Ram_dataspace_capability alloc(size_t) = 0;
virtual void free(Genode::Ram_dataspace_capability) = 0;
};
/*
* Wrap Bit_array into a convinient Bitmap allocator
*/
template <unsigned BITS>
struct Bitmap
{
struct Full : Genode::Exception { };
static constexpr addr_t INVALID { BITS - 1 };
Genode::Bit_array<BITS> _array { };
size_t _used { 0 };
addr_t _find_free(size_t const bits)
{
for (size_t i = 0; i < BITS; i += bits) {
if (_array.get(i, bits)) { continue; }
return i;
}
throw Full();
}
/**
* Return index from where given number of bits was allocated
*
* \param bits number of bits to allocate
*
* \return index of start bit
*/
addr_t alloc(size_t const bits)
{
addr_t const start = _find_free(bits);
_array.set(start, bits);
_used += bits;
return start;
}
/**
* Free given number of bits from start index
*
* \param start index of the start bit
* \param bits number of bits to free
*/
void free(addr_t const start, size_t const bits)
{
_used -= bits;
_array.clear(start, bits);
}
};
/*
* Wrap array into convinient interface
*
* The used datatype T must implement the following methods:
*
* bool valid() const returns true if the object is valid
* void invalidate() adjusts the object so that valid() returns false
*/
template <typename T, size_t CAP>
struct Slots
{
T _entries[CAP] { };
/**
* Lookup slot
*/
template <typename FUNC>
T *lookup(FUNC const &func)
{
for (size_t i = 0; i < CAP; i++) {
if (!_entries[i].valid()) { continue; }
if ( func(_entries[i])) { return &_entries[i]; }
}
return nullptr;
}
/**
* Get free slot
*/
T *get()
{
for (size_t i = 0; i < CAP; i++) {
if (!_entries[i].valid()) { return &_entries[i]; }
}
return nullptr;
}
/**
* Iterate over all slots until FUNC returns true
*/
template <typename FUNC>
bool for_each(FUNC const &func)
{
for (size_t i = 0; i < CAP; i++) {
if (!_entries[i].valid()) { continue; }
if ( func(_entries[i])) { return true; }
}
return false;
}
};
/**
* Extract string from memory
*
* This function is used to extract the information strings from the
* identify structure.
*/
char const *extract_string(char const *base, size_t offset, size_t len)
{
static char tmp[64] = { };
if (len > sizeof(tmp)) { return nullptr; }
Genode::strncpy(tmp, base + offset, len);
len--; /* skip NUL */
while (len > 0 && tmp[--len] == ' ') { tmp[len] = 0; }
return tmp;
}
}
#endif /* _NVME_UTIL_H_ */

View File

@ -112,3 +112,4 @@ utf8
demo
ping
ping_nic_router
nvme