L4Re Operating System Framework
Interface and Usage Documentation
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
block_device_mgr.h
1/*
2 * Copyright (C) 2018-2020, 2022-2024 Kernkonzept GmbH.
3 * Author(s): Sarah Hoffmann <sarah.hoffmann@kernkonzept.com>
4 * Manuel von Oltersdorff-Kalettka <manuel.kalettka@kernkonzept.com>
5 *
6 * License: see LICENSE.spdx (in this directory or the directories above)
7 */
8#pragma once
9
10#include <cassert>
11#include <cstring>
12#include <memory>
13#include <string>
14#include <vector>
15
16#include <l4/cxx/ref_ptr>
17#include <l4/cxx/ref_ptr_list>
18#include <l4/cxx/unique_ptr>
19#include <l4/re/error_helper>
20#include <l4/sys/factory>
21#include <l4/sys/cxx/ipc_epiface>
22
23#include <l4/libblock-device/debug.h>
24#include <l4/libblock-device/errand.h>
25#include <l4/libblock-device/partition.h>
26#include <l4/libblock-device/part_device.h>
27#include <l4/libblock-device/virtio_client.h>
28#include <l4/libblock-device/scheduler.h>
29
30namespace Block_device {
31
32template <typename DEV>
33struct Simple_factory
34{
35 using Device_type = DEV;
36 using Client_type = Virtio_client<Device_type>;
37
38 static cxx::unique_ptr<Client_type>
39 create_client(cxx::Ref_ptr<Device_type> const &dev,
40 unsigned numds, bool readonly)
41 { return cxx::make_unique<Client_type>(dev, numds, readonly); }
42};
43
44template <typename BASE_DEV>
45struct Partitionable_factory
46{
47 using Device_type = BASE_DEV;
48 using Client_type = Virtio_client<Device_type>;
49
50 static cxx::unique_ptr<Client_type>
51 create_client(cxx::Ref_ptr<Device_type> const &dev,
52 unsigned numds, bool readonly)
53 {
54 return cxx::make_unique<Client_type>(dev, numds, readonly);
55 }
56
58 create_partition(cxx::Ref_ptr<Device_type> const &dev, unsigned partition_id,
59 Partition_info const &pi)
60 {
62 new Partitioned_device<Device_type>(dev, partition_id, pi));
63 }
64};
65
66
77template <typename DEV, typename FACTORY = Simple_factory<DEV>,
78 typename SCHEDULER = Rr_scheduler<typename FACTORY::Device_type>>
80{
81 using Device_factory_type = FACTORY;
82 using Client_type = typename Device_factory_type::Client_type;
83 using Device_type = typename Device_factory_type::Device_type;
84 using Scheduler_type = SCHEDULER;
85
86 using Ds_vector = std::vector<L4::Cap<L4Re::Dataspace>>;
87
88 using Pairing_callback = std::function<void(Device_type *)>;
89
93 struct Pending_client
94 {
96 std::string device_id;
100 int num_ds;
102 bool readonly;
103
104 bool enable_trusted_ds_validation;
105
106 std::shared_ptr<Ds_vector const> trusted_dataspaces;
107
109 Pairing_callback pairing_cb;
110
111 Pending_client() = default;
112
113 Pending_client(L4::Cap<L4::Rcv_endpoint> g, std::string const &dev, int ds,
114 bool ro, bool enable_trusted_ds_validation,
115 std::shared_ptr<Ds_vector const> trusted_dataspaces,
116 Pairing_callback cb)
117 : device_id(dev), gate(g), num_ds(ds), readonly(ro),
118 enable_trusted_ds_validation(enable_trusted_ds_validation),
119 trusted_dataspaces(trusted_dataspaces), pairing_cb(cb)
120 {}
121 };
122
123 class Connection : public cxx::Ref_obj_list_item<Connection>
124 {
125 public:
126 explicit Connection(Device_mgr *mgr, cxx::Ref_ptr<Device_type> &&dev)
127 : _shutdown_state(Shutdown_type::Running),
128 _device(cxx::move(dev)),
129 _mgr(mgr)
130 {}
131
132 L4::Cap<void> cap() const
133 { return _interface ? _interface->obj_cap() : L4::Cap<void>(); }
134
135 void start_disk_scan(Errand::Callback const &callback)
136 {
137 _device->start_device_scan(
138 [=]()
139 {
140 scan_disk_partitions(callback, 0);
141 });
142 }
143
144 void unregister_interfaces(L4::Registry_iface *registry) const
145 {
146 if (_interface)
147 registry->unregister_obj(_interface.get());
148
149 for (auto *sub : _subs)
150 sub->unregister_interfaces(registry);
151 }
152
153 int create_interface_for(Pending_client *c, L4::Registry_iface *registry)
154 {
155 if (_shutdown_state != Shutdown_type::Running)
156 return -L4_EIO;
157
158 if (_interface)
159 return contains_device(c->device_id) ? -L4_EBUSY : -L4_ENODEV;
160
161 // check for match in partitions
162
163 bool busy = false;
164 for (auto *sub : _subs)
165 {
166 if (sub->_interface)
167 busy = true;
168
169 int ret = sub->create_interface_for(c, registry);
170
171 if (ret != -L4_ENODEV) // includes L4_EOK
172 return ret;
173 }
174
175 if (!match_hid(c->device_id))
176 return -L4_ENODEV;
177
178 if (busy)
179 return -L4_EBUSY;
180
181 auto clt = Device_factory_type::create_client(_device, c->num_ds,
182 c->readonly);
183
184 clt->add_trusted_dataspaces(c->trusted_dataspaces);
185 if (c->enable_trusted_ds_validation)
186 clt->enable_trusted_ds_validation();
187
188 if (c->gate.is_valid())
189 {
190 if (!clt->register_obj(registry, c->gate).is_valid())
191 return -L4_ENOMEM;
192 }
193 else
194 {
195 c->gate = L4::cap_reinterpret_cast<L4::Rcv_endpoint>(
196 clt->register_obj(registry));
197 if (!c->gate.is_valid())
198 return -L4_ENOMEM;
199 }
200
201 _mgr->_scheduler->add_client(clt.get());
202 _interface.reset(clt.release());
203
204 // Let it be known that the client and the device paired
205 if (c->pairing_cb)
206 c->pairing_cb(_device.get());
207 return L4_EOK;
208 }
209
210 void check_clients(L4::Registry_iface *registry)
211 {
212 if (_interface)
213 {
214 if (_interface->obj_cap() && !_interface->obj_cap().validate().label())
215 remove_client(registry);
216
217 return;
218 }
219
220 // Sub-devices only need to be checked when the parent device was free.
221 for (auto *sub : _subs)
222 sub->check_clients(registry);
223 }
224
226 void shutdown_event(Shutdown_type type)
227 {
228 // Set new shutdown state
229 _shutdown_state = type;
230 for (auto const &sub: _subs)
231 sub->shutdown_event(type);
232 if (_interface)
233 _interface->shutdown_event(type);
234 }
235
236 private:
248 template <typename T = Device_factory_type>
249 auto scan_disk_partitions(Errand::Callback const &callback, int)
250 -> decltype((T::create_partition)(cxx::Ref_ptr<Device_type>(), 0, Partition_info()), void())
251 {
252 auto reader = cxx::make_ref_obj<Partition_reader<Device_type>>(_device.get());
253 // The reference to reader will be captured in the lambda passed to
254 // reader's own read() method. At the same time, reader will store
255 // the reference to the lambda.
256 reader->read(
257 [=]()
258 {
259 l4_size_t sz = reader->table_size();
260
261 for (l4_size_t i = 1; i <= sz; ++i)
262 {
263 Partition_info info;
264 if (reader->get_partition(i, &info) < 0)
265 continue;
266
267 auto conn = cxx::make_ref_obj<Connection>(
268 _mgr,
269 Device_factory_type::create_partition(_device, i, info));
270 _subs.push_front(std::move(conn));
271 }
272
273 callback();
274
275 // Prolong the life-span of reader until we are sure the reader is
276 // not currently invoked (i.e. capture the last reference to it in
277 // an independent timeout callback).
278 Errand::schedule([reader](){}, 0);
279 });
280 }
281
289 template <typename T = Device_factory_type>
290 void scan_disk_partitions(Errand::Callback const &callback, long)
291 { callback(); }
292
296 void remove_client(L4::Registry_iface *registry)
297 {
298 assert(_interface);
299
300 // This operation is idempotent.
301 _interface->shutdown_event(Shutdown_type::Client_gone);
302
303 if (_interface->busy())
304 {
305 Dbg::trace().printf("Deferring dead client removal.\n");
306
307 // Cannot remove the client while it still has active I/O requests.
308 // This means that the device did not abort its inflight requests in
309 // its reset() callback. It is still desirable though to wait for
310 // those requests to finish and defer the dead client removal until
311 // later.
312 Errand::schedule([this, registry]() { remove_client(registry); },
313 10000);
314 return;
315 }
316
317 _interface->unregister_obj(registry);
318 _mgr->_scheduler->remove_client(_interface.get());
319 _interface.reset();
320 }
321
322 bool contains_device(std::string const &name) const
323 {
324 if (match_hid(name))
325 return true;
326
327 for (auto *sub : _subs)
328 if (sub->contains_device(name))
329 return true;
330
331 return false;
332 }
333
334 bool match_hid(std::string const &name) const
335 { return _device->match_hid(cxx::String(name.c_str(), name.length())); }
336
337
339 Shutdown_type _shutdown_state;
343 cxx::unique_ptr<Client_type> _interface;
346
347 Device_mgr *_mgr;
348 };
349
350public:
352 : _registry(registry)
353 {
354 _scheduler = cxx::make_unique<Scheduler_type>(registry);
355 }
356
357 virtual ~Device_mgr()
358 {
359 for (auto *c : _connpts)
360 c->unregister_interfaces(_registry);
361 }
362
383 static int parse_device_name(std::string const &param, std::string &device)
384 {
385 std::string const partlabel("partlabel:");
386 std::string const partuuid("partuuid:");
387
388 if (param.size() > partlabel.size()
389 && param.compare(0, partlabel.size(), partlabel) == 0)
390 {
391 device = param.substr(partlabel.size());
392 return L4_EOK;
393 }
394 else if (param.size() > partuuid.size()
395 && param.compare(0, partuuid.size(), partuuid) == 0)
396 {
397 auto device_partuuid = param.substr(partuuid.size());
398 if (!is_uuid(device_partuuid.c_str()))
399 {
400 Dbg::trace().printf("The 'partuuid:' parameter expects a UUID.\n");
401 return -L4_EINVAL;
402 }
403
404 device = device_partuuid;
405 std::transform(device.begin(), device.end(), device.begin(),
406 [](unsigned char c){ return std::toupper(c); });
407 return L4_EOK;
408 }
409 else
410 {
411 device = param;
412 if (is_uuid(param.c_str()))
413 std::transform(device.begin(), device.end(), device.begin(),
414 [](unsigned char c) { return std::toupper(c); });
415 return L4_EOK;
416 }
417 }
418
419 int add_static_client(L4::Cap<L4::Rcv_endpoint> client, const char *device,
420 int partno, int num_ds, bool readonly = false,
421 Pairing_callback cb = nullptr,
422 bool enable_trusted_ds_validation = false,
423 std::shared_ptr<Ds_vector const> trusted_dataspaces
424 = nullptr)
425 {
426 char _buf[30];
427 const char *buf;
428
429 if (partno == 0)
430 {
431 Err().printf("Invalid partition number 0.\n");
432 return -L4_ENODEV;
433 }
434
435 if (partno != -1)
436 {
437 /* Could we avoid to make a string here and parsing this again
438 * deeper in the stack? */
439 snprintf(_buf, sizeof(_buf), "%s:%d", device, partno);
440 buf = _buf;
441 }
442 else
443 buf = device;
444
445 _pending_clients.emplace_back(client, buf, num_ds, readonly,
446 enable_trusted_ds_validation,
447 trusted_dataspaces, cb);
448
449 return L4_EOK;
450 }
451
452 int create_dynamic_client(std::string const &device, int partno, int num_ds,
453 L4::Cap<void> *cap, bool readonly = false,
454 Pairing_callback cb = nullptr,
455 bool enable_trusted_ds_validation = false,
456 std::shared_ptr<Ds_vector const> trusted_dataspaces
457 = nullptr)
458 {
459 Pending_client clt;
460
461 // Maximum number of dataspaces that can be registered.
462 clt.num_ds = num_ds;
463
464 clt.readonly = readonly;
465
466 clt.device_id = device;
467
468 clt.pairing_cb = cb;
469
470 clt.trusted_dataspaces = trusted_dataspaces;
471
472 clt.enable_trusted_ds_validation = enable_trusted_ds_validation;
473
474 if (partno > 0)
475 {
476 clt.device_id += ':';
477 clt.device_id += std::to_string(partno);
478 }
479
480 for (auto *c : _connpts)
481 {
482 int ret = c->create_interface_for(&clt, _registry);
483
484 if (ret == -L4_ENODEV)
485 continue;
486
487 if (ret < 0)
488 return ret;
489
490 // found the requested device
491 *cap = clt.gate;
492 return L4_EOK;
493 }
494
495 return -L4_ENODEV;
496 }
497
502 {
503 for (auto *c : _connpts)
504 c->check_clients(_registry);
505 }
506
507 void add_disk(cxx::Ref_ptr<Device_type> &&device, Errand::Callback const &callback)
508 {
509 auto conn = cxx::make_ref_obj<Connection>(this, std::move(device));
510
511 conn->start_disk_scan(
512 [=]()
513 {
514 _connpts.push_front(conn);
515 connect_static_clients(conn.get());
516 callback();
517 });
518 }
519
521 void shutdown_event(Shutdown_type type)
522 {
523 l4_assert(type != Client_gone);
524 l4_assert(type != Client_shutdown);
525
526 for (auto const &con : _connpts)
527 con->shutdown_event(type);
528 }
529
530private:
531 void connect_static_clients(Connection *con)
532 {
533 for (auto &c : _pending_clients)
534 {
535 Dbg::trace().printf("Checking existing client %s\n", c.device_id.c_str());
536 if (!c.gate.is_valid())
537 continue;
538
539 int ret = con->create_interface_for(&c, _registry);
540
541 if (ret == L4_EOK)
542 {
543 c.gate = L4::Cap<L4::Rcv_endpoint>();
544 // There might be other clients waiting for other partitions.
545 // Continue search.
546 continue;
547 }
548
549 if (ret != -L4_ENODEV)
550 break;
551 }
552 }
553
554 static constexpr bool is_uuid(char const *s)
555 {
556 for (unsigned i = 0; i < 36; ++i)
557 if (i == 8 || i == 13 || i == 18 || i == 23)
558 {
559 if (s[i] != '-')
560 return false;
561 }
562 else
563 {
564 if (!isxdigit(s[i]))
565 return false;
566 }
567 return s[36] == '\0';
568 }
569
571 L4::Registry_iface *_registry;
575 std::vector<Pending_client> _pending_clients;
577 cxx::unique_ptr<Scheduler_type> _scheduler;
578};
579
580} // name space
Basic class that scans devices and handles client connections.
void check_clients()
Remove clients where the client IPC gate is no longer valid.
void shutdown_event(Shutdown_type type)
Process a shutdown event on all connections.
static int parse_device_name(std::string const &param, std::string &device)
Parse and verify a device string parameter.
C++ interface for capabilities.
Definition capability.h:219
Abstract interface for object registries.
Definition ipc_epiface:323
virtual void unregister_obj(L4::Epiface *o, bool unmap=true)=0
Unregister the given object o from the server.
List of smart-pointer-managed objects.
A reference-counting pointer with automatic cleanup.
Definition ref_ptr:71
Allocation free string class with explicit length field.
Definition string:31
Error helper.
Common factory related definitions.
unsigned int l4_size_t
Unsigned size type.
Definition l4int.h:24
@ L4_EINVAL
Invalid argument.
Definition err.h:46
@ L4_EBUSY
Object currently busy, try later.
Definition err.h:42
@ L4_ENODEV
No such thing.
Definition err.h:44
@ L4_EIO
I/O error.
Definition err.h:35
@ L4_EOK
Ok.
Definition err.h:32
@ L4_ENOMEM
No memory.
Definition err.h:39
Implementation of a list of ref-ptr-managed objects.
Information about a single partition.
Definition partition.h:30
Item for list linked via cxx::Ref_ptr with default refence counting.
Definition ref_ptr_list:26
#define l4_assert(expr)
Low-level assert.
Definition assert.h:32