Loading...
Searching...
No Matches
virtio-block
// vi:ft=cpp
/* SPDX-License-Identifier: MIT */
/*
* Copyright (C) 2015-2022, 2024 Kernkonzept GmbH.
* Author(s): Sarah Hoffmann <sarah.hoffmann@kernkonzept.com>
* Manuel von Oltersdorff-Kalettka <manuel.kalettka@kernkonzept.com>
*
*/
#pragma once
#include <l4/sys/factory>
#include <l4/sys/semaphore>
#include <l4/re/dataspace>
#include <l4/re/env>
#include <l4/re/util/unique_cap>
#include <l4/re/util/object_registry>
#include <l4/re/error_helper>
#include <l4/util/atomic.h>
#include <l4/util/bitops.h>
#include <l4/l4virtio/client/l4virtio>
#include <l4/l4virtio/l4virtio>
#include <l4/l4virtio/virtqueue>
#include <l4/l4virtio/virtio_block.h>
#include <l4/sys/consts.h>
#include <cstring>
#include <vector>
#include <functional>
namespace L4virtio { namespace Driver {
class Block_device : public Device
{
public:
typedef std::function<void(unsigned char)> Callback;
private:
enum { Header_size = sizeof(l4virtio_block_header_t) };
struct Request
{
l4_uint16_t tail;
Callback callback;
Request() : tail(Virtqueue::Eoq), callback(0) {}
};
public:
class Handle
{
friend Block_device;
l4_uint16_t head;
explicit Handle(l4_uint16_t descno) : head(descno) {}
public:
Handle() : head(Virtqueue::Eoq) {}
bool valid() const { return head != Virtqueue::Eoq; }
};
void setup_device(L4::Cap<L4virtio::Device> srvcap, l4_size_t usermem,
void **userdata, Ptr<void> &user_devaddr,
L4::Cap<L4Re::Dataspace> qds = L4::Cap<L4Re::Dataspace>(),
l4_uint32_t fmask0 = -1U, l4_uint32_t fmask1 = -1U)
{
// Contact device.
driver_connect(srvcap);
if (_config->device != L4VIRTIO_ID_BLOCK)
L4Re::chksys(-L4_ENODEV, "Device is not a block device.");
if (_config->num_queues != 1)
L4Re::chksys(-L4_EINVAL, "Invalid number of queues reported.");
// Memory is shared in one large dataspace which contains queues,
// space for header/status and additional user-defined memory.
unsigned queuesz = max_queue_size(0);
l4_size_t totalsz = l4_round_page(usermem);
l4_uint64_t const header_offset =
l4_round_size(_queue.total_size(queuesz),
l4util_bsr(alignof(l4virtio_block_header_t)));
l4_uint64_t const status_offset = header_offset + queuesz * Header_size;
l4_uint64_t const usermem_offset = l4_round_page(status_offset + queuesz);
// reserve space for one header/status per descriptor
// TODO Should be reduced to 1/3 but this way no freelist is needed.
totalsz += usermem_offset;
auto *e = L4Re::Env::env();
if (!qds.is_valid())
{
_ds = L4Re::chkcap(L4Re::Util::make_unique_cap<L4Re::Dataspace>(),
"Allocate queue dataspace capability");
L4Re::chksys(e->mem_alloc()->alloc(totalsz, _ds.get(),
L4Re::Mem_alloc::Continuous
| L4Re::Mem_alloc::Pinned),
"Allocate memory for virtio structures");
_queue_ds = _ds.get();
}
else
{
if (qds->size() < totalsz)
L4Re::chksys(-L4_EINVAL, "External queue dataspace too small.");
_queue_ds = qds;
}
// Now sort out which region goes where in the dataspace.
L4Re::chksys(e->rm()->attach(&_queue_region, totalsz,
L4Re::Rm::F::Search_addr | L4Re::Rm::F::RW,
L4::Ipc::make_cap_rw(_queue_ds), 0,
L4_PAGESHIFT),
"Attach dataspace for virtio structures");
l4_uint64_t devaddr;
L4Re::chksys(register_ds(_queue_ds, 0, totalsz, &devaddr),
"Register queue dataspace with device");
_queue.init_queue(queuesz, _queue_region.get());
config_queue(0, queuesz, devaddr, devaddr + _queue.avail_offset(),
devaddr + _queue.used_offset());
_header_addr = devaddr + header_offset;
_headers = reinterpret_cast< l4virtio_block_header_t *>(_queue_region.get()
+ header_offset);
_status_addr = devaddr + status_offset;
_status = _queue_region.get() + status_offset;
user_devaddr = Ptr<void>(devaddr + usermem_offset);
if (userdata)
*userdata = _queue_region.get() + usermem_offset;
// setup the callback mechanism
_pending.assign(queuesz, Request());
// Finish handshake with device.
_config->driver_features_map[0] = fmask0;
_config->driver_features_map[1] = fmask1;
driver_acknowledge();
}
l4virtio_block_config_t const &device_config() const
{
return *_config->device_config<l4virtio_block_config_t>();
}
Handle start_request(l4_uint64_t sector, l4_uint32_t type,
Callback callback)
{
l4_uint16_t descno = _queue.alloc_descriptor();
if (descno == Virtqueue::Eoq)
return Handle(Virtqueue::Eoq);
L4virtio::Virtqueue::Desc &desc = _queue.desc(descno);
Request &req = _pending[descno];
// setup the header
l4virtio_block_header_t &head = _headers[descno];
head.type = type;
head.ioprio = 0;
head.sector = sector;
// and put it in the descriptor
desc.addr = Ptr<void>(_header_addr + descno * Header_size);
desc.len = Header_size;
desc.flags.raw = 0; // no write, no indirect
req.tail = descno;
req.callback = callback;
return Handle(descno);
}
int add_block(Handle handle, Ptr<void> addr, l4_uint32_t size)
{
l4_uint16_t descno = _queue.alloc_descriptor();
if (descno == Virtqueue::Eoq)
return -L4_EAGAIN;
Request &req = _pending[handle.head];
L4virtio::Virtqueue::Desc &desc = _queue.desc(descno);
L4virtio::Virtqueue::Desc &prev = _queue.desc(req.tail);
prev.next = descno;
prev.flags.next() = true;
desc.addr = addr;
desc.len = size;
desc.flags.raw = 0;
if (_headers[handle.head].type > 0) // write or flush request
desc.flags.write() = true;
req.tail = descno;
return L4_EOK;
}
int send_request(Handle handle)
{
// add the status bit
auto descno = _queue.alloc_descriptor();
if (descno == Virtqueue::Eoq)
return -L4_EAGAIN;
Request &req = _pending[handle.head];
L4virtio::Virtqueue::Desc &desc = _queue.desc(descno);
L4virtio::Virtqueue::Desc &prev = _queue.desc(req.tail);
prev.next = descno;
prev.flags.next() = true;
desc.addr = Ptr<void>(_status_addr + descno);
desc.len = 1;
desc.flags.raw = 0;
desc.flags.write() = true;
req.tail = descno;
send(_queue, handle.head);
return L4_EOK;
}
int process_request(Handle handle)
{
// add the status bit
auto descno = _queue.alloc_descriptor();
if (descno == Virtqueue::Eoq)
return -L4_EAGAIN;
L4virtio::Virtqueue::Desc &desc = _queue.desc(descno);
L4virtio::Virtqueue::Desc &prev = _queue.desc(_pending[handle.head].tail);
prev.next = descno;
prev.flags.next() = true;
desc.addr = Ptr<void>(_status_addr + descno);
desc.len = 1;
desc.flags.raw = 0;
desc.flags.write() = true;
_pending[handle.head].tail = descno;
int ret = send_and_wait(_queue, handle.head);
unsigned char status = _status[descno];
free_request(handle);
if (ret < 0)
return ret;
switch (status)
{
case L4VIRTIO_BLOCK_S_OK: return L4_EOK;
case L4VIRTIO_BLOCK_S_IOERR: return -L4_EIO;
case L4VIRTIO_BLOCK_S_UNSUPP: return -L4_ENOSYS;
}
return -L4_EINVAL;
}
void free_request(Handle handle)
{
if (handle.head != Virtqueue::Eoq
&& _pending[handle.head].tail != Virtqueue::Eoq)
_queue.free_descriptor(handle.head, _pending[handle.head].tail);
_pending[handle.head].tail = Virtqueue::Eoq;
}
void process_used_queue()
{
for (l4_uint16_t descno = _queue.find_next_used();
descno != Virtqueue::Eoq;
descno = _queue.find_next_used()
)
{
if (descno >= _queue.num() || _pending[descno].tail == Virtqueue::Eoq)
L4Re::chksys(-L4_ENOSYS, "Bad descriptor number");
unsigned char status = _status[descno];
free_request(Handle(descno));
if (_pending[descno].callback)
_pending[descno].callback(status);
}
}
protected:
L4Re::Util::Unique_cap<L4Re::Dataspace> _ds;
L4::Cap<L4Re::Dataspace> _queue_ds;
private:
L4Re::Rm::Unique_region<unsigned char *> _queue_region;
l4virtio_block_header_t *_headers;
unsigned char *_status;
l4_uint64_t _header_addr;
l4_uint64_t _status_addr;
Virtqueue _queue;
std::vector<Request> _pending;
};
} }
bitops.h
bit manipulation functions
L4Re::Env::env
static Env const * env() noexcept
Returns the initial environment for the current task.
Definition env:96
L4Re::Mem_alloc::Continuous
@ Continuous
Allocate physically contiguous memory.
Definition mem_alloc:64
L4Re::Mem_alloc::Pinned
@ Pinned
Deprecated, use L4Re::Dma_space instead.
Definition mem_alloc:65
L4Re::Rm::Unique_region
Unique region.
Definition rm:436
L4::Cap
C++ interface for capabilities.
Definition capability.h:224
L4virtio::Driver::Block_device::Handle
Handle to an ongoing request.
Definition virtio-block:57
L4virtio::Driver::Block_device
Simple class for accessing a virtio block device synchronously.
Definition virtio-block:37
L4virtio::Driver::Block_device::send_request
int send_request(Handle handle)
Process request asynchronously.
Definition virtio-block:265
L4virtio::Driver::Block_device::add_block
int add_block(Handle handle, Ptr< void > addr, l4_uint32_t size)
Add a data block to a request that has already been set up.
Definition virtio-block:229
L4virtio::Driver::Block_device::process_request
int process_request(Handle handle)
Process request synchronously.
Definition virtio-block:306
L4virtio::Driver::Block_device::device_config
l4virtio_block_config_t const & device_config() const
Return a reference to the device configuration.
Definition virtio-block:178
L4virtio::Driver::Block_device::process_used_queue
void process_used_queue()
Process and free all items in the used queue.
Definition virtio-block:357
L4virtio::Driver::Block_device::setup_device
void setup_device(L4::Cap< L4virtio::Device > srvcap, l4_size_t usermem, void **userdata, Ptr< void > &user_devaddr, L4::Cap< L4Re::Dataspace > qds=L4::Cap< L4Re::Dataspace >(), l4_uint32_t fmask0=-1U, l4_uint32_t fmask1=-1U)
Establish a connection to the device and set up shared memory.
Definition virtio-block:92
L4virtio::Driver::Block_device::start_request
Handle start_request(l4_uint64_t sector, l4_uint32_t type, Callback callback)
Start the setup of a new request.
Definition virtio-block:191
L4virtio::Driver::Device
Client-side implementation for a general virtio device.
Definition l4virtio:32
L4virtio::Driver::Device::max_queue_size
int max_queue_size(int num) const
Maximum queue size allowed by the device.
Definition l4virtio:230
L4virtio::Driver::Device::driver_connect
void driver_connect(L4::Cap< L4virtio::Device > srvcap, bool manage_notify=true)
Contacts the device and starts the initial handshake.
Definition l4virtio:56
L4virtio::Driver::Device::send
void send(Virtqueue &queue, l4_uint16_t descno)
Send a request to the device.
Definition l4virtio:312
L4virtio::Driver::Device::register_ds
int register_ds(L4::Cap< L4Re::Dataspace > ds, l4_umword_t offset, l4_umword_t size, l4_uint64_t *devaddr)
Share a dataspace with the device.
Definition l4virtio:196
L4virtio::Driver::Device::config_queue
int config_queue(int num, unsigned size, l4_uint64_t desc_addr, l4_uint64_t avail_addr, l4_uint64_t used_addr)
Send the virtqueue configuration to the device.
Definition l4virtio:212
L4virtio::Driver::Device::driver_acknowledge
int driver_acknowledge()
Finalize handshake with the device.
Definition l4virtio:156
L4virtio::Driver::Device::send_and_wait
int send_and_wait(Virtqueue &queue, l4_uint16_t descno)
Send a request to the device and wait for it to be processed.
Definition l4virtio:247
L4virtio::Driver::Virtqueue
Driver-side implementation of a Virtqueue.
Definition virtqueue:471
L4virtio::Driver::Virtqueue::free_descriptor
void free_descriptor(l4_uint16_t head, l4_uint16_t tail)
Free a chained list of descriptors in the descriptor queue.
Definition virtqueue:631
L4virtio::Ptr
Pointer used in virtio descriptors.
Definition virtqueue:48
L4virtio::Virtqueue::Desc
Descriptor in the descriptor table.
Definition virtqueue:87
L4virtio::Virtqueue::Desc::next
l4_uint16_t next
Index of the next chained descriptor.
Definition virtqueue:111
L4virtio::Virtqueue::Desc::len
l4_uint32_t len
Length of described buffer.
Definition virtqueue:109
L4virtio::Virtqueue::Desc::flags
Flags flags
Descriptor flags.
Definition virtqueue:110
L4virtio::Virtqueue::Desc::addr
Ptr< void > addr
Address stored in descriptor.
Definition virtqueue:108
dataspace
Dataspace interface.
env
Environment interface.
error_helper
Error helper.
factory
Common factory related definitions.
l4_size_t
unsigned int l4_size_t
Unsigned size type.
Definition l4int.h:24
l4_uint32_t
unsigned int l4_uint32_t
Unsigned 32bit value.
Definition l4int.h:29
l4_uint16_t
unsigned short int l4_uint16_t
Unsigned 16bit value.
Definition l4int.h:27
l4_uint64_t
unsigned long long l4_uint64_t
Unsigned 64bit value.
Definition l4int.h:31
L4_ENOSYS
@ L4_ENOSYS
No sys.
Definition err.h:50
L4_EINVAL
@ L4_EINVAL
Invalid argument.
Definition err.h:46
L4_ENODEV
@ L4_ENODEV
No such thing.
Definition err.h:44
L4_EIO
@ L4_EIO
I/O error.
Definition err.h:35
L4_EOK
@ L4_EOK
Ok.
Definition err.h:32
L4_EAGAIN
@ L4_EAGAIN
Try again.
Definition err.h:38
l4_round_page
l4_addr_t l4_round_page(l4_addr_t address) L4_NOTHROW
Round address up to the next page.
Definition consts.h:473
L4_PAGESHIFT
#define L4_PAGESHIFT
Size of a page, log2-based.
Definition consts.h:26
l4_round_size
l4_addr_t l4_round_size(l4_addr_t value, unsigned char bits) L4_NOTHROW
Round value up to the next alignment with bits size.
Definition consts.h:484
L4VIRTIO_BLOCK_S_IOERR
@ L4VIRTIO_BLOCK_S_IOERR
IO error on device.
Definition virtio_block.h:35
L4VIRTIO_BLOCK_S_UNSUPP
@ L4VIRTIO_BLOCK_S_UNSUPP
Operation is not supported.
Definition virtio_block.h:36
L4VIRTIO_BLOCK_S_OK
@ L4VIRTIO_BLOCK_S_OK
Request finished successfully.
Definition virtio_block.h:34
L4VIRTIO_ID_BLOCK
@ L4VIRTIO_ID_BLOCK
General block device.
Definition virtio.h:65
consts.h
Common constants.
atomic.h
atomic operations header and generic implementations
L4Re::Util::make_unique_cap
Unique_cap< T > make_unique_cap()
Allocate a capability slot and wrap it in an Unique_cap.
Definition unique_cap:55
L4Re::Util::Unique_cap
L4::Detail::Unique_cap_impl< T, Smart_cap_auto< L4_FP_ALL_SPACES > > Unique_cap
Unique capability that implements automatic free and unmap of the capability selector.
Definition unique_cap:43
L4Re::chksys
long chksys(long err, char const *extra="", long ret=0)
Generate C++ exception on error.
Definition error_helper:72
L4Re::chkcap
T chkcap(T &&cap, char const *extra="", long err=-L4_ENOMEM)
Check for valid capability or raise C++ exception.
Definition error_helper:149
L4::Ipc::make_cap_rw
Cap< T > make_cap_rw(L4::Cap< T > cap) noexcept
Make an L4::Ipc::Cap<T> for the given capability with L4_CAP_FPAGE_RW rights.
Definition ipc_types:795
L4virtio
L4-VIRTIO Transport C++ API.
Definition l4virtio:26
semaphore
Semaphore class definition.
L4Re::Rm::F::RW
@ RW
Readable and writable region.
Definition rm:139
L4Re::Rm::F::Search_addr
@ Search_addr
Search for a suitable address range.
Definition rm:114
L4virtio::Virtqueue::Desc::Flags::next
constexpr next_bfm_t::Val next() const
Get the next bits (0 to 0) of raw.
Definition virtqueue:101
L4virtio::Virtqueue::Desc::Flags::write
constexpr write_bfm_t::Val write() const
Get the write bits (1 to 1) of raw.
Definition virtqueue:103
L4virtio::Virtqueue::Desc::Flags::raw
l4_uint16_t raw
raw flags value of a virtio descriptor.
Definition virtqueue:94
l4virtio_block_config_t
Device configuration for block devices.
Definition virtio_block.h:69
l4virtio_block_header_t
Header structure of a request for a block device.
Definition virtio_block.h:43
l4virtio_block_header_t::type
l4_uint32_t type
Kind of request, see L4virtio_block_operations.
Definition virtio_block.h:44
l4virtio_block_header_t::ioprio
l4_uint32_t ioprio
Priority (unused).
Definition virtio_block.h:45
l4virtio_block_header_t::sector
l4_uint64_t sector
First sector to read/write.
Definition virtio_block.h:46
unique_cap
Unique_cap / Unique_del_cap.