nvme_drv: add support for host-memory-buffer

This commit introduces support for the HMB feature and will setup the
buffer during start-up. The host-memory-buffer (HMB) feature is mostly
used on NVMe devices that do not make use of an DRAM cache to store its
translation tables amongst other operational data. Not using HMB can
impair the performance on such devices.

The memory is allocated in 2 MiB chunks of DMA-capable memory and its
total size in bytes is configurable via the 'hmb_size' config attribute.
The driver always checks the minimal and preferred size of the HMB and
issues a warning in case it is not enabled via the configuration.
Moreover, if the configured size is less than the minimal amount
required by the device the HMB is not configured at all and a warning
is issued also. If the configured size is more than the preferred size
it will be capped to that amount.

Fixes #4715.
This commit is contained in:
Josef Söntgen 2022-11-13 20:10:43 +00:00 committed by Christian Helmuth
parent 239d4864e9
commit ae0e0c118e
3 changed files with 255 additions and 5 deletions

View File

@ -151,10 +151,10 @@ append config {
</config>
</start>
<start name="nvme_drv">
<resource name="RAM" quantum="8M"/>
<start name="nvme_drv" caps="120">
<resource name="RAM" quantum="24M"/>
<provides> <service name="Block"/> </provides>
<config>
<config max_hmb_size="16M" verbose_regs="yes" verbose_identify="yes">
<policy label_prefix="block_tester" writeable="} [writeable] {"/>
</config>
<route>

View File

@ -17,13 +17,20 @@ Configuration
The following config illustrates how the driver is configured:
!<start name="nvme_drv">
! <resource name="ram" quantum="8M"/>
! <resource name="ram" quantum="24M"/>
! <provides><service name="Block"/></provides>
! <config>
! <config max_hmb_size="16M">
! <policy label_prefix="client1" writeable="yes"/>
! </config>
!</start>
The 'max_hmb_size' attribute instructs the driver to setup the
host-memory-buffer with at most 16 MiB of DMA-capable memory if such a
buffer is needed by the device. Should the value be less than the minimal
required amount of memory it will not be used and a warning is issued. On
the other hand, if the specified value is larger than the preferred amount
of memory as favored by the device it will be capped to that amount instead.
Report
======

View File

@ -20,6 +20,7 @@
#include <base/component.h>
#include <base/heap.h>
#include <base/log.h>
#include <base/registry.h>
#include <block/request_stream.h>
#include <dataspace/client.h>
#include <os/attached_mmio.h>
@ -70,6 +71,8 @@ namespace Nvme {
struct Sqe_set_feature;
struct Sqe_io;
struct Set_hmb;
struct Queue;
struct Sq;
struct Cq;
@ -96,6 +99,17 @@ namespace Nvme {
MAX_ADMIN_ENTRIES_MASK = MAX_ADMIN_ENTRIES - 1,
MPS_LOG2 = 12u,
MPS = 1u << MPS_LOG2,
/*
* Setup the descriptor list in one page and use a chunk
* size that covers the common amount of HMB well and
* requires resonable sized mappings.
*/
HMB_LIST_SIZE = 4096,
HMB_LIST_ENTRY_SIZE = 16,
HMB_LIST_MAX_ENTRIES = HMB_LIST_SIZE / HMB_LIST_ENTRY_SIZE,
HMB_CHUNK_SIZE = (2u << 20),
HMB_CHUNK_UNITS = HMB_CHUNK_SIZE / MPS,
};
enum {
@ -135,6 +149,10 @@ namespace Nvme {
WRITE_ZEROS = 0x08,
};
enum Feature_fid {
HMB = 0x0d,
};
enum Feature_sel {
CURRENT = 0b000,
DEFAULT = 0b001,
@ -180,6 +198,11 @@ struct Nvme::Identify_data : Genode::Mmio
struct Nsm : Bitfield< 3, 1> { }; /* namespace management */
struct Vm : Bitfield< 7, 1> { }; /* virtualization management */
};
/* optional host memory buffer */
struct Hmpre : Register<0x110, 32> { }; /* preferred size */
struct Hmmin : Register<0x114, 32> { }; /* minimum size */
struct Nn : Register<0x204, 32> { }; /* number of namespaces */
struct Vwc : Register<0x204, 8> { }; /* volatile write cache */
@ -371,6 +394,65 @@ struct Nvme::Sqe_set_feature : Nvme::Sqe
};
struct Hmb_de : Genode::Mmio
{
enum { SIZE = 16u };
struct Badd : Register<0x00, 64> { };
struct Bsize : Register<0x08, 64> { };
Hmb_de(addr_t const base, addr_t const buffer, size_t units) : Genode::Mmio(base)
{
write<Badd>(buffer);
write<Bsize>(units);
}
};
struct Nvme::Set_hmb : Nvme::Sqe_set_feature
{
struct Cdw11 : Register<0x2c, 32>
{
struct Ehm : Bitfield< 0, 1> { }; /* enable host memory buffer */
struct Mr : Bitfield< 1, 1> { }; /* memory return */
};
struct Cdw12 : Register<0x30, 32>
{
struct Hsize : Bitfield<0, 32> { }; /* host memory buffer size (in MPS units) */
};
struct Cdw13 : Register<0x34, 32>
{
/* bits 3:0 should be zero */
struct Hmdlla : Bitfield<0, 32> { }; /* host memory descriptor list lower address */
};
struct Cdw14 : Register<0x38, 32>
{
struct Hmdlua : Bitfield<0, 32> { }; /* host memory descriptor list upper address */
};
struct Cdw15 : Register<0x3c, 32>
{
struct Hmdlec : Bitfield<0, 32> { }; /* host memory descriptor list entry count */
};
Set_hmb(addr_t const base, uint64_t const hmdl,
size_t const units, uint32_t const entries)
:
Sqe_set_feature(base)
{
write<Sqe_set_feature::Cdw10::Fid>(Feature_fid::HMB);
write<Cdw11::Ehm>(1);
write<Cdw12::Hsize>(units);
write<Cdw13::Hmdlla>(hmdl);
write<Cdw14::Hmdlua>(hmdl >> 32u);
write<Cdw15::Hmdlec>(entries);
}
};
/*
* Create completion queue command
*/
@ -700,6 +782,9 @@ class Nvme::Controller : Platform::Device,
Identify_data::Mn mn { };
Identify_data::Fr fr { };
size_t mdts { };
uint32_t hmpre { };
uint32_t hmmin { };
};
struct Nsinfo
@ -756,10 +841,45 @@ class Nvme::Controller : Platform::Device,
QUERYNS_CID,
CREATE_IO_CQ_CID,
CREATE_IO_SQ_CID,
SET_HMB_CID,
};
Constructible<Platform::Dma_buffer> _nvme_query_ns[MAX_NS] { };
struct Hmb_chunk
{
Registry<Hmb_chunk>::Element _elem;
Platform::Dma_buffer dma_buffer;
Hmb_chunk(Registry<Hmb_chunk> &registry,
Platform::Connection &platform, size_t size)
:
_elem { registry, *this },
dma_buffer { platform, size, UNCACHED }
{ }
virtual ~Hmb_chunk() { }
};
struct Hmb_chunk_registry : Registry<Hmb_chunk>
{
Allocator &_alloc;
Hmb_chunk_registry(Allocator &alloc)
: _alloc { alloc } { }
~Hmb_chunk_registry()
{
for_each([&] (Hmb_chunk &c) {
destroy(_alloc, &c); });
}
};
Heap _hmb_alloc { _env.ram(), _env.rm() };
Constructible<Hmb_chunk_registry> _hmb_chunk_registry { };
Constructible<Platform::Dma_buffer> _hmb_descr_list_buffer { };
Info _info { };
/* create larger array to use namespace id to as index */
@ -1033,6 +1153,9 @@ class Nvme::Controller : Platform::Device,
_info.mn = _identify_data->mn;
_info.fr = _identify_data->fr;
_info.hmpre = _identify_data->read<Identify_data::Hmpre>();
_info.hmmin = _identify_data->read<Identify_data::Hmmin>();
/* limit maximum I/O request length */
uint8_t const mdts = _identify_data->read<Identify_data::Mdts>();
_mdts_bytes = !mdts ? (size_t)Nvme::MAX_IO_LEN
@ -1046,6 +1169,106 @@ class Nvme::Controller : Platform::Device,
_max_io_entries_mask = _max_io_entries - 1;
}
/*
* Check units match at least hmmin and limit to hmpre or the
* amount of memory we can cover with our list and chunk size.
*/
uint32_t _check_hmb_units(uint32_t units)
{
if (!units) {
if (_info.hmpre)
warning("HMB support available but not configured");
return 0;
}
units = align_addr(units, log2((unsigned)HMB_CHUNK_UNITS));
if (units < _info.hmmin) {
warning("HMB will not be enabled as configured size of ",
Number_of_bytes(units * Nvme::MPS),
" is less than minimal required amount of ",
Number_of_bytes(_info.hmmin * Nvme::MPS));
return 0;
}
if (units > _info.hmpre)
units = _info.hmpre;
uint32_t const max_units = HMB_LIST_MAX_ENTRIES * HMB_CHUNK_UNITS;
if (units > max_units)
units = max_units;
if (units < _info.hmpre)
warning("HMB size of ",
Number_of_bytes(units * Nvme::MPS),
" is less than preferred amount of ",
Number_of_bytes(_info.hmpre * Nvme::MPS));
return units;
}
/**
* Setup host-memory-buffer
*
* \param size size of the HMB in bytes
*/
void _setup_hmb(size_t size)
{
uint32_t units = _check_hmb_units(size / Nvme::MPS);
if (!units)
return;
size_t const bytes = units * Nvme::MPS;
uint32_t const num_entries = bytes / HMB_CHUNK_SIZE;
try {
_hmb_descr_list_buffer.construct(_platform, HMB_LIST_SIZE, UNCACHED);
} catch (... /* intentional catch-all */) {
warning("could not allocate HMB descriptor list page");
return;
}
_hmb_chunk_registry.construct(_hmb_alloc);
addr_t list_base =
(addr_t)_hmb_descr_list_buffer->local_addr<addr_t>();
for (uint32_t i = 0; i < num_entries; i++) {
try {
Hmb_chunk *c =
new (_hmb_alloc) Hmb_chunk(*_hmb_chunk_registry,
_platform, HMB_CHUNK_SIZE);
Hmb_de e(list_base, c->dma_buffer.dma_addr(), HMB_CHUNK_UNITS);
list_base += Hmb_de::SIZE;
} catch (... /* intentional catch-all */) {
warning("could not allocate HMB chunk");
/* if one allocation fails we bail entirely */
_hmb_chunk_registry.destruct();
_hmb_descr_list_buffer.destruct();
return;
}
}
Set_hmb b(_admin_command(Opcode::SET_FEATURES, 0, SET_HMB_CID),
_hmb_descr_list_buffer->dma_addr(), units, num_entries);
write<Admin_sdb::Sqt>(_admin_sq->tail);
if (!_wait_for_admin_cq(10, SET_HMB_CID)) {
warning("could not enable HMB");
_hmb_chunk_registry.destruct();
_hmb_descr_list_buffer.destruct();
return;
}
log("HMB enabled with ", Number_of_bytes(bytes), " in ",
num_entries, " chunks of ", Number_of_bytes(HMB_CHUNK_SIZE));
}
/**
* Setup I/O completion queue
*
@ -1172,6 +1395,14 @@ class Nvme::Controller : Platform::Device,
_query_ns();
}
/**
* Setup HMB
*/
void setup_hmb(size_t bytes)
{
_setup_hmb(bytes);
}
/**
* Setup I/O queue
*/
@ -1334,6 +1565,8 @@ class Nvme::Controller : Platform::Device,
log("nn:", _identify_data->read<Identify_data::Nn>());
log("vwc:", _identify_data->read<Identify_data::Vwc>());
log("mdts:", _identify_data->read<Identify_data::Mdts>());
log("hmpre:", _identify_data->read<Identify_data::Hmpre>());
log("hmmin:", _identify_data->read<Identify_data::Hmmin>());
}
void dump_nslist()
@ -1391,6 +1624,8 @@ class Nvme::Driver : Genode::Noncopyable
bool _verbose_mem { false };
bool _verbose_regs { false };
size_t _hmb_size { 0 };
struct Io_error : Genode::Exception { };
struct Request_congestion : Genode::Exception { };
@ -1416,6 +1651,8 @@ class Nvme::Driver : Genode::Noncopyable
_verbose_io = config.attribute_value("verbose_io", _verbose_io);
_verbose_mem = config.attribute_value("verbose_mem", _verbose_mem);
_verbose_regs = config.attribute_value("verbose_regs", _verbose_regs);
_hmb_size = config.attribute_value("max_hmb_size", Genode::Number_of_bytes(0));
}
Genode::Signal_handler<Driver> _config_sigh {
@ -1570,6 +1807,12 @@ class Nvme::Driver : Genode::Noncopyable
_nvme_ctrlr.dump_nslist();
}
/*
* Setup HMB
*/
if (_nvme_ctrlr.info().hmpre)
_nvme_ctrlr.setup_hmb(_hmb_size);
/*
* Setup I/O
*/