L4Re Operating System Framework
Interface and Usage Documentation
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
virtio-block
1// vi:ft=cpp
2/* SPDX-License-Identifier: MIT */
3/*
4 * Copyright (C) 2017-2021, 2024 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>
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
68 rp.start(_mem_list, _request, &data);
69
70 unsigned total = data.len;
71
72 try
73 {
74 while (rp.has_more())
75 {
76 rp.next(_mem_list, &data);
77 total += data.len;
78 }
79 }
80 catch (Bad_descriptor const &e)
81 {
82 // need to convert the exception because e contains a raw pointer to rp
83 throw L4::Runtime_error(-L4_EIO, "bad virtio descriptor");
84 }
85
86 if (total < Header_size + 1)
87 throw L4::Runtime_error(-L4_EIO, "virtio request too short");
88
89 return total - Header_size - 1;
90 }
91
95 bool has_more()
96 {
97 // peek into the remaining data
98 while (_data.len == 0 && _rp.has_more())
99 _rp.next(_mem_list, &_data);
100
101 // there always must be one byte left for status
102 return (_data.len > 1 || _rp.has_more());
103 }
104
113 Data_block next_block()
114 {
115 Data_block out;
116
117 if (_data.len == 0)
118 {
119 if (!_rp.has_more())
121 "No more data blocks in virtio request");
122
123 if (_todo_blocks == 0)
125 --_todo_blocks;
126
127 _rp.next(_mem_list, &_data);
128 }
129
130 if (_data.len > _max_block_size)
132
133 out = _data;
134
135 if (!_rp.has_more())
136 {
137 --(out.len);
138 _data.len = 1;
139 _data.addr = static_cast<char *>(_data.addr) + out.len;
140 }
141 else
142 _data.len = 0; // is consumed
143
144 return out;
145 }
146
149 { return _header; }
150
151private:
152 Block_request(Virtqueue::Request req, Driver_mem_list_t<Ds_data> *mem_list,
153 unsigned max_blocks, l4_uint32_t max_block_size)
154 : _mem_list(mem_list),
155 _request(req),
156 _todo_blocks(max_blocks),
157 _max_block_size(max_block_size)
158 {
159 // read header which should be in the first block
160 _rp.start(mem_list, _request, &_data);
161 --_todo_blocks;
162
163 if (_data.len < Header_size)
165
166 _header = *(static_cast<l4virtio_block_header_t *>(_data.addr));
167
168 _data.addr = static_cast<char *>(_data.addr) + Header_size;
169 _data.len -= Header_size;
170
171 // if there is no space for status bit we cannot really recover
172 if (!_rp.has_more() && _data.len == 0)
174 }
175
176 int release_request(Virtqueue *queue, l4_uint8_t status, unsigned sz)
177 {
178 // write back status
179 // If there was an error on the way or the status byte is in its
180 // own block, fast-forward to the last block.
181 if (_rp.has_more())
182 {
183 while (_rp.next(_mem_list, &_data) && _todo_blocks > 0)
184 --_todo_blocks;
185
186 if (_todo_blocks > 0 && _data.len > 0)
187 *(static_cast<l4_uint8_t *>(_data.addr) + _data.len - 1) = status;
188 else
189 return -L4_EIO; // too many data blocks
190 }
191 else if (_data.len > 0)
192 *(static_cast<l4_uint8_t *>(_data.addr)) = status;
193 else
194 return -L4_EIO; // no space for final status byte
195
196 // now release the head
197 queue->consumed(_request, sz);
198
199 return L4_EOK;
200 }
201
207 Driver_mem_list_t<Ds_data> *_mem_list;
211 Request_processor _rp;
213 Data_block _data;
214
216 Virtqueue::Request _request;
218 unsigned _todo_blocks;
220 l4_uint32_t _max_block_size;
221};
222
223struct Block_features : public Dev_config::Features
224{
225 Block_features() = default;
226 Block_features(l4_uint32_t raw) : Dev_config::Features(raw) {}
227
229 CXX_BITFIELD_MEMBER( 1, 1, size_max, raw);
231 CXX_BITFIELD_MEMBER( 2, 2, seg_max, raw);
233 CXX_BITFIELD_MEMBER( 4, 4, geometry, raw);
235 CXX_BITFIELD_MEMBER( 5, 5, ro, raw);
237 CXX_BITFIELD_MEMBER( 6, 6, blk_size, raw);
239 CXX_BITFIELD_MEMBER( 9, 9, flush, raw);
241 CXX_BITFIELD_MEMBER(10, 10, topology, raw);
243 CXX_BITFIELD_MEMBER(11, 11, config_wce, raw);
245 CXX_BITFIELD_MEMBER(12, 12, mq, raw);
247 CXX_BITFIELD_MEMBER(13, 13, discard, raw);
249 CXX_BITFIELD_MEMBER(14, 14, write_zeroes, raw);
250};
251
252
258template <typename Ds_data>
260{
261private:
262 L4Re::Util::Unique_cap<L4::Irq> _kick_guest_irq;
263 Virtqueue _queue;
264 unsigned _vq_max;
265 l4_uint32_t _max_block_size = UINT_MAX;
266 Dev_config_t<l4virtio_block_config_t> _dev_config;
267
268public:
269 typedef Block_request<Ds_data> Request;
270
271protected:
272 Block_features negotiated_features() const
273 { return _dev_config.negotiated_features(0); }
274
275 Block_features device_features() const
276 { return _dev_config.host_features(0); }
277
278 void set_device_features(Block_features df)
279 { _dev_config.host_features(0) = df.raw; }
280
291 {
292 _dev_config.priv_config()->size_max = sz;
293 Block_features df = device_features();
294 df.size_max() = true;
295 set_device_features(df);
296
297 _max_block_size = sz;
298 }
299
305 {
306 _dev_config.priv_config()->seg_max = sz;
307 Block_features df = device_features();
308 df.seg_max() = true;
309 set_device_features(df);
310 }
311
315 void set_geometry(l4_uint16_t cylinders, l4_uint8_t heads, l4_uint8_t sectors)
316 {
317 l4virtio_block_config_t volatile *pc = _dev_config.priv_config();
318 pc->geometry.cylinders = cylinders;
319 pc->geometry.heads = heads;
320 pc->geometry.sectors = sectors;
321 Block_features df = device_features();
322 df.geometry() = true;
323 set_device_features(df);
324 }
325
333 {
334 _dev_config.priv_config()->blk_size = sz;
335 Block_features df = device_features();
336 df.blk_size() = true;
337 set_device_features(df);
338 }
339
348 void set_topology(l4_uint8_t physical_block_exp,
349 l4_uint8_t alignment_offset,
350 l4_uint32_t min_io_size,
351 l4_uint32_t opt_io_size)
352 {
353 l4virtio_block_config_t volatile *pc = _dev_config.priv_config();
354 pc->topology.physical_block_exp = physical_block_exp;
355 pc->topology.alignment_offset = alignment_offset;
356 pc->topology.min_io_size = min_io_size;
357 pc->topology.opt_io_size = opt_io_size;
358 Block_features df = device_features();
359 df.topology() = true;
360 set_device_features(df);
361 }
362
365 {
366 Block_features df = device_features();
367 df.flush() = true;
368 set_device_features(df);
369 }
370
376 {
377 l4virtio_block_config_t volatile *pc = _dev_config.priv_config();
378 pc->writeback = writeback;
379 Block_features df = device_features();
380 df.config_wce() = true;
381 set_device_features(df);
382 }
383
389 {
390 l4virtio_block_config_t volatile *pc = _dev_config.priv_config();
391 return pc->writeback;
392 }
393
402 void set_discard(l4_uint32_t max_discard_sectors, l4_uint32_t max_discard_seg,
403 l4_uint32_t discard_sector_alignment)
404 {
405 l4virtio_block_config_t volatile *pc = _dev_config.priv_config();
406 pc->max_discard_sectors = max_discard_sectors;
407 pc->max_discard_seg = max_discard_seg;
408 pc->discard_sector_alignment = discard_sector_alignment;
409 Block_features df = device_features();
410 df.discard() = true;
411 set_device_features(df);
412 }
413
422 void set_write_zeroes(l4_uint32_t max_write_zeroes_sectors,
423 l4_uint32_t max_write_zeroes_seg,
424 l4_uint8_t write_zeroes_may_unmap)
425 {
426 l4virtio_block_config_t volatile *pc = _dev_config.priv_config();
427 pc->max_write_zeroes_sectors = max_write_zeroes_sectors;
428 pc->max_write_zeroes_seg = max_write_zeroes_seg;
429 pc->write_zeroes_may_unmap = write_zeroes_may_unmap;
430 Block_features df = device_features();
431 df.write_zeroes() = true;
432 set_device_features(df);
433 }
434
435public:
444 Block_dev_base(l4_uint32_t vendor, unsigned queue_size, l4_uint64_t capacity,
445 bool read_only)
446 : L4virtio::Svr::Device_t<Ds_data>(&_dev_config),
447 _vq_max(queue_size),
448 _dev_config(vendor, L4VIRTIO_ID_BLOCK, 1)
449 {
450 this->reset_queue_config(0, queue_size);
451
452 Block_features df(0);
453 df.ring_indirect_desc() = true;
454 df.ro() = read_only;
455 set_device_features(df);
456
457 _dev_config.set_host_feature(L4VIRTIO_FEATURE_VERSION_1);
458
459 _dev_config.priv_config()->capacity = capacity;
460 }
461
465 virtual void reset_device() = 0;
466
470 virtual bool queue_stopped() = 0;
471
483 void finalize_request(cxx::unique_ptr<Request> req, unsigned sz,
485 {
486 if (_dev_config.status().fail_state() || !_queue.ready())
487 return;
488
489 if (req->release_request(&_queue, status, sz) < 0)
490 this->device_error();
491
492 if (_queue.no_notify_guest())
493 return;
494
495 _dev_config.add_irq_status(L4VIRTIO_IRQ_STATUS_VRING);
496 _kick_guest_irq->trigger();
497
498 // Request can be dropped here.
499 }
500
501 int reconfig_queue(unsigned idx) override
502 {
503 if (idx == 0 && this->setup_queue(&_queue, 0, _vq_max))
504 return 0;
505
506 return -L4_EINVAL;
507 }
508
509 void reset() override
510 {
511 _queue.disable();
512 _dev_config.reset_queue(0, _vq_max);
513 _dev_config.reset_hdr();
514 reset_device();
515 }
516
517protected:
518 bool check_for_new_requests()
519 {
520 if (!_queue.ready() || queue_stopped())
521 return false;
522
523 if (_dev_config.status().fail_state())
524 return false;
525
526 return _queue.desc_avail();
527 }
528
530 cxx::unique_ptr<Request> get_request()
531 {
532 cxx::unique_ptr<Request> req;
533
534 if (!_queue.ready() || queue_stopped())
535 return req;
536
537 if (_dev_config.status().fail_state())
538 return req;
539
540 auto r = _queue.next_avail();
541 if (!r)
542 return req;
543
544 try
545 {
546 cxx::unique_ptr<Request> cur{
547 new Request(r, &(this->_mem_info), _vq_max, _max_block_size)};
548
549 req = cxx::move(cur);
550 }
551 catch (Bad_descriptor const &e)
552 {
553 this->device_error();
554 return req;
555 }
556
557 return req;
558 }
559
560private:
561 void register_single_driver_irq() override
562 {
563 _kick_guest_irq = L4Re::Util::Unique_cap<L4::Irq>(
564 L4Re::chkcap(this->server_iface()->template rcv_cap<L4::Irq>(0)));
565
566 L4Re::chksys(this->server_iface()->realloc_rcv_cap(0));
567 }
568
569 void trigger_driver_config_irq() override
570 {
571 _dev_config.add_irq_status(L4VIRTIO_IRQ_STATUS_CONFIG);
572 _kick_guest_irq->trigger();
573 }
574
575 bool check_queues() override
576 {
577 if (!_queue.ready())
578 {
579 reset();
580 return false;
581 }
582
583 return true;
584 }
585};
586
587template <typename Ds_data>
588struct Block_dev
589: Block_dev_base<Ds_data>,
590 L4::Epiface_t<Block_dev<Ds_data>, L4virtio::Device>
591{
592private:
593 class Irq_object : public L4::Irqep_t<Irq_object>
594 {
595 public:
596 Irq_object(Block_dev<Ds_data> *parent) : _parent(parent) {}
597
598 void handle_irq()
599 {
600 _parent->kick();
601 }
602
603 private:
604 Block_dev<Ds_data> *_parent;
605 };
606 Irq_object _irq_handler;
607
608protected:
609 L4::Epiface *irq_iface()
610 { return &_irq_handler; }
611
612public:
613 Block_dev(l4_uint32_t vendor, unsigned queue_size, l4_uint64_t capacity,
614 bool read_only)
615 : Block_dev_base<Ds_data>(vendor, queue_size, capacity, read_only),
616 _irq_handler(this)
617 {}
618
629 L4::Cap<void> register_obj(L4::Registry_iface *registry,
630 char const *service = 0)
631 {
632 L4Re::chkcap(registry->register_irq_obj(this->irq_iface()));
633 L4::Cap<void> ret;
634 if (service)
635 ret = registry->register_obj(this, service);
636 else
637 ret = registry->register_obj(this);
638 L4Re::chkcap(ret);
639
640 return ret;
641 }
642
643 L4::Cap<void> register_obj(L4::Registry_iface *registry,
645 {
646 L4Re::chkcap(registry->register_irq_obj(this->irq_iface()));
647
648 return L4Re::chkcap(registry->register_obj(this, ep));
649 }
650
651 typedef Block_request<Ds_data> Request;
666 virtual bool process_request(cxx::unique_ptr<Request> &&req) = 0;
667
668protected:
669 L4::Ipc_svr::Server_iface *server_iface() const override
670 {
671 return this->L4::Epiface::server_iface();
672 }
673
674 void kick()
675 {
676 for (;;)
677 {
678 auto req = this->get_request();
679 if (!req)
680 return;
681 if (!this->process_request(cxx::move(req)))
682 return;
683 }
684 }
685
686private:
687 L4::Cap<L4::Irq> device_notify_irq() const override
688 {
689 return L4::cap_cast<L4::Irq>(_irq_handler.obj_cap());
690 }
691};
692
693} }
C++ interface for capabilities.
Definition capability.h:224
Interface for server-loop related functions.
Definition ipc_epiface:37
Abstract interface for object registries.
Definition ipc_epiface:323
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:260
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:422
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:388
cxx::unique_ptr< Request > get_request()
Return one request if available.
Definition virtio-block:530
void set_blk_size(l4_uint32_t sz)
Sets block disk size to be reported to the client.
Definition virtio-block:332
void set_config_wce(l4_uint8_t writeback)
Sets cache mode and enables the writeback toggle.
Definition virtio-block:375
Block_dev_base(l4_uint32_t vendor, unsigned queue_size, l4_uint64_t capacity, bool read_only)
Create a new virtio block device.
Definition virtio-block:444
void set_flush()
Enables the flush command.
Definition virtio-block:364
void set_size_max(l4_uint32_t sz)
Sets the maximum size of any single segment reported to client.
Definition virtio-block:290
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:402
void reset() override
reset callback, called for doing a device reset
Definition virtio-block:509
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:315
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:304
void set_topology(l4_uint8_t physical_block_exp, l4_uint8_t alignment_offset, l4_uint32_t min_io_size, l4_uint32_t opt_io_size)
Sets the I/O alignment information reported back to the client.
Definition virtio-block:348
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:483
int reconfig_queue(unsigned idx) override
callback for client queue-config request
Definition virtio-block:501
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:148
bool has_more()
Check if the request contains more data blocks.
Definition virtio-block:95
Data_block next_block()
Return next block in scatter-gather list.
Definition virtio-block:113
Server-side L4-VIRTIO device stub.
Definition l4virtio:802
void device_error()
Transition device into DEVICE_NEEDS_RESET state.
Definition l4virtio:1024
Mem_list _mem_info
Memory region list.
Definition l4virtio:807
bool setup_queue(Virtqueue *q, unsigned qn, unsigned num_max)
Enable/disable the specified queue.
Definition l4virtio:1047
virtual L4::Cap< L4::Irq > device_notify_irq(unsigned idx)
Callback to gather the device notification IRQ (multi IRQ).
Definition l4virtio:874
void reset_queue_config(unsigned idx, unsigned num_max, bool inc_generation=false)
Trigger reset for the configuration space for queue idx.
Definition l4virtio:1002
List of driver memory regions assigned to a single L4-VIRTIO transport instance.
Definition l4virtio:630
Region of driver memory, that shall be managed locally.
Definition l4virtio:451
T * local(Ptr< T > p) const
Get the local address for driver address p.
Definition l4virtio:616
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:570
bool has_more() const
Are there more chained descriptors?
Definition virtio:553
void start(DESC_MAN *dm, Virtqueue *ring, Virtqueue::Head_desc const &request, ARGS... args)
Start processing a new request.
Definition virtio:501
Virtqueue implementation for the device.
Definition virtio:88
bool desc_avail() const
Test for available descriptors.
Definition virtio:175
Request next_avail()
Get the next available descriptor from the available ring.
Definition virtio:136
Descriptor in the descriptor table.
Definition virtqueue:87
l4_uint32_t len
Length of described buffer.
Definition virtqueue:109
Ptr< void > addr
Address stored in descriptor.
Definition virtqueue:108
Low-level Virtqueue.
Definition virtqueue:81
void disable()
Completely disable the queue.
Definition virtqueue:223
bool no_notify_guest() const
Get the no IRQ flag of this queue.
Definition virtqueue:413
bool ready() const
Test if this queue is in working state.
Definition virtqueue:399
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:43
@ L4_EINVAL
Invalid argument.
Definition err.h:46
@ L4_EIO
I/O error.
Definition err.h:35
@ L4_EOK
Ok.
Definition err.h:32
@ L4VIRTIO_BLOCK_S_OK
Request finished successfully.
@ L4VIRTIO_FEATURE_VERSION_1
Virtio protocol version 1 supported. Must be 1 for L4virtio.
Definition virtio.h:99
@ L4VIRTIO_ID_BLOCK
General block device.
Definition virtio.h:64
@ L4VIRTIO_IRQ_STATUS_VRING
VRING IRQ pending flag.
Definition virtio.h:110
@ L4VIRTIO_IRQ_STATUS_CONFIG
CONFIG IRQ pending flag.
Definition virtio.h:111
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
long chksys(long err, char const *extra="", long ret=0)
Generate C++ exception on error.
Definition error_helper:72
T chkcap(T &&cap, char const *extra="", long err=-L4_ENOMEM)
Check for valid capability or raise C++ exception.
Definition error_helper:149
L4-VIRTIO Transport C++ API.
Definition l4virtio:26
Epiface implementation for Kobject-based interface implementations.
Definition ipc_epiface:504
Base class for interface implementations.
Definition ipc_epiface:146
Server_iface * server_iface() const
Get pointer to server interface at which the object is currently registered.
Definition ipc_epiface:213
Epiface implementation for interrupt handlers.
Definition ipc_epiface:283
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
Device configuration for block devices.
Header structure of a request for a block device.
Unique_cap / Unique_del_cap.