L4Re Operating System Framework
Interface and Usage Documentation
Loading...
Searching...
No Matches
virtio-gpio-device
1// vi:set ft=cpp: -*- Mode: C++ -*-
2/*
3 * Copyright (C) 2025 Kernkonzept GmbH.
4 * Author(s): Christian Pötzsch <christian.poetzsch@kernkonzept.com>
5 *
6 * License: see LICENSE.spdx (in this directory or the directories above)
7 */
8
9#pragma once
10
11#include <l4/re/error_helper>
12#include <l4/sys/cxx/ipc_epiface>
13
14#include <l4/l4virtio/server/virtio>
15#include <l4/l4virtio/server/l4virtio>
16#include <l4/l4virtio/l4virtio>
17
18#include <l4/re/error_helper>
19#include <l4/re/util/object_registry>
20#include <l4/re/util/br_manager>
21#include <l4/sys/cxx/ipc_epiface>
22#include <l4/cxx/pair>
23
24#include <vector>
25#include <memory>
26
27namespace L4virtio {
28namespace Svr {
29
30/* GPIO message status types */
31enum : l4_uint8_t
32{
33 Gpio_status_ok = 0x0,
34 Gpio_status_err = 0x1
35};
36
37/* GPIO message types */
38enum : l4_uint8_t
39{
40 Gpio_msg_get_line_names = 0x1,
41 Gpio_msg_get_direction = 0x2,
42 Gpio_msg_set_direction = 0x3,
43 Gpio_msg_get_value = 0x4,
44 Gpio_msg_set_value = 0x5,
45 Gpio_msg_set_irq_type = 0x6
46};
47
48/* GPIO value types */
49enum : l4_uint8_t
50{
51 Gpio_low = 0x0,
52 Gpio_high = 0x1
53};
54
55/* GPIO direction types */
56enum : l4_uint8_t
57{
58 Gpio_direction_none = 0x0,
59 Gpio_direction_out = 0x1,
60 Gpio_direction_in = 0x2
61};
62
63/* GPIO interrupt types */
64enum : l4_uint8_t
65{
66 Gpio_irq_type_none = 0x0,
67 Gpio_irq_type_edge_rising = 0x1,
68 Gpio_irq_type_edge_falling = 0x2,
69 Gpio_irq_type_edge_both = 0x3,
70 Gpio_irq_type_level_high = 0x4,
71 Gpio_irq_type_level_low = 0x8
72};
73
74/* GPIO interrupt status types */
75enum : l4_uint8_t
76{
77 Gpio_irq_status_invalid = 0x0,
78 Gpio_irq_status_valid = 0x1
79};
80
81struct Gpio_request
82{
83 l4_uint16_t type;
84 l4_uint16_t gpio;
85 l4_uint32_t value;
86};
87static_assert(sizeof(Gpio_request) == 8,
88 "Gpio_request contains padding bytes.");
89
90struct Gpio_response
91{
92 l4_uint8_t status;
93 l4_uint8_t value;
94};
95static_assert(sizeof(Gpio_response) == 2,
96 "Gpio_response contains padding bytes.");
97
98struct Gpio_irq_request
99{
100 l4_uint16_t gpio;
101};
102static_assert(sizeof(Gpio_irq_request) == 2,
103 "Gpio_irq_request contains padding bytes.");
104
105struct Gpio_irq_response
106{
107 l4_uint8_t status;
108};
109static_assert(sizeof(Gpio_irq_response) == 1,
110 "Gpio_irq_response contains padding bytes.");
111
112struct Gpio_request_msg
113{
114 struct Gpio_request *in_hdr = nullptr;
115 struct Gpio_response *out_hdr = nullptr;
116};
117
118struct Gpio_irq_request_msg
119{
120 struct Gpio_irq_request *in_hdr = nullptr;
121 struct Gpio_irq_response *out_hdr = nullptr;
122};
123
140template <typename Request_handler,
141 typename Epiface = L4virtio::Device>
142class Virtio_gpio : public L4virtio::Svr::Device,
143 public L4::Epiface_t<Virtio_gpio<Request_handler,
144 Epiface>,
145 Epiface>
146{
147private:
148 enum
149 {
150 queue_size = 128,
151 };
152
153public:
154 using Gpio_request_handler = Request_handler;
155
166 struct Irq_handler
167 {
168 Irq_handler(Virtio_gpio *gpio, L4virtio::Svr::Virtqueue *q,
170 l4_uint8_t *status)
171 : _gpio(gpio), _q(q), _head(head), _status(status)
172 {}
173
174 void handle_irq()
175 {
176 *_status = Gpio_irq_status_valid;
177 _q->finish(_head, _gpio, sizeof(Gpio_irq_response));
178 }
179
180 void cancel()
181 {
182 *_status = Gpio_irq_status_invalid;
183 _q->finish(_head, _gpio, sizeof(Gpio_irq_response));
184 }
185
186 private:
187 Virtio_gpio *_gpio;
190 l4_uint8_t *_status;
191 };
192
198 struct Host_irq : L4::Irqep_t<Host_irq>
199 {
200 explicit Host_irq(Virtio_gpio *gpio)
201 : L4::Irqep_t<Host_irq>(), _gpio(gpio) {}
202
203 void handle_irq()
204 { _gpio->handle_queue(); }
205
206 private:
207 Virtio_gpio *_gpio;
208 };
209
213 struct Request_processor : L4virtio::Svr::Request_processor
214 {
215 Request_processor(L4virtio::Svr::Virtqueue *q, Gpio_request_handler *hndlr,
216 Virtio_gpio *gpio)
217 : _q(q), _req_handler(hndlr), _gpio(gpio), _head(), _req()
218 {}
219
220 protected:
221 bool init_queue()
222 {
223 auto r = _q->next_avail();
224
225 if (L4_UNLIKELY(!r))
226 return false;
227
228 _head = start(_gpio->mem_info(), r, &_req);
229
230 return true;
231 }
232
240 template <typename T>
242 {
243 T req;
244 req.in_hdr = reinterpret_cast<decltype(T::in_hdr)>(_req.pos);
245
246 // Need the next output buffer.
247 if (!next(_gpio->mem_info(), &_req) || !current_flags().write())
248 return req;
249
250 req.out_hdr = reinterpret_cast<decltype(T::out_hdr)>(_req.pos);
251
252 return req;
253 }
254
256 {
258 {
259 pos = nullptr;
260 left = 0;
261 }
262 // This constructor is called from within start, so make it available.
263 Data_buffer(L4virtio::Svr::Driver_mem_region const *r,
266 {
267 pos = static_cast<char *>(r->local(d.addr));
268 left = d.len;
269 }
270
271 };
272
274 Gpio_request_handler *_req_handler;
275 Virtio_gpio *_gpio;
277 Data_buffer _req;
278 };
279
280 // Handler for the gpio request queue
281 struct Req_processor : Request_processor
282 {
283 using Request_processor::Request_processor;
284
285 void handle_request()
286 {
287 if (!this->_head)
288 if (!this->init_queue())
289 return;
290
291 using Consumed_entry =
292 cxx::Pair<L4virtio::Svr::Virtqueue::Head_desc, l4_uint32_t>;
293 std::vector<Consumed_entry> consumed;
294
295 for (;;)
296 {
297 Gpio_request_msg req = this->template get_request<Gpio_request_msg>();
298 if (!req.in_hdr || !req.out_hdr)
299 {
300 this->_gpio->device_error();
301 break;
302 }
303
304 // default response is error
305 req.out_hdr->status = Gpio_status_err;
306 switch (req.in_hdr->type)
307 {
308 case Gpio_msg_get_line_names:
309 // we don't support this
310 break;
311 case Gpio_msg_get_direction:
312 {
313 if (this->_req_handler->get_direction(req.in_hdr->gpio,
314 &req.out_hdr->value))
315 req.out_hdr->status = Gpio_status_ok;
316 break;
317 }
318 case Gpio_msg_set_direction:
319 {
320 if (req.in_hdr->value == Gpio_direction_none ||
321 req.in_hdr->value == Gpio_direction_out ||
322 req.in_hdr->value == Gpio_direction_in)
323 {
324 if (this->_req_handler->set_direction(req.in_hdr->gpio,
325 req.in_hdr->value))
326 req.out_hdr->status = Gpio_status_ok;
327 }
328 break;
329 }
330 case Gpio_msg_get_value:
331 {
332 if (this->_req_handler->get_value(req.in_hdr->gpio,
333 &req.out_hdr->value))
334 req.out_hdr->status = Gpio_status_ok;
335 break;
336 }
337 case Gpio_msg_set_value:
338 {
339 if (req.in_hdr->value == Gpio_low ||
340 req.in_hdr->value == Gpio_high)
341 {
342 if (this->_req_handler->set_value(req.in_hdr->gpio,
343 req.in_hdr->value))
344 req.out_hdr->status = Gpio_status_ok;
345 }
346 break;
347 }
348 case Gpio_msg_set_irq_type:
349 {
350 if (req.in_hdr->value == Gpio_irq_type_none ||
351 req.in_hdr->value == Gpio_irq_type_edge_rising ||
352 req.in_hdr->value == Gpio_irq_type_edge_falling ||
353 req.in_hdr->value == Gpio_irq_type_edge_both ||
354 req.in_hdr->value == Gpio_irq_type_level_high ||
355 req.in_hdr->value == Gpio_irq_type_level_low)
356 {
357 if (this->_req_handler->set_irq_type(req.in_hdr->gpio,
358 req.in_hdr->value))
359 req.out_hdr->status = Gpio_status_ok;
360 }
361 break;
362 }
363 }
364
365 // Save the descriptors which are done
366 consumed.emplace_back(this->_head, sizeof(Gpio_response));
367
368 if (!this->init_queue())
369 break;
370 }
371
372 // Put all finished descriptors back into the used list and notify the
373 // driver.
374 this->_q->finish(consumed.begin(), consumed.end(), this->_gpio);
375
376 this->_head = Virtqueue::Head_desc();
377 }
378 };
379
380 // Handler for the gpio event queue
381 struct Irq_req_processor : Request_processor
382 {
383 using Request_processor::Request_processor;
384
385 void handle_request()
386 {
387 if (!this->_head)
388 if (!this->init_queue())
389 return;
390
391 for (;;)
392 {
393 // There is only one type of message in the event queue. This
394 // basically arms (unmask) the irq.
395 Gpio_irq_request_msg req = this->template get_request<Gpio_irq_request_msg>();
396 if (!req.in_hdr || !req.out_hdr)
397 {
398 this->_gpio->device_error();
399 break;
400 }
401
402 // Save the virtio descriptor for this event in an extra Irq_handler
403 // object. The descriptor will be returned to the client when the irq
404 // is triggered or canceled.
405 this->_req_handler->enable_irq(req.in_hdr->gpio,
406 std::make_shared<Irq_handler>(this->_gpio,
407 this->_q,
408 this->_head,
409 &req.out_hdr->status));
410
411 if (!this->init_queue())
412 break;
413 }
414
415 this->_head = Virtqueue::Head_desc();
416 }
417 };
418
419 struct Features : public L4virtio::Svr::Dev_config::Features
420 {
421 Features() = default;
422 Features(l4_uint32_t raw) : L4virtio::Svr::Dev_config::Features(raw) {}
423
424 CXX_BITFIELD_MEMBER(0, 0, gpio_f_irq, raw);
425 };
426
427 struct Gpio_config_space
428 {
429 l4_uint16_t ngpio;
430 l4_uint8_t padding[2];
431 l4_uint32_t gpio_names_size;
432 };
433
434 Virtio_gpio(Gpio_request_handler *hndlr,
435 L4Re::Util::Object_registry *registry,
436 l4_uint16_t ngpio)
437 : L4virtio::Svr::Device(&_dev_config),
438 _registry(registry),
439 _dev_config(L4VIRTIO_VENDOR_KK, L4VIRTIO_ID_GPIO, 2),
440 _host_irq(this),
441 _req_processor(&_q[0], hndlr, this),
442 _irq_req_processor(&_q[1], hndlr, this)
443 {
444 init_mem_info(2);
445
446 for (size_t i = 0; i < 2; i++)
447 {
448 reset_queue_config(i, queue_size);
449 setup_queue(&_q[i], i, queue_size);
450 }
451
452 registry->register_irq_obj(&_host_irq);
453
454 Features hf(0);
455 hf.ring_indirect_desc() = true;
456 hf.gpio_f_irq() = true;
457 _dev_config.host_features(0) = hf.raw;
458 _dev_config.set_host_feature(L4VIRTIO_FEATURE_VERSION_1);
459
460 // fill gpio config space
461 _dev_config.priv_config()->ngpio = ngpio;
462 _dev_config.priv_config()->gpio_names_size = 0; // not supported
463
464 _dev_config.reset_hdr();
465 }
466
467 ~Virtio_gpio()
468 { _registry->unregister_obj(&_host_irq); }
469
470 void notify_queue(L4virtio::Svr::Virtqueue *queue)
471 {
472 if (queue->no_notify_guest())
473 return;
474
475 _dev_config.add_irq_status(L4VIRTIO_IRQ_STATUS_VRING);
476 L4Re::chkipc(_notify_guest_irq->trigger(), "trigger guest irq");
477 }
478
479 void handle_queue()
480 {
481 _req_processor.handle_request();
482 _irq_req_processor.handle_request();
483 }
484
485 void reset() override
486 {}
487
488 bool check_queues() override
489 { return true; }
490
491 int reconfig_queue(unsigned idx) override
492 {
493 if (idx >= sizeof(_q) / sizeof(_q[0]))
494 return -L4_ERANGE;
495
496 return setup_queue(_q + idx, idx, queue_size);
497 }
498
500 {
501 _dev_config.add_irq_status(L4VIRTIO_IRQ_STATUS_CONFIG);
502 _notify_guest_irq->trigger();
503 }
504
506 { return L4::Epiface::server_iface(); }
507
508 long op_set_status(L4virtio::Device::Rights r, unsigned status)
509 { return L4virtio::Svr::Device::op_set_status(r, status); }
510
511 long op_config_queue(L4virtio::Device::Rights r, unsigned queue)
512 { return L4virtio::Svr::Device::op_config_queue(r, queue); }
513
514 long op_device_config(L4virtio::Device::Rights r,
515 L4::Ipc::Cap<L4Re::Dataspace> &config_ds,
516 l4_addr_t &ds_offset)
517 { return L4virtio::Svr::Device::op_device_config(r, config_ds, ds_offset); }
518
520 { return L4::cap_cast<L4::Irq>(_host_irq.obj_cap()); }
521
523 {
524 _notify_guest_irq = L4Re::chkcap
525 (server_iface()->template rcv_cap<L4::Irq>(0));
526
527 L4Re::chksys(server_iface()->realloc_rcv_cap(0));
528 }
529
530private:
532 L4virtio::Svr::Dev_config_t<Gpio_config_space> _dev_config;
533 Host_irq _host_irq;
534 L4::Cap<L4::Irq> _notify_guest_irq;
536 Req_processor _req_processor;
537 Irq_req_processor _irq_req_processor;
538};
539
540} // namespace Svr
541} // namespace L4virtio
A registry that manages server objects and their attached IPC gates for a single server loop for a sp...
L4::Cap< L4::Irq > register_irq_obj(L4::Epiface *o) override
Register a handler for an interrupt.
C++ interface for capabilities.
Definition capability.h:224
Interface for server-loop related functions.
Definition ipc_epiface:37
bool setup_queue(Virtqueue *q, unsigned qn, unsigned num_max)
Definition l4virtio:1047
void reset_queue_config(unsigned idx, unsigned num_max, bool inc_generation=false)
Definition l4virtio:1002
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
Virtqueue::Desc::Flags current_flags() const
Get the flags of the currently processed descriptor.
Definition virtio:545
void start(DESC_MAN *dm, Virtqueue *ring, Virtqueue::Head_desc const &request, ARGS... args)
Start processing a new request.
Definition virtio:501
void reset() override
reset callback, called for doing a device reset
int reconfig_queue(unsigned idx) override
callback for client queue-config request
bool check_queues() override
callback for checking if the queues at DRIVER_OK transition
void trigger_driver_config_irq() override
callback for triggering configuration change notification IRQ
L4::Cap< L4::Irq > device_notify_irq() const override
callback to gather the device notification IRQ (old-style)
void register_single_driver_irq() override
callback for registering a single guest IRQ for all queues (old-style)
VIRTIO request, essentially a descriptor from the available ring.
Definition virtio:94
Virtqueue implementation for the device.
Definition virtio:88
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
bool no_notify_guest() const
Get the no IRQ flag of this queue.
Definition virtqueue:414
Error helper.
unsigned long l4_addr_t
Address type.
Definition l4int.h:34
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
@ L4_ERANGE
Range error.
Definition err.h:48
#define L4_UNLIKELY(x)
Expression is unlikely to execute.
Definition compiler.h:284
@ L4VIRTIO_FEATURE_VERSION_1
Virtio protocol version 1 supported. Must be 1 for L4virtio.
Definition virtio.h:101
@ L4VIRTIO_ID_GPIO
Gpio device.
Definition virtio.h:81
@ L4VIRTIO_IRQ_STATUS_VRING
VRING IRQ pending flag.
Definition virtio.h:112
@ L4VIRTIO_IRQ_STATUS_CONFIG
CONFIG IRQ pending flag.
Definition virtio.h:113
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_msgtag_t chkipc(l4_msgtag_t tag, char const *extra="", l4_utcb_t *utcb=l4_utcb())
Test a message tag for IPC errors.
Definition error_helper:180
Cap< T > cap_cast(Cap< F > const &c) noexcept
static_cast for capabilities.
Definition capability.h:416
L4-VIRTIO Transport C++ API.
Definition l4virtio:26
Pair implementation.
Epiface implementation for Kobject-based interface implementations.
Definition ipc_epiface:504
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
Abstract data buffer.
Definition virtio:307
l4_uint32_t left
Bytes left in buffer.
Definition virtio:309
char * pos
Current buffer position.
Definition virtio:308
Generic handler for the Virtio requests.
T get_request()
The driver prepares the GPIO request in two data parts: 1st: in_hdr 2rd: out_hdr.