L4Re Operating System Framework
Interface and Usage Documentation
Loading...
Searching...
No Matches
virtio-block
1// vi:ft=cpp
2/* SPDX-License-Identifier: MIT */
3/*
4 * Copyright (C) 2017-2021, 2024-2025 Kernkonzept GmbH.
5 * Author(s): Sarah Hoffmann <sarah.hoffmann@kernkonzept.com>
6 *
7 */
8#pragma once
9
10#include <l4/cxx/unique_ptr>
11#include <l4/re/util/unique_cap>
12
13#include <climits>
14
15#include <l4/l4virtio/virtio.h>
16#include <l4/l4virtio/virtio_block.h>
17#include <l4/l4virtio/server/l4virtio>
18#include <l4/sys/cxx/ipc_epiface>
19
20namespace L4virtio { namespace Svr {
21
22template <typename Ds_data> class Block_dev_base;
23
27template<typename Ds_data>
28class Block_request
29{
30 friend class Block_dev_base<Ds_data>;
31 enum { Header_size = sizeof(l4virtio_block_header_t) };
32
33public:
34 struct Data_block
35 {
39 void *addr;
41 l4_uint32_t len;
42
43 Data_block() = default;
44
45 Data_block(Driver_mem_region_t<Ds_data> *m, Virtqueue::Desc const &desc,
46 Request_processor const *)
47 : mem(m), addr(m->local(desc.addr)), len(desc.len)
48 {}
49 };
50
51
52
63 unsigned data_size() const
64 {
66 Data_block data;
67 unsigned total;
68
69 try
70 {
71 rp.start(_mem_list, _request, &data);
72
73 total = data.len;
74
75 while (rp.has_more())
76 {
77 rp.next(_mem_list, &data);
78 total += data.len;
79 }
80 }
81 catch (Bad_descriptor const &e)
82 {
83 // need to convert the exception because e contains a raw pointer to rp
84 throw L4::Runtime_error(-L4_EIO, "bad virtio descriptor");
85 }
86
87 if (total < Header_size + 1)
88 throw L4::Runtime_error(-L4_EIO, "virtio request too short");
89
90 return total - Header_size - 1;
91 }
92
96 bool has_more()
97 {
98 // peek into the remaining data
99 while (_data.len == 0 && _rp.has_more())
100 _rp.next(_mem_list, &_data);
101
102 // there always must be one byte left for status
103 return (_data.len > 1 || _rp.has_more());
104 }
105
114 Data_block next_block()
115 {
116 Data_block out;
117
118 if (_data.len == 0)
119 {
120 if (!_rp.has_more())
122 "No more data blocks in virtio request");
123
124 if (_todo_blocks == 0)
126 --_todo_blocks;
127
128 _rp.next(_mem_list, &_data);
129 }
130
131 if (_data.len > _max_block_size)
133
134 out = _data;
135
136 if (!_rp.has_more())
137 {
138 --(out.len);
139 _data.len = 1;
140 _data.addr = static_cast<char *>(_data.addr) + out.len;
141 }
142 else
143 _data.len = 0; // is consumed
144
145 return out;
146 }
147
150 { return _header; }
151
152private:
153 Block_request(Virtqueue::Request req, Driver_mem_list_t<Ds_data> *mem_list,
154 unsigned max_blocks, l4_uint32_t max_block_size)
155 : _mem_list(mem_list),
156 _request(req),
157 _todo_blocks(max_blocks),
158 _max_block_size(max_block_size)
159 {
160 // read header which should be in the first block
161 _rp.start(mem_list, _request, &_data);
162 --_todo_blocks;
163
164 if (_data.len < Header_size)
166
167 _header = *(static_cast<l4virtio_block_header_t *>(_data.addr));
168
169 _data.addr = static_cast<char *>(_data.addr) + Header_size;
170 _data.len -= Header_size;
171
172 // if there is no space for status bit we cannot really recover
173 if (!_rp.has_more() && _data.len == 0)
175 }
176
177 int release_request(Virtqueue *queue, l4_uint8_t status, unsigned sz)
178 {
179 // write back status
180 // If there was an error on the way or the status byte is in its
181 // own block, fast-forward to the last block.
182 if (_rp.has_more())
183 {
184 try
185 {
186 while (_rp.next(_mem_list, &_data) && _todo_blocks > 0)
187 --_todo_blocks;
188 }
189 catch (Bad_descriptor const &)
190 {
191 // there was a bad descriptor on the path to the last block
192 return -L4_EIO;
193 }
194
195 if (_todo_blocks > 0 && _data.len > 0)
196 *(static_cast<l4_uint8_t *>(_data.addr) + _data.len - 1) = status;
197 else
198 return -L4_EIO; // too many data blocks
199 }
200 else if (_data.len > 0)
201 *(static_cast<l4_uint8_t *>(_data.addr)) = status;
202 else
203 return -L4_EIO; // no space for final status byte
204
205 // now release the head
206 queue->consumed(_request, sz);
207
208 return L4_EOK;
209 }
210
216 Driver_mem_list_t<Ds_data> *_mem_list;
220 Request_processor _rp;
222 Data_block _data;
223
225 Virtqueue::Request _request;
227 unsigned _todo_blocks;
229 l4_uint32_t _max_block_size;
230};
231
232struct Block_features : public Dev_config::Features
233{
234 Block_features() = default;
235 Block_features(l4_uint32_t raw) : Dev_config::Features(raw) {}
236
238 CXX_BITFIELD_MEMBER( 1, 1, size_max, raw);
240 CXX_BITFIELD_MEMBER( 2, 2, seg_max, raw);
242 CXX_BITFIELD_MEMBER( 4, 4, geometry, raw);
244 CXX_BITFIELD_MEMBER( 5, 5, ro, raw);
246 CXX_BITFIELD_MEMBER( 6, 6, blk_size, raw);
248 CXX_BITFIELD_MEMBER( 9, 9, flush, raw);
250 CXX_BITFIELD_MEMBER(10, 10, topology, raw);
252 CXX_BITFIELD_MEMBER(11, 11, config_wce, raw);
254 CXX_BITFIELD_MEMBER(12, 12, mq, raw);
256 CXX_BITFIELD_MEMBER(13, 13, discard, raw);
258 CXX_BITFIELD_MEMBER(14, 14, write_zeroes, raw);
259};
260
261
267template <typename Ds_data>
269{
270private:
271 L4Re::Util::Unique_cap<L4::Irq> _kick_guest_irq;
272 Virtqueue _queue;
273 unsigned _vq_max;
274 l4_uint32_t _max_block_size = UINT_MAX;
275 Dev_config_t<l4virtio_block_config_t> _dev_config;
276
277public:
278 typedef Block_request<Ds_data> Request;
279
280protected:
281 Block_features negotiated_features() const
282 { return _dev_config.negotiated_features(0); }
283
284 Block_features device_features() const
285 { return _dev_config.host_features(0); }
286
287 void set_device_features(Block_features df)
288 { _dev_config.host_features(0) = df.raw; }
289
300 {
301 _dev_config.priv_config()->size_max = sz;
302 Block_features df = device_features();
303 df.size_max() = true;
304 set_device_features(df);
305
306 _max_block_size = sz;
307 }
308
314 {
315 _dev_config.priv_config()->seg_max = sz;
316 Block_features df = device_features();
317 df.seg_max() = true;
318 set_device_features(df);
319 }
320
324 void set_geometry(l4_uint16_t cylinders, l4_uint8_t heads, l4_uint8_t sectors)
325 {
326 l4virtio_block_config_t volatile *pc = _dev_config.priv_config();
327 pc->geometry.cylinders = cylinders;
328 pc->geometry.heads = heads;
329 pc->geometry.sectors = sectors;
330 Block_features df = device_features();
331 df.geometry() = true;
332 set_device_features(df);
333 }
334
342 {
343 _dev_config.priv_config()->blk_size = sz;
344 Block_features df = device_features();
345 df.blk_size() = true;
346 set_device_features(df);
347 }
348
357 void set_topology(l4_uint8_t physical_block_exp,
358 l4_uint8_t alignment_offset,
359 l4_uint16_t min_io_size,
360 l4_uint32_t opt_io_size)
361 {
362 l4virtio_block_config_t volatile *pc = _dev_config.priv_config();
363 pc->topology.physical_block_exp = physical_block_exp;
364 pc->topology.alignment_offset = alignment_offset;
365 pc->topology.min_io_size = min_io_size;
366 pc->topology.opt_io_size = opt_io_size;
367 Block_features df = device_features();
368 df.topology() = true;
369 set_device_features(df);
370 }
371
374 {
375 Block_features df = device_features();
376 df.flush() = true;
377 set_device_features(df);
378 }
379
385 {
386 l4virtio_block_config_t volatile *pc = _dev_config.priv_config();
387 pc->writeback = writeback;
388 Block_features df = device_features();
389 df.config_wce() = true;
390 set_device_features(df);
391 }
392
398 {
399 l4virtio_block_config_t volatile *pc = _dev_config.priv_config();
400 return pc->writeback;
401 }
402
411 void set_discard(l4_uint32_t max_discard_sectors, l4_uint32_t max_discard_seg,
412 l4_uint32_t discard_sector_alignment)
413 {
414 l4virtio_block_config_t volatile *pc = _dev_config.priv_config();
415 pc->max_discard_sectors = max_discard_sectors;
416 pc->max_discard_seg = max_discard_seg;
417 pc->discard_sector_alignment = discard_sector_alignment;
418 Block_features df = device_features();
419 df.discard() = true;
420 set_device_features(df);
421 }
422
431 void set_write_zeroes(l4_uint32_t max_write_zeroes_sectors,
432 l4_uint32_t max_write_zeroes_seg,
433 l4_uint8_t write_zeroes_may_unmap)
434 {
435 l4virtio_block_config_t volatile *pc = _dev_config.priv_config();
436 pc->max_write_zeroes_sectors = max_write_zeroes_sectors;
437 pc->max_write_zeroes_seg = max_write_zeroes_seg;
438 pc->write_zeroes_may_unmap = write_zeroes_may_unmap;
439 Block_features df = device_features();
440 df.write_zeroes() = true;
441 set_device_features(df);
442 }
443
444public:
457 Block_dev_base(l4_uint32_t vendor, unsigned queue_size, l4_uint64_t capacity,
458 bool read_only, L4Re::Util::Dbg msg_user = L4Re::Util::Dbg{},
459 L4Re::Util::Dbg msg_dev = L4Re::Util::Dbg{2})
460 : L4virtio::Svr::Device_t<Ds_data>(&_dev_config, msg_user, msg_dev),
461 _vq_max(queue_size),
462 _dev_config(vendor, L4VIRTIO_ID_BLOCK, 1)
463 {
464 this->reset_queue_config(0, queue_size);
465
466 Block_features df(0);
467 df.ring_indirect_desc() = true;
468 df.ro() = read_only;
469 set_device_features(df);
470
471 _dev_config.set_host_feature(L4VIRTIO_FEATURE_VERSION_1);
472
473 _dev_config.priv_config()->capacity = capacity;
474 }
475
479 virtual void reset_device() = 0;
480
484 virtual bool queue_stopped() = 0;
485
497 void finalize_request(cxx::unique_ptr<Request> req, unsigned sz,
499 {
500 if (_dev_config.status().fail_state() || !_queue.ready())
501 return;
502
503 if (req->release_request(&_queue, status, sz) < 0)
504 this->device_error();
505
506 if (_queue.no_notify_guest())
507 return;
508
509 _dev_config.add_irq_status(L4VIRTIO_IRQ_STATUS_VRING);
510 _kick_guest_irq->trigger();
511
512 // Request can be dropped here.
513 }
514
515 int reconfig_queue(unsigned idx) override
516 {
517 if (idx == 0 && this->setup_queue(&_queue, 0, _vq_max))
518 return 0;
519
520 return -L4_EINVAL;
521 }
522
523 void reset() override
524 {
525 _queue.disable();
526 _dev_config.reset_queue(0, _vq_max);
527 _dev_config.reset_hdr();
528 reset_device();
529 }
530
531protected:
532 bool check_for_new_requests()
533 {
534 if (!_queue.ready() || queue_stopped())
535 return false;
536
537 if (_dev_config.status().fail_state())
538 return false;
539
540 return _queue.desc_avail();
541 }
542
544 cxx::unique_ptr<Request> get_request()
545 {
546 cxx::unique_ptr<Request> req;
547
548 if (!_queue.ready() || queue_stopped())
549 return req;
550
551 if (_dev_config.status().fail_state())
552 return req;
553
554 auto r = _queue.next_avail();
555 if (!r)
556 return req;
557
558 try
559 {
560 cxx::unique_ptr<Request> cur{
561 new Request(r, &(this->_mem_info), _vq_max, _max_block_size)};
562
563 req = cxx::move(cur);
564 }
565 catch (Bad_descriptor const &e)
566 {
567 this->device_error();
568 return req;
569 }
570
571 return req;
572 }
573
574private:
575 void register_single_driver_irq() override
576 {
577 _kick_guest_irq = L4Re::Util::Unique_cap<L4::Irq>(
578 L4Re::chkcap(this->server_iface()->template rcv_cap<L4::Irq>(0)));
579
580 L4Re::chksys(this->server_iface()->realloc_rcv_cap(0));
581 }
582
583 void trigger_driver_config_irq() override
584 {
586 _kick_guest_irq->trigger();
587 }
588
589 bool check_queues() override
590 {
591 if (!_queue.ready())
592 {
593 reset();
594 return false;
595 }
596
597 return true;
598 }
599};
600
601template <typename Ds_data>
602struct Block_dev
603: Block_dev_base<Ds_data>,
604 L4::Epiface_t<Block_dev<Ds_data>, L4virtio::Device>
605{
606private:
607 class Irq_object : public L4::Irqep_t<Irq_object>
608 {
609 public:
610 Irq_object(Block_dev<Ds_data> *parent) : _parent(parent) {}
611
612 void handle_irq()
613 {
614 _parent->kick();
615 }
616
617 private:
618 Block_dev<Ds_data> *_parent;
619 };
620 Irq_object _irq_handler;
621
622protected:
623 L4::Epiface *irq_iface()
624 { return &_irq_handler; }
625
626public:
627 Block_dev(l4_uint32_t vendor, unsigned queue_size, l4_uint64_t capacity,
628 bool read_only, L4Re::Util::Dbg msg_user = L4Re::Util::Dbg{},
629 L4Re::Util::Dbg msg_dev = L4Re::Util::Dbg{2})
630 : Block_dev_base<Ds_data>(vendor, queue_size, capacity, read_only,
631 msg_user, msg_dev),
632 _irq_handler(this)
633 {}
634
645 L4::Cap<void> register_obj(L4::Registry_iface *registry,
646 char const *service = 0)
647 {
648 L4Re::chkcap(registry->register_irq_obj(this->irq_iface()));
649 L4::Cap<void> ret;
650 if (service)
651 ret = registry->register_obj(this, service);
652 else
653 ret = registry->register_obj(this);
654 L4Re::chkcap(ret);
655
656 return ret;
657 }
658
659 L4::Cap<void> register_obj(L4::Registry_iface *registry,
660 L4::Cap<L4::Rcv_endpoint> ep)
661 {
662 L4Re::chkcap(registry->register_irq_obj(this->irq_iface()));
663
664 return L4Re::chkcap(registry->register_obj(this, ep));
665 }
666
667 typedef Block_request<Ds_data> Request;
682 virtual bool process_request(cxx::unique_ptr<Request> &&req) = 0;
683
684protected:
685 L4::Ipc_svr::Server_iface *server_iface() const override
686 {
687 return this->L4::Epiface::server_iface();
688 }
689
690 void kick()
691 {
692 for (;;)
693 {
694 auto req = this->get_request();
695 if (!req)
696 return;
697 if (!this->process_request(cxx::move(req)))
698 return;
699 }
700 }
701
702private:
703 L4::Cap<L4::Irq> device_notify_irq() const override
704 {
705 return L4::cap_cast<L4::Irq>(_irq_handler.obj_cap());
706 }
707};
708
709} }
virtual L4::Cap< L4::Irq > register_irq_obj(L4::Epiface *o)=0
Register o as server-side object for asynchronous IRQs.
virtual L4::Cap< void > register_obj(L4::Epiface *o, char const *service)=0
Register an L4::Epiface for an IPC gate available in the applications environment under the name serv...
Exception for an abstract runtime error.
Definition exceptions:129
Base class for virtio block devices.
Definition virtio-block:269
void set_write_zeroes(l4_uint32_t max_write_zeroes_sectors, l4_uint32_t max_write_zeroes_seg, l4_uint8_t write_zeroes_may_unmap)
Sets constraints for and enables the write zeroes command.
Definition virtio-block:431
Block_dev_base(l4_uint32_t vendor, unsigned queue_size, l4_uint64_t capacity, bool read_only, L4Re::Util::Dbg msg_user=L4Re::Util::Dbg{}, L4Re::Util::Dbg msg_dev=L4Re::Util::Dbg{2})
Create a new virtio block device.
Definition virtio-block:457
virtual bool queue_stopped()=0
Return true, if the queues should not be processed further.
l4_uint8_t get_writeback()
Get the writeback field from the configuration space.
Definition virtio-block:397
cxx::unique_ptr< Request > get_request()
Return one request if available.
Definition virtio-block:544
void set_blk_size(l4_uint32_t sz)
Sets block disk size to be reported to the client.
Definition virtio-block:341
void set_config_wce(l4_uint8_t writeback)
Sets cache mode and enables the writeback toggle.
Definition virtio-block:384
void set_flush()
Enables the flush command.
Definition virtio-block:373
void set_size_max(l4_uint32_t sz)
Sets the maximum size of any single segment reported to client.
Definition virtio-block:299
virtual void reset_device()=0
Reset the actual hardware device.
void set_discard(l4_uint32_t max_discard_sectors, l4_uint32_t max_discard_seg, l4_uint32_t discard_sector_alignment)
Sets constraints for and enables the discard command.
Definition virtio-block:411
void reset() override
reset callback, called for doing a device reset
Definition virtio-block:523
void set_geometry(l4_uint16_t cylinders, l4_uint8_t heads, l4_uint8_t sectors)
Set disk geometry that is reported to the client.
Definition virtio-block:324
void set_seg_max(l4_uint32_t sz)
Sets the maximum number of segments in a request that is reported to client.
Definition virtio-block:313
void set_topology(l4_uint8_t physical_block_exp, l4_uint8_t alignment_offset, l4_uint16_t min_io_size, l4_uint32_t opt_io_size)
Sets the I/O alignment information reported back to the client.
Definition virtio-block:357
void finalize_request(cxx::unique_ptr< Request > req, unsigned sz, l4_uint8_t status=L4VIRTIO_BLOCK_S_OK)
Releases resources related to a request and notifies the client.
Definition virtio-block:497
int reconfig_queue(unsigned idx) override
callback for client queue-config request
Definition virtio-block:515
A request to read or write data.
Definition virtio-block:29
unsigned data_size() const
Compute the total size of the data in the request.
Definition virtio-block:63
l4virtio_block_header_t const & header() const
Return the block request header.
Definition virtio-block:149
bool has_more()
Check if the request contains more data blocks.
Definition virtio-block:96
Data_block next_block()
Return next block in scatter-gather list.
Definition virtio-block:114
void add_irq_status(l4_uint32_t status)
Adds irq status bit.
Definition l4virtio:285
Status status() const
Get current device status (trusted).
Definition l4virtio:242
Server-side L4-VIRTIO device stub.
Definition l4virtio:826
virtual void trigger_driver_config_irq()=0
Device_t(Dev_config *dev_config, L4Re::Util::Dbg msg_user=L4Re::Util::Dbg{}, L4Re::Util::Dbg msg_dev=L4Re::Util::Dbg{2})
Definition l4virtio:930
virtual void register_single_driver_irq()
Definition l4virtio:871
bool setup_queue(Virtqueue *q, unsigned qn, unsigned num_max)
Definition l4virtio:1091
void reset_queue_config(unsigned idx, unsigned num_max, bool inc_generation=false, unsigned device_notify_index=0)
Definition l4virtio:1044
virtual L4::Cap< L4::Irq > device_notify_irq() const
Definition l4virtio:878
List of driver memory regions assigned to a single L4-VIRTIO transport instance.
Definition l4virtio:654
Region of driver memory, that shall be managed locally.
Definition l4virtio:475
T * local(Ptr< T > p) const
Get the local address for driver address p.
Definition l4virtio:640
Encapsulate the state for processing a VIRTIO request.
Definition virtio:473
bool next(DESC_MAN *dm, ARGS... args)
Switch to the next descriptor in a descriptor chain.
Definition virtio:575
bool has_more() const
Are there more chained descriptors?
Definition virtio:557
void start(DESC_MAN *dm, Virtqueue *ring, Virtqueue::Head_desc const &request, ARGS... args)
Start processing a new request.
Definition virtio:503
Virtqueue implementation for the device.
Definition virtio:88
bool desc_avail() const
Test for available descriptors.
Definition virtio:175
Descriptor in the descriptor table.
Definition virtqueue:93
l4_uint32_t len
Length of described buffer.
Definition virtqueue:115
Ptr< void > addr
Address stored in descriptor.
Definition virtqueue:114
Low-level Virtqueue.
Definition virtqueue:87
bool no_notify_guest() const
Get the no IRQ flag of this queue.
Definition virtqueue:433
bool ready() const
Test if this queue is in working state.
Definition virtqueue:419
unsigned char l4_uint8_t
Unsigned 8bit value.
Definition l4int.h:25
unsigned int l4_uint32_t
Unsigned 32bit value.
Definition l4int.h:29
unsigned short int l4_uint16_t
Unsigned 16bit value.
Definition l4int.h:27
unsigned long long l4_uint64_t
Unsigned 64bit value.
Definition l4int.h:31
@ L4_EEXIST
Already exists.
Definition err.h:44
@ L4_EINVAL
Invalid argument.
Definition err.h:47
@ L4_EIO
I/O error.
Definition err.h:36
@ L4_EOK
Ok.
Definition err.h:33
struct l4virtio_block_header_t l4virtio_block_header_t
Header structure of a request for a block device.
@ L4VIRTIO_BLOCK_S_OK
Request finished successfully.
@ L4VIRTIO_FEATURE_VERSION_1
Virtio protocol version 1 supported. Must be 1 for L4virtio.
Definition virtio.h:110
@ L4VIRTIO_ID_BLOCK
General block device.
Definition virtio.h:65
@ L4VIRTIO_IRQ_STATUS_VRING
VRING IRQ pending flag.
Definition virtio.h:121
@ L4VIRTIO_IRQ_STATUS_CONFIG
CONFIG IRQ pending flag.
Definition virtio.h:122
L4::Detail::Unique_cap_impl< T, L4Re::Util::Smart_cap_auto< L4_FP_ALL_SPACES > > Unique_cap
Unique capability that implements automatic free and unmap of the capability slot.
Definition unique_cap:43
T chkcap(T &&cap, char const *extra="", l4_ret_t err=-L4_ENOMEM)
Check for valid capability or raise C++ exception.
Definition error_helper:149
l4_ret_t chksys(l4_ret_t err, char const *extra="", l4_ret_t ret=0)
Generate C++ exception on error.
Definition error_helper:72
Cap< T > cap_cast(Cap< F > const &c) noexcept
static_cast for capabilities.
Definition capability.h:437
L4-VIRTIO Transport C++ API.
Definition l4virtio:26
Server_iface * server_iface() const
Get pointer to server interface at which the object is currently registered.
Definition ipc_epiface:331
Exception used by Queue to indicate descriptor errors.
Definition virtio:398
@ Bad_size
Invalid size of memory block.
Definition virtio:406
l4_uint32_t raw
The raw value of the features bitmap.
Definition virtio:68
constexpr fail_state_bfm_t::Val fail_state() const
Get the fail_state bits (6 to 7) of raw.
Definition virtio:44
Device configuration for block devices.
Header structure of a request for a block device.
Unique_cap / Unique_del_cap.