mirror of
https://github.com/genodelabs/genode.git
synced 2025-02-20 09:46:20 +00:00
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:
parent
372e426ec7
commit
04516a0d39
2
repos/os/recipes/src/nvme_drv/content.mk
Normal file
2
repos/os/recipes/src/nvme_drv/content.mk
Normal file
@ -0,0 +1,2 @@
|
||||
SRC_DIR = src/drivers/nvme
|
||||
include $(GENODE_DIR)/repos/base/recipes/src/content.inc
|
1
repos/os/recipes/src/nvme_drv/hash
Normal file
1
repos/os/recipes/src/nvme_drv/hash
Normal file
@ -0,0 +1 @@
|
||||
2018-03-27 fcf9749c441d830aa4666f70e04cd1560c783b2f
|
6
repos/os/recipes/src/nvme_drv/used_apis
Normal file
6
repos/os/recipes/src/nvme_drv/used_apis
Normal file
@ -0,0 +1,6 @@
|
||||
base
|
||||
os
|
||||
platform_session
|
||||
block_session
|
||||
report_session
|
||||
timer_session
|
189
repos/os/run/nvme.run
Normal file
189
repos/os/run/nvme.run
Normal 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
|
40
repos/os/src/drivers/nvme/README
Normal file
40
repos/os/src/drivers/nvme/README
Normal 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>
|
1752
repos/os/src/drivers/nvme/main.cc
Normal file
1752
repos/os/src/drivers/nvme/main.cc
Normal file
File diff suppressed because it is too large
Load Diff
149
repos/os/src/drivers/nvme/pci.h
Normal file
149
repos/os/src/drivers/nvme/pci.h
Normal 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_ */
|
5
repos/os/src/drivers/nvme/target.mk
Normal file
5
repos/os/src/drivers/nvme/target.mk
Normal file
@ -0,0 +1,5 @@
|
||||
TARGET = nvme_drv
|
||||
SRC_CC = main.cc
|
||||
INC_DIR += $(PRG_DIR)
|
||||
LIBS += base
|
||||
REQUIRES = pci
|
152
repos/os/src/drivers/nvme/util.h
Normal file
152
repos/os/src/drivers/nvme/util.h
Normal 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_ */
|
@ -112,3 +112,4 @@ utf8
|
||||
demo
|
||||
ping
|
||||
ping_nic_router
|
||||
nvme
|
||||
|
Loading…
x
Reference in New Issue
Block a user