Loading...
Searching...
No Matches
virtio-gpio-device
// vi:set ft=cpp: -*- Mode: C++ -*-
/*
* Copyright (C) 2025 Kernkonzept GmbH.
* Author(s): Christian Pötzsch <christian.poetzsch@kernkonzept.com>
*
* License: see LICENSE.spdx (in this directory or the directories above)
*/
#pragma once
#include <l4/re/error_helper>
#include <l4/sys/cxx/ipc_epiface>
#include <l4/l4virtio/server/virtio>
#include <l4/l4virtio/server/l4virtio>
#include <l4/l4virtio/l4virtio>
#include <l4/re/error_helper>
#include <l4/re/util/object_registry>
#include <l4/re/util/br_manager>
#include <l4/sys/cxx/ipc_epiface>
#include <l4/cxx/pair>
#include <vector>
#include <memory>
namespace L4virtio {
namespace Svr {
/* GPIO message status types */
enum : l4_uint8_t
{
Gpio_status_ok = 0x0,
Gpio_status_err = 0x1
};
/* GPIO message types */
enum : l4_uint8_t
{
Gpio_msg_get_line_names = 0x1,
Gpio_msg_get_direction = 0x2,
Gpio_msg_set_direction = 0x3,
Gpio_msg_get_value = 0x4,
Gpio_msg_set_value = 0x5,
Gpio_msg_set_irq_type = 0x6
};
/* GPIO value types */
enum : l4_uint8_t
{
Gpio_low = 0x0,
Gpio_high = 0x1
};
/* GPIO direction types */
enum : l4_uint8_t
{
Gpio_direction_none = 0x0,
Gpio_direction_out = 0x1,
Gpio_direction_in = 0x2
};
/* GPIO interrupt types */
enum : l4_uint8_t
{
Gpio_irq_type_none = 0x0,
Gpio_irq_type_edge_rising = 0x1,
Gpio_irq_type_edge_falling = 0x2,
Gpio_irq_type_edge_both = 0x3,
Gpio_irq_type_level_high = 0x4,
Gpio_irq_type_level_low = 0x8
};
/* GPIO interrupt status types */
enum : l4_uint8_t
{
Gpio_irq_status_invalid = 0x0,
Gpio_irq_status_valid = 0x1
};
struct Gpio_request
{
l4_uint16_t type;
l4_uint16_t gpio;
l4_uint32_t value;
};
static_assert(sizeof(Gpio_request) == 8,
"Gpio_request contains padding bytes.");
struct Gpio_response
{
l4_uint8_t status;
l4_uint8_t value;
};
static_assert(sizeof(Gpio_response) == 2,
"Gpio_response contains padding bytes.");
struct Gpio_irq_request
{
l4_uint16_t gpio;
};
static_assert(sizeof(Gpio_irq_request) == 2,
"Gpio_irq_request contains padding bytes.");
struct Gpio_irq_response
{
l4_uint8_t status;
};
static_assert(sizeof(Gpio_irq_response) == 1,
"Gpio_irq_response contains padding bytes.");
struct Gpio_request_msg
{
struct Gpio_request *in_hdr = nullptr;
struct Gpio_response *out_hdr = nullptr;
};
struct Gpio_irq_request_msg
{
struct Gpio_irq_request *in_hdr = nullptr;
struct Gpio_irq_response *out_hdr = nullptr;
};
template <typename Request_handler,
typename Epiface = L4virtio::Device>
class Virtio_gpio : public L4virtio::Svr::Device,
public L4::Epiface_t<Virtio_gpio<Request_handler,
Epiface>,
Epiface>
{
private:
enum
{
queue_size = 128,
};
public:
using Gpio_request_handler = Request_handler;
struct Irq_handler
{
Irq_handler(Virtio_gpio *gpio, L4virtio::Svr::Virtqueue *q,
L4virtio::Svr::Virtqueue::Head_desc const &head,
l4_uint8_t *status)
: _gpio(gpio), _q(q), _head(head), _status(status)
{}
void handle_irq()
{
*_status = Gpio_irq_status_valid;
_q->finish(_head, _gpio, sizeof(Gpio_irq_response));
}
void cancel()
{
*_status = Gpio_irq_status_invalid;
_q->finish(_head, _gpio, sizeof(Gpio_irq_response));
}
private:
Virtio_gpio *_gpio;
L4virtio::Svr::Virtqueue *_q;
L4virtio::Svr::Virtqueue::Head_desc _head;
l4_uint8_t *_status;
};
struct Host_irq : L4::Irqep_t<Host_irq>
{
explicit Host_irq(Virtio_gpio *gpio)
: L4::Irqep_t<Host_irq>(), _gpio(gpio) {}
void handle_irq()
{ _gpio->handle_queue(); }
private:
Virtio_gpio *_gpio;
};
struct Request_processor : L4virtio::Svr::Request_processor
{
Request_processor(L4virtio::Svr::Virtqueue *q, Gpio_request_handler *hndlr,
Virtio_gpio *gpio)
: _q(q), _req_handler(hndlr), _gpio(gpio), _head(), _req()
{}
protected:
bool init_queue()
{
auto r = _q->next_avail();
if (L4_UNLIKELY(!r))
return false;
_head = start(_gpio->mem_info(), r, &_req);
return true;
}
template <typename T>
T get_request()
{
T req;
req.in_hdr = reinterpret_cast<decltype(T::in_hdr)>(_req.pos);
// Need the next output buffer.
if (!next(_gpio->mem_info(), &_req) || !current_flags().write())
return req;
req.out_hdr = reinterpret_cast<decltype(T::out_hdr)>(_req.pos);
return req;
}
struct Data_buffer : public L4virtio::Svr::Data_buffer
{
Data_buffer()
{
pos = nullptr;
left = 0;
}
// This constructor is called from within start, so make it available.
Data_buffer(L4virtio::Svr::Driver_mem_region const *r,
L4virtio::Svr::Virtqueue::Desc const &d,
L4virtio::Svr::Request_processor const *)
{
pos = static_cast<char *>(r->local(d.addr));
left = d.len;
}
};
L4virtio::Svr::Virtqueue *_q;
Gpio_request_handler *_req_handler;
Virtio_gpio *_gpio;
L4virtio::Svr::Virtqueue::Head_desc _head;
Data_buffer _req;
};
// Handler for the gpio request queue
struct Req_processor : Request_processor
{
using Request_processor::Request_processor;
void handle_request()
{
if (!this->_head)
if (!this->init_queue())
return;
using Consumed_entry =
cxx::Pair<L4virtio::Svr::Virtqueue::Head_desc, l4_uint32_t>;
std::vector<Consumed_entry> consumed;
for (;;)
{
Gpio_request_msg req = this->template get_request<Gpio_request_msg>();
if (!req.in_hdr || !req.out_hdr)
{
this->_gpio->device_error();
break;
}
// default response is error
req.out_hdr->status = Gpio_status_err;
switch (req.in_hdr->type)
{
case Gpio_msg_get_line_names:
// we don't support this
break;
case Gpio_msg_get_direction:
{
if (this->_req_handler->get_direction(req.in_hdr->gpio,
&req.out_hdr->value))
req.out_hdr->status = Gpio_status_ok;
break;
}
case Gpio_msg_set_direction:
{
if (req.in_hdr->value == Gpio_direction_none ||
req.in_hdr->value == Gpio_direction_out ||
req.in_hdr->value == Gpio_direction_in)
{
if (this->_req_handler->set_direction(req.in_hdr->gpio,
req.in_hdr->value))
req.out_hdr->status = Gpio_status_ok;
}
break;
}
case Gpio_msg_get_value:
{
if (this->_req_handler->get_value(req.in_hdr->gpio,
&req.out_hdr->value))
req.out_hdr->status = Gpio_status_ok;
break;
}
case Gpio_msg_set_value:
{
if (req.in_hdr->value == Gpio_low ||
req.in_hdr->value == Gpio_high)
{
if (this->_req_handler->set_value(req.in_hdr->gpio,
req.in_hdr->value))
req.out_hdr->status = Gpio_status_ok;
}
break;
}
case Gpio_msg_set_irq_type:
{
if (req.in_hdr->value == Gpio_irq_type_none ||
req.in_hdr->value == Gpio_irq_type_edge_rising ||
req.in_hdr->value == Gpio_irq_type_edge_falling ||
req.in_hdr->value == Gpio_irq_type_edge_both ||
req.in_hdr->value == Gpio_irq_type_level_high ||
req.in_hdr->value == Gpio_irq_type_level_low)
{
if (this->_req_handler->set_irq_type(req.in_hdr->gpio,
req.in_hdr->value))
req.out_hdr->status = Gpio_status_ok;
}
break;
}
}
// Save the descriptors which are done
consumed.emplace_back(this->_head, sizeof(Gpio_response));
if (!this->init_queue())
break;
}
// Put all finished descriptors back into the used list and notify the
// driver.
this->_q->finish(consumed.begin(), consumed.end(), this->_gpio);
this->_head = Virtqueue::Head_desc();
}
};
// Handler for the gpio event queue
struct Irq_req_processor : Request_processor
{
using Request_processor::Request_processor;
void handle_request()
{
if (!this->_head)
if (!this->init_queue())
return;
for (;;)
{
// There is only one type of message in the event queue. This
// basically arms (unmask) the irq.
Gpio_irq_request_msg req = this->template get_request<Gpio_irq_request_msg>();
if (!req.in_hdr || !req.out_hdr)
{
this->_gpio->device_error();
break;
}
// Save the virtio descriptor for this event in an extra Irq_handler
// object. The descriptor will be returned to the client when the irq
// is triggered or canceled.
this->_req_handler->enable_irq(req.in_hdr->gpio,
std::make_shared<Irq_handler>(this->_gpio,
this->_q,
this->_head,
&req.out_hdr->status));
if (!this->init_queue())
break;
}
this->_head = Virtqueue::Head_desc();
}
};
struct Features : public L4virtio::Svr::Dev_config::Features
{
Features() = default;
Features(l4_uint32_t raw) : L4virtio::Svr::Dev_config::Features(raw) {}
CXX_BITFIELD_MEMBER(0, 0, gpio_f_irq, raw);
};
struct Gpio_config_space
{
l4_uint16_t ngpio;
l4_uint8_t padding[2];
l4_uint32_t gpio_names_size;
};
Virtio_gpio(Gpio_request_handler *hndlr,
L4Re::Util::Object_registry *registry,
l4_uint16_t ngpio)
: L4virtio::Svr::Device(&_dev_config),
_registry(registry),
_dev_config(L4VIRTIO_VENDOR_KK, L4VIRTIO_ID_GPIO, 2),
_host_irq(this),
_req_processor(&_q[0], hndlr, this),
_irq_req_processor(&_q[1], hndlr, this)
{
init_mem_info(2);
for (size_t i = 0; i < 2; i++)
{
reset_queue_config(i, queue_size);
setup_queue(&_q[i], i, queue_size);
}
registry->register_irq_obj(&_host_irq);
Features hf(0);
hf.ring_indirect_desc() = true;
hf.gpio_f_irq() = true;
_dev_config.host_features(0) = hf.raw;
_dev_config.set_host_feature(L4VIRTIO_FEATURE_VERSION_1);
// fill gpio config space
_dev_config.priv_config()->ngpio = ngpio;
_dev_config.priv_config()->gpio_names_size = 0; // not supported
_dev_config.reset_hdr();
}
~Virtio_gpio()
{ _registry->unregister_obj(&_host_irq); }
void notify_queue(L4virtio::Svr::Virtqueue *queue)
{
if (queue->no_notify_guest())
return;
_dev_config.add_irq_status(L4VIRTIO_IRQ_STATUS_VRING);
L4Re::chkipc(_notify_guest_irq->trigger(), "trigger guest irq");
}
void handle_queue()
{
_req_processor.handle_request();
_irq_req_processor.handle_request();
}
void reset() override
{}
bool check_queues() override
{ return true; }
int reconfig_queue(unsigned idx) override
{
if (idx >= sizeof(_q) / sizeof(_q[0]))
return -L4_ERANGE;
return setup_queue(_q + idx, idx, queue_size);
}
void trigger_driver_config_irq() override
{
_dev_config.add_irq_status(L4VIRTIO_IRQ_STATUS_CONFIG);
_notify_guest_irq->trigger();
}
L4::Ipc_svr::Server_iface *server_iface() const override
{ return L4::Epiface::server_iface(); }
long op_set_status(L4virtio::Device::Rights r, unsigned status)
{ return L4virtio::Svr::Device::op_set_status(r, status); }
long op_config_queue(L4virtio::Device::Rights r, unsigned queue)
{ return L4virtio::Svr::Device::op_config_queue(r, queue); }
long op_device_config(L4virtio::Device::Rights r,
L4::Ipc::Cap<L4Re::Dataspace> &config_ds,
l4_addr_t &ds_offset)
{ return L4virtio::Svr::Device::op_device_config(r, config_ds, ds_offset); }
L4::Cap<L4::Irq> device_notify_irq() const override
{ return L4::cap_cast<L4::Irq>(_host_irq.obj_cap()); }
void register_single_driver_irq() override
{
_notify_guest_irq = L4Re::chkcap
(server_iface()->template rcv_cap<L4::Irq>(0));
L4Re::chksys(server_iface()->realloc_rcv_cap(0));
}
private:
L4Re::Util::Object_registry *_registry;
L4virtio::Svr::Dev_config_t<Gpio_config_space> _dev_config;
Host_irq _host_irq;
L4::Cap<L4::Irq> _notify_guest_irq;
L4virtio::Svr::Virtqueue _q[2];
Req_processor _req_processor;
Irq_req_processor _irq_req_processor;
};
} // namespace Svr
} // namespace L4virtio
L4Re::Util::Object_registry
A registry that manages server objects and their attached IPC gates for a single server loop for a sp...
Definition object_registry:44
L4Re::Util::Object_registry::register_irq_obj
L4::Cap< L4::Irq > register_irq_obj(L4::Epiface *o) override
Register a handler for an interrupt.
Definition object_registry:227
L4::Cap
C++ interface for capabilities.
Definition capability.h:224
L4::Ipc_svr::Server_iface
Interface for server-loop related functions.
Definition ipc_epiface:37
L4virtio::Svr::Device_t< No_custom_data >::setup_queue
bool setup_queue(Virtqueue *q, unsigned qn, unsigned num_max)
Definition l4virtio:1047
L4virtio::Svr::Device_t< No_custom_data >::init_mem_info
void init_mem_info(unsigned num)
Definition l4virtio:1012
L4virtio::Svr::Device_t< No_custom_data >::reset_queue_config
void reset_queue_config(unsigned idx, unsigned num_max, bool inc_generation=false)
Definition l4virtio:1002
L4virtio::Svr::Driver_mem_region_t::local
T * local(Ptr< T > p) const
Get the local address for driver address p.
Definition l4virtio:616
L4virtio::Svr::Request_processor
Encapsulate the state for processing a VIRTIO request.
Definition virtio:473
L4virtio::Svr::Request_processor::next
bool next(DESC_MAN *dm, ARGS... args)
Switch to the next descriptor in a descriptor chain.
Definition virtio:570
L4virtio::Svr::Request_processor::current_flags
Virtqueue::Desc::Flags current_flags() const
Get the flags of the currently processed descriptor.
Definition virtio:545
L4virtio::Svr::Request_processor::start
void start(DESC_MAN *dm, Virtqueue *ring, Virtqueue::Head_desc const &request, ARGS... args)
Start processing a new request.
Definition virtio:501
L4virtio::Svr::Virtio_gpio::reset
void reset() override
reset callback, called for doing a device reset
Definition virtio-gpio-device:485
L4virtio::Svr::Virtio_gpio::reconfig_queue
int reconfig_queue(unsigned idx) override
callback for client queue-config request
Definition virtio-gpio-device:491
L4virtio::Svr::Virtio_gpio::check_queues
bool check_queues() override
callback for checking if the queues at DRIVER_OK transition
Definition virtio-gpio-device:488
L4virtio::Svr::Virtio_gpio::trigger_driver_config_irq
void trigger_driver_config_irq() override
callback for triggering configuration change notification IRQ
Definition virtio-gpio-device:499
L4virtio::Svr::Virtio_gpio::device_notify_irq
L4::Cap< L4::Irq > device_notify_irq() const override
callback to gather the device notification IRQ (old-style)
Definition virtio-gpio-device:519
L4virtio::Svr::Virtio_gpio::register_single_driver_irq
void register_single_driver_irq() override
callback for registering a single guest IRQ for all queues (old-style)
Definition virtio-gpio-device:522
L4virtio::Svr::Virtqueue::Head_desc
VIRTIO request, essentially a descriptor from the available ring.
Definition virtio:94
L4virtio::Svr::Virtqueue
Virtqueue implementation for the device.
Definition virtio:88
L4virtio::Virtqueue::Desc
Descriptor in the descriptor table.
Definition virtqueue:87
L4virtio::Virtqueue::Desc::len
l4_uint32_t len
Length of described buffer.
Definition virtqueue:109
L4virtio::Virtqueue::Desc::addr
Ptr< void > addr
Address stored in descriptor.
Definition virtqueue:108
L4virtio::Virtqueue::no_notify_guest
bool no_notify_guest() const
Get the no IRQ flag of this queue.
Definition virtqueue:414
error_helper
Error helper.
l4_addr_t
unsigned long l4_addr_t
Address type.
Definition l4int.h:34
l4_uint8_t
unsigned char l4_uint8_t
Unsigned 8bit value.
Definition l4int.h:25
l4_uint32_t
unsigned int l4_uint32_t
Unsigned 32bit value.
Definition l4int.h:29
l4_uint16_t
unsigned short int l4_uint16_t
Unsigned 16bit value.
Definition l4int.h:27
L4_ERANGE
@ L4_ERANGE
Range error.
Definition err.h:48
L4_UNLIKELY
#define L4_UNLIKELY(x)
Expression is unlikely to execute.
Definition compiler.h:284
L4VIRTIO_FEATURE_VERSION_1
@ L4VIRTIO_FEATURE_VERSION_1
Virtio protocol version 1 supported. Must be 1 for L4virtio.
Definition virtio.h:101
L4VIRTIO_ID_GPIO
@ L4VIRTIO_ID_GPIO
Gpio device.
Definition virtio.h:81
L4VIRTIO_IRQ_STATUS_VRING
@ L4VIRTIO_IRQ_STATUS_VRING
VRING IRQ pending flag.
Definition virtio.h:112
L4VIRTIO_IRQ_STATUS_CONFIG
@ L4VIRTIO_IRQ_STATUS_CONFIG
CONFIG IRQ pending flag.
Definition virtio.h:113
L4Re::chksys
long chksys(long err, char const *extra="", long ret=0)
Generate C++ exception on error.
Definition error_helper:72
L4Re::chkcap
T chkcap(T &&cap, char const *extra="", long err=-L4_ENOMEM)
Check for valid capability or raise C++ exception.
Definition error_helper:149
L4Re::chkipc
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
L4::cap_cast
Cap< T > cap_cast(Cap< F > const &c) noexcept
static_cast for capabilities.
Definition capability.h:416
L4virtio
L4-VIRTIO Transport C++ API.
Definition l4virtio:26
pair
Pair implementation.
L4::Epiface_t
Epiface implementation for Kobject-based interface implementations.
Definition ipc_epiface:504
L4::Epiface::server_iface
Server_iface * server_iface() const
Get pointer to server interface at which the object is currently registered.
Definition ipc_epiface:213
L4::Irqep_t
Epiface implementation for interrupt handlers.
Definition ipc_epiface:283
L4virtio::Svr::Data_buffer
Abstract data buffer.
Definition virtio:307
L4virtio::Svr::Data_buffer::left
l4_uint32_t left
Bytes left in buffer.
Definition virtio:309
L4virtio::Svr::Data_buffer::pos
char * pos
Current buffer position.
Definition virtio:308
L4virtio::Svr::Virtio_gpio::Request_processor
Generic handler for the Virtio requests.
Definition virtio-gpio-device:214
L4virtio::Svr::Virtio_gpio::Request_processor::get_request
T get_request()
The driver prepares the GPIO request in two data parts: 1st: in_hdr 2rd: out_hdr.
Definition virtio-gpio-device:241