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
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
75template <typename DEV, typename FACTORY = Simple_factory<DEV>>
77{
78 using Device_factory_type = FACTORY;
79 using Client_type = typename Device_factory_type::Client_type;
80 using Device_type = typename Device_factory_type::Device_type;
81
82 using Ds_vector = std::vector<L4::Cap<L4Re::Dataspace>>;
83
84 using Pairing_callback = std::function<void(Device_type *)>;
85
89 struct Pending_client
90 {
92 std::string device_id;
96 int num_ds;
98 bool readonly;
99
100 bool enable_trusted_ds_validation;
101
102 std::shared_ptr<Ds_vector const> trusted_dataspaces;
103
105 Pairing_callback pairing_cb;
106
107 Pending_client() = default;
108
109 Pending_client(L4::Cap<L4::Rcv_endpoint> g, std::string const &dev, int ds,
110 bool ro, bool enable_trusted_ds_validation,
111 std::shared_ptr<Ds_vector const> trusted_dataspaces,
112 Pairing_callback cb)
113 : device_id(dev), gate(g), num_ds(ds), readonly(ro),
114 enable_trusted_ds_validation(enable_trusted_ds_validation),
115 trusted_dataspaces(trusted_dataspaces), pairing_cb(cb)
116 {}
117 };
118
119 class Connection : public cxx::Ref_obj_list_item<Connection>
120 {
121 public:
122 explicit Connection(cxx::Ref_ptr<Device_type> &&dev)
123 : _shutdown_state(Shutdown_type::Running),
124 _device(cxx::move(dev))
125 {}
126
127 L4::Cap<void> cap() const
128 { return _interface ? _interface->obj_cap() : L4::Cap<void>(); }
129
130 void start_disk_scan(Errand::Callback const &callback)
131 {
132 _device->start_device_scan(
133 [=]()
134 {
135 scan_disk_partitions(callback, 0);
136 });
137 }
138
139 void unregister_interfaces(L4::Registry_iface *registry) const
140 {
141 if (_interface)
142 registry->unregister_obj(_interface.get());
143
144 for (auto *sub : _subs)
145 sub->unregister_interfaces(registry);
146 }
147
148 int create_interface_for(Pending_client *c, L4::Registry_iface *registry)
149 {
150 if (_shutdown_state != Shutdown_type::Running)
151 return -L4_EIO;
152
153 if (_interface)
154 return contains_device(c->device_id) ? -L4_EBUSY : -L4_ENODEV;
155
156 // check for match in partitions
157
158 bool busy = false;
159 for (auto *sub : _subs)
160 {
161 if (sub->_interface)
162 busy = true;
163
164 int ret = sub->create_interface_for(c, registry);
165
166 if (ret != -L4_ENODEV) // includes L4_EOK
167 return ret;
168 }
169
170 if (!match_hid(c->device_id))
171 return -L4_ENODEV;
172
173 if (busy)
174 return -L4_EBUSY;
175
176 auto clt = Device_factory_type::create_client(_device, c->num_ds,
177 c->readonly);
178
179 clt->add_trusted_dataspaces(c->trusted_dataspaces);
180 if (c->enable_trusted_ds_validation)
181 clt->enable_trusted_ds_validation();
182
183 if (c->gate.is_valid())
184 {
185 if (!clt->register_obj(registry, c->gate).is_valid())
186 return -L4_ENOMEM;
187 }
188 else
189 {
190 c->gate = L4::cap_reinterpret_cast<L4::Rcv_endpoint>(
191 clt->register_obj(registry));
192 if (!c->gate.is_valid())
193 return -L4_ENOMEM;
194 }
195
196 _interface.reset(clt.release());
197
198 // Let it be known that the client and the device paired
199 if (c->pairing_cb)
200 c->pairing_cb(_device.get());
201 return L4_EOK;
202 }
203
204 void check_clients(L4::Registry_iface *registry)
205 {
206 if (_interface)
207 {
208 if (_interface->obj_cap() && !_interface->obj_cap().validate().label())
209 remove_client(registry);
210
211 return;
212 }
213
214 // Sub-devices only need to be checked when the parent device was free.
215 for (auto *sub : _subs)
216 sub->check_clients(registry);
217 }
218
220 void shutdown_event(Shutdown_type type)
221 {
222 // Set new shutdown state
223 _shutdown_state = type;
224 for (auto const &sub: _subs)
225 sub->shutdown_event(type);
226 if (_interface)
227 _interface->shutdown_event(type);
228 }
229
230 private:
242 template <typename T = Device_factory_type>
243 auto scan_disk_partitions(Errand::Callback const &callback, int)
244 -> decltype((T::create_partition)(cxx::Ref_ptr<Device_type>(), 0, Partition_info()), void())
245 {
246 auto reader = cxx::make_ref_obj<Partition_reader<Device_type>>(_device.get());
247 // The reference to reader will be captured in the lambda passed to
248 // reader's own read() method. At the same time, reader will store
249 // the reference to the lambda.
250 reader->read(
251 [=]()
252 {
253 l4_size_t sz = reader->table_size();
254
255 for (l4_size_t i = 1; i <= sz; ++i)
256 {
257 Partition_info info;
258 if (reader->get_partition(i, &info) < 0)
259 continue;
260
261 auto conn = cxx::make_ref_obj<Connection>(
262 Device_factory_type::create_partition(_device, i, info));
263 _subs.push_front(std::move(conn));
264 }
265
266 callback();
267
268 // Prolong the life-span of reader until we are sure the reader is
269 // not currently invoked (i.e. capture the last reference to it in
270 // an independent timeout callback).
271 Errand::schedule([reader](){}, 0);
272 });
273 }
274
282 template <typename T = Device_factory_type>
283 void scan_disk_partitions(Errand::Callback const &callback, long)
284 { callback(); }
285
289 void remove_client(L4::Registry_iface *registry)
290 {
291 assert(_interface);
292
293 // This operation is idempotent.
294 _interface->shutdown_event(Shutdown_type::Client_gone);
295
296 if (_interface->busy())
297 {
298 Dbg::trace().printf("Deferring dead client removal.\n");
299
300 // Cannot remove the client while it still has active I/O requests.
301 // This means that the device did not abort its inflight requests in
302 // its reset() callback. It is still desirable though to wait for
303 // those requests to finish and defer the dead client removal until
304 // later.
305 Errand::schedule([this, registry]() { remove_client(registry); },
306 10000);
307 return;
308 }
309
310 _interface->unregister_obj(registry);
311 _interface.reset();
312 }
313
314 bool contains_device(std::string const &name) const
315 {
316 if (match_hid(name))
317 return true;
318
319 for (auto *sub : _subs)
320 if (sub->contains_device(name))
321 return true;
322
323 return false;
324 }
325
326 bool match_hid(std::string const &name) const
327 { return _device->match_hid(cxx::String(name.c_str(), name.length())); }
328
330 Shutdown_type _shutdown_state;
334 cxx::unique_ptr<Client_type> _interface;
337 };
338
339public:
341 : _registry(registry)
342 {}
343
344 virtual ~Device_mgr()
345 {
346 for (auto *c : _connpts)
347 c->unregister_interfaces(_registry);
348 }
349
350 int add_static_client(L4::Cap<L4::Rcv_endpoint> client, const char *device,
351 int partno, int num_ds, bool readonly = false,
352 Pairing_callback cb = nullptr,
353 bool enable_trusted_ds_validation = false,
354 std::shared_ptr<Ds_vector const> trusted_dataspaces
355 = nullptr)
356 {
357 char _buf[30];
358 const char *buf;
359
360 if (partno == 0)
361 {
362 Err().printf("Invalid partition number 0.\n");
363 return -L4_ENODEV;
364 }
365
366 if (partno != -1)
367 {
368 /* Could we avoid to make a string here and parsing this again
369 * deeper in the stack? */
370 snprintf(_buf, sizeof(_buf), "%s:%d", device, partno);
371 buf = _buf;
372 }
373 else
374 buf = device;
375
376 _pending_clients.emplace_back(client, buf, num_ds, readonly,
377 enable_trusted_ds_validation,
378 trusted_dataspaces, cb);
379
380 return L4_EOK;
381 }
382
383 int create_dynamic_client(std::string const &device, int partno, int num_ds,
384 L4::Cap<void> *cap, bool readonly = false,
385 Pairing_callback cb = nullptr,
386 bool enable_trusted_ds_validation = false,
387 std::shared_ptr<Ds_vector const> trusted_dataspaces
388 = nullptr)
389 {
390 Pending_client clt;
391
392 // Maximum number of dataspaces that can be registered.
393 clt.num_ds = num_ds;
394
395 clt.readonly = readonly;
396
397 clt.device_id = device;
398
399 clt.pairing_cb = cb;
400
401 clt.trusted_dataspaces = trusted_dataspaces;
402
403 clt.enable_trusted_ds_validation = enable_trusted_ds_validation;
404
405 if (partno > 0)
406 {
407 clt.device_id += ':';
408 clt.device_id += std::to_string(partno);
409 }
410
411 for (auto *c : _connpts)
412 {
413 int ret = c->create_interface_for(&clt, _registry);
414
415 if (ret == -L4_ENODEV)
416 continue;
417
418 if (ret < 0)
419 return ret;
420
421 // found the requested device
422 *cap = clt.gate;
423 return L4_EOK;
424 }
425
426 return -L4_ENODEV;
427 }
428
433 {
434 for (auto *c : _connpts)
435 c->check_clients(_registry);
436 }
437
438 void add_disk(cxx::Ref_ptr<Device_type> &&device, Errand::Callback const &callback)
439 {
440 auto conn = cxx::make_ref_obj<Connection>(std::move(device));
441
442 conn->start_disk_scan(
443 [=]()
444 {
445 _connpts.push_front(conn);
446 connect_static_clients(conn.get());
447 callback();
448 });
449 }
450
452 void shutdown_event(Shutdown_type type)
453 {
454 l4_assert(type != Client_gone);
455 l4_assert(type != Client_shutdown);
456
457 for (auto const &con : _connpts)
458 con->shutdown_event(type);
459 }
460
461private:
462 void connect_static_clients(Connection *con)
463 {
464 for (auto &c : _pending_clients)
465 {
466 Dbg::trace().printf("Checking existing client %s\n", c.device_id.c_str());
467 if (!c.gate.is_valid())
468 continue;
469
470 int ret = con->create_interface_for(&c, _registry);
471
472 if (ret == L4_EOK)
473 {
474 c.gate = L4::Cap<L4::Rcv_endpoint>();
475 // There might be other clients waiting for other partitions.
476 // Continue search.
477 continue;
478 }
479
480 if (ret != -L4_ENODEV)
481 break;
482 }
483 }
484
485
487 L4::Registry_iface *_registry;
491 std::vector<Pending_client> _pending_clients;
492};
493
494} // 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:222
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