L4Re Operating System Framework
Interface and Usage Documentation
Loading...
Searching...
No Matches
block_device_mgr.h
1/*
2 * Copyright (C) 2018-2020, 2022 Kernkonzept GmbH.
3 * Author(s): Sarah Hoffmann <sarah.hoffmann@kernkonzept.com>
4 * Manuel von Oltersdorff-Kalettka <manuel.kalettka@kernkonzept.com>
5 *
6 * This file is distributed under the terms of the GNU General Public
7 * License, version 2. Please see the COPYING-GPL-2 file for details.
8 */
9#pragma once
10
11#include <cassert>
12#include <cstring>
13#include <memory>
14#include <string>
15#include <vector>
16
17#include <l4/cxx/ref_ptr>
18#include <l4/cxx/ref_ptr_list>
19#include <l4/cxx/unique_ptr>
20#include <l4/re/error_helper>
21#include <l4/sys/factory>
22#include <l4/sys/cxx/ipc_epiface>
23
24#include <l4/libblock-device/debug.h>
25#include <l4/libblock-device/errand.h>
26#include <l4/libblock-device/partition.h>
27#include <l4/libblock-device/part_device.h>
28#include <l4/libblock-device/virtio_client.h>
29#include <l4/libblock-device/scheduler.h>
30
31namespace Block_device {
32
33template <typename DEV>
34struct Simple_factory
35{
36 using Device_type = DEV;
37 using Client_type = Virtio_client<Device_type>;
38
39 static cxx::unique_ptr<Client_type>
40 create_client(cxx::Ref_ptr<Device_type> const &dev,
41 unsigned numds, bool readonly)
42 { return cxx::make_unique<Client_type>(dev, numds, readonly); }
43};
44
45template <typename BASE_DEV>
46struct Partitionable_factory
47{
48 using Device_type = BASE_DEV;
49 using Client_type = Virtio_client<Device_type>;
50
51 static cxx::unique_ptr<Client_type>
52 create_client(cxx::Ref_ptr<Device_type> const &dev,
53 unsigned numds, bool readonly)
54 {
55 return cxx::make_unique<Client_type>(dev, numds, readonly);
56 }
57
59 create_partition(cxx::Ref_ptr<Device_type> const &dev, unsigned partition_id,
60 Partition_info const &pi)
61 {
63 new Partitioned_device<Device_type>(dev, partition_id, pi));
64 }
65};
66
67
78template <typename DEV, typename FACTORY = Simple_factory<DEV>,
79 typename SCHEDULER = Rr_scheduler<typename FACTORY::Device_type>>
81{
82 using Device_factory_type = FACTORY;
83 using Client_type = typename Device_factory_type::Client_type;
84 using Device_type = typename Device_factory_type::Device_type;
85 using Scheduler_type = SCHEDULER;
86
87 using Ds_vector = std::vector<L4::Cap<L4Re::Dataspace>>;
88
89 using Pairing_callback = std::function<void(Device_type *)>;
90
94 struct Pending_client
95 {
97 std::string device_id;
101 int num_ds;
103 bool readonly;
104
105 bool enable_trusted_ds_validation;
106
107 std::shared_ptr<Ds_vector const> trusted_dataspaces;
108
110 Pairing_callback pairing_cb;
111
112 Pending_client() = default;
113
114 Pending_client(L4::Cap<L4::Rcv_endpoint> g, std::string const &dev, int ds,
115 bool ro, bool enable_trusted_ds_validation,
116 std::shared_ptr<Ds_vector const> trusted_dataspaces,
117 Pairing_callback cb)
118 : device_id(dev), gate(g), num_ds(ds), readonly(ro),
119 enable_trusted_ds_validation(enable_trusted_ds_validation),
120 trusted_dataspaces(trusted_dataspaces), pairing_cb(cb)
121 {}
122 };
123
124 class Connection : public cxx::Ref_obj_list_item<Connection>
125 {
126 public:
127 explicit Connection(Device_mgr *mgr, cxx::Ref_ptr<Device_type> &&dev)
128 : _shutdown_state(Shutdown_type::Running),
129 _device(cxx::move(dev)),
130 _mgr(mgr)
131 {}
132
133 L4::Cap<void> cap() const
134 { return _interface ? _interface->obj_cap() : L4::Cap<void>(); }
135
136 void start_disk_scan(Errand::Callback const &callback)
137 {
138 _device->start_device_scan(
139 [=]()
140 {
141 scan_disk_partitions(callback, 0);
142 });
143 }
144
145 void unregister_interfaces(L4::Registry_iface *registry) const
146 {
147 if (_interface)
148 registry->unregister_obj(_interface.get());
149
150 for (auto *sub : _subs)
151 sub->unregister_interfaces(registry);
152 }
153
154 int create_interface_for(Pending_client *c, L4::Registry_iface *registry)
155 {
156 if (_shutdown_state != Shutdown_type::Running)
157 return -L4_EIO;
158
159 if (_interface)
160 return contains_device(c->device_id) ? -L4_EBUSY : -L4_ENODEV;
161
162 // check for match in partitions
163
164 bool busy = false;
165 for (auto *sub : _subs)
166 {
167 if (sub->_interface)
168 busy = true;
169
170 int ret = sub->create_interface_for(c, registry);
171
172 if (ret != -L4_ENODEV) // includes L4_EOK
173 return ret;
174 }
175
176 if (!match_hid(c->device_id))
177 return -L4_ENODEV;
178
179 if (busy)
180 return -L4_EBUSY;
181
182 auto clt = Device_factory_type::create_client(_device, c->num_ds,
183 c->readonly);
184
185 clt->add_trusted_dataspaces(c->trusted_dataspaces);
186 if (c->enable_trusted_ds_validation)
187 clt->enable_trusted_ds_validation();
188
189 if (c->gate.is_valid())
190 {
191 if (!clt->register_obj(registry, c->gate).is_valid())
192 return -L4_ENOMEM;
193 }
194 else
195 {
196 c->gate = L4::cap_reinterpret_cast<L4::Rcv_endpoint>(
197 clt->register_obj(registry));
198 if (!c->gate.is_valid())
199 return -L4_ENOMEM;
200 }
201
202 _mgr->_scheduler->add_client(clt.get());
203 _interface.reset(clt.release());
204
205 // Let it be known that the client and the device paired
206 if (c->pairing_cb)
207 c->pairing_cb(_device.get());
208 return L4_EOK;
209 }
210
211 void check_clients(L4::Registry_iface *registry)
212 {
213 if (_interface)
214 {
215 if (_interface->obj_cap() && !_interface->obj_cap().validate().label())
216 remove_client(registry);
217
218 return;
219 }
220
221 // Sub-devices only need to be checked when the parent device was free.
222 for (auto *sub : _subs)
223 sub->check_clients(registry);
224 }
225
227 void shutdown_event(Shutdown_type type)
228 {
229 // Set new shutdown state
230 _shutdown_state = type;
231 for (auto const &sub: _subs)
232 sub->shutdown_event(type);
233 if (_interface)
234 _interface->shutdown_event(type);
235 }
236
237 private:
249 template <typename T = Device_factory_type>
250 auto scan_disk_partitions(Errand::Callback const &callback, int)
251 -> decltype((T::create_partition)(cxx::Ref_ptr<Device_type>(), 0, Partition_info()), void())
252 {
253 auto reader = cxx::make_ref_obj<Partition_reader<Device_type>>(_device.get());
254 // The reference to reader will be captured in the lambda passed to
255 // reader's own read() method. At the same time, reader will store
256 // the reference to the lambda.
257 reader->read(
258 [=]()
259 {
260 l4_size_t sz = reader->table_size();
261
262 for (l4_size_t i = 1; i <= sz; ++i)
263 {
264 Partition_info info;
265 if (reader->get_partition(i, &info) < 0)
266 continue;
267
268 auto conn = cxx::make_ref_obj<Connection>(
269 _mgr,
270 Device_factory_type::create_partition(_device, i, info));
271 _subs.push_front(std::move(conn));
272 }
273
274 callback();
275
276 // Prolong the life-span of reader until we are sure the reader is
277 // not currently invoked (i.e. capture the last reference to it in
278 // an independent timeout callback).
279 Errand::schedule([reader](){}, 0);
280 });
281 }
282
290 template <typename T = Device_factory_type>
291 void scan_disk_partitions(Errand::Callback const &callback, long)
292 { callback(); }
293
297 void remove_client(L4::Registry_iface *registry)
298 {
299 assert(_interface);
300
301 // This operation is idempotent.
302 _interface->shutdown_event(Shutdown_type::Client_gone);
303
304 if (_interface->busy())
305 {
306 Dbg::trace().printf("Deferring dead client removal.\n");
307
308 // Cannot remove the client while it still has active I/O requests.
309 // This means that the device did not abort its inflight requests in
310 // its reset() callback. It is still desirable though to wait for
311 // those requests to finish and defer the dead client removal until
312 // later.
313 Errand::schedule([this, registry]() { remove_client(registry); },
314 10000);
315 return;
316 }
317
318 _interface->unregister_obj(registry);
319 _mgr->_scheduler->remove_client(_interface.get());
320 _interface.reset();
321 }
322
323 bool contains_device(std::string const &name) const
324 {
325 if (match_hid(name))
326 return true;
327
328 for (auto *sub : _subs)
329 if (sub->contains_device(name))
330 return true;
331
332 return false;
333 }
334
335 bool match_hid(std::string const &name) const
336 { return _device->match_hid(cxx::String(name.c_str(), name.length())); }
337
338
340 Shutdown_type _shutdown_state;
344 cxx::unique_ptr<Client_type> _interface;
347
348 Device_mgr *_mgr;
349 };
350
351public:
353 : _registry(registry)
354 {
355 _scheduler = cxx::make_unique<Scheduler_type>(registry);
356 }
357
358 virtual ~Device_mgr()
359 {
360 for (auto *c : _connpts)
361 c->unregister_interfaces(_registry);
362 }
363
364 int add_static_client(L4::Cap<L4::Rcv_endpoint> client, const char *device,
365 int partno, int num_ds, bool readonly = false,
366 Pairing_callback cb = nullptr,
367 bool enable_trusted_ds_validation = false,
368 std::shared_ptr<Ds_vector const> trusted_dataspaces
369 = nullptr)
370 {
371 char _buf[30];
372 const char *buf;
373
374 if (partno == 0)
375 {
376 Err().printf("Invalid partition number 0.\n");
377 return -L4_ENODEV;
378 }
379
380 if (partno != -1)
381 {
382 /* Could we avoid to make a string here and parsing this again
383 * deeper in the stack? */
384 snprintf(_buf, sizeof(_buf), "%s:%d", device, partno);
385 buf = _buf;
386 }
387 else
388 buf = device;
389
390 _pending_clients.emplace_back(client, buf, num_ds, readonly,
391 enable_trusted_ds_validation,
392 trusted_dataspaces, cb);
393
394 return L4_EOK;
395 }
396
397 int create_dynamic_client(std::string const &device, int partno, int num_ds,
398 L4::Cap<void> *cap, bool readonly = false,
399 Pairing_callback cb = nullptr,
400 bool enable_trusted_ds_validation = false,
401 std::shared_ptr<Ds_vector const> trusted_dataspaces
402 = nullptr)
403 {
404 Pending_client clt;
405
406 // Maximum number of dataspaces that can be registered.
407 clt.num_ds = num_ds;
408
409 clt.readonly = readonly;
410
411 clt.device_id = device;
412
413 clt.pairing_cb = cb;
414
415 clt.trusted_dataspaces = trusted_dataspaces;
416
417 clt.enable_trusted_ds_validation = enable_trusted_ds_validation;
418
419 if (partno > 0)
420 {
421 clt.device_id += ':';
422 clt.device_id += std::to_string(partno);
423 }
424
425 for (auto *c : _connpts)
426 {
427 int ret = c->create_interface_for(&clt, _registry);
428
429 if (ret == -L4_ENODEV)
430 continue;
431
432 if (ret < 0)
433 return ret;
434
435 // found the requested device
436 *cap = clt.gate;
437 return L4_EOK;
438 }
439
440 return -L4_ENODEV;
441 }
442
447 {
448 for (auto *c : _connpts)
449 c->check_clients(_registry);
450 }
451
452 void add_disk(cxx::Ref_ptr<Device_type> &&device, Errand::Callback const &callback)
453 {
454 auto conn = cxx::make_ref_obj<Connection>(this, std::move(device));
455
456 conn->start_disk_scan(
457 [=]()
458 {
459 _connpts.push_front(conn);
460 connect_static_clients(conn.get());
461 callback();
462 });
463 }
464
466 void shutdown_event(Shutdown_type type)
467 {
468 l4_assert(type != Client_gone);
469 l4_assert(type != Client_shutdown);
470
471 for (auto const &con : _connpts)
472 con->shutdown_event(type);
473 }
474
475private:
476 void connect_static_clients(Connection *con)
477 {
478 for (auto &c : _pending_clients)
479 {
480 Dbg::trace().printf("Checking existing client %s\n", c.device_id.c_str());
481 if (!c.gate.is_valid())
482 continue;
483
484 int ret = con->create_interface_for(&c, _registry);
485
486 if (ret == L4_EOK)
487 {
488 c.gate = L4::Cap<L4::Rcv_endpoint>();
489 // There might be other clients waiting for other partitions.
490 // Continue search.
491 continue;
492 }
493
494 if (ret != -L4_ENODEV)
495 break;
496 }
497 }
498
499
501 L4::Registry_iface *_registry;
505 std::vector<Pending_client> _pending_clients;
507 cxx::unique_ptr<Scheduler_type> _scheduler;
508};
509
510} // 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.
C++ interface for capabilities.
Definition capability.h:219
Abstract interface for object registries.
Definition ipc_epiface:334
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:82
Allocation free string class with explicit length field.
Definition string:42
Error helper.
Common factory related definitions.
unsigned int l4_size_t
Unsigned size type.
Definition l4int.h:35
@ L4_EBUSY
Object currently busy, try later.
Definition err.h:53
@ L4_ENODEV
No such thing.
Definition err.h:55
@ L4_EIO
I/O error.
Definition err.h:46
@ L4_EOK
Ok.
Definition err.h:43
@ L4_ENOMEM
No memory.
Definition err.h:50
Implementation of a list of ref-ptr-managed objects.
Information about a single partition.
Definition partition.h:31
Item for list linked via cxx::Ref_ptr with default refence counting.
Definition ref_ptr_list:27
#define l4_assert(expr)
Low-level assert.
Definition assert.h:43