L4Re Operating System Framework
Interface and Usage Documentation
Loading...
Searching...
No Matches
virtio-spi-device
1// vi:set ft=cpp: -*- Mode: C++ -*-
2/*
3 * Copyright (C) 2025 Kernkonzept GmbH.
4 * Author(s): Martin Kuettler <martin.kuettler@kernkonzept.com>
5 * Jakub Jermar <jakub.jermar@kernkonzept.com>
6 *
7 * License: see LICENSE.spdx (in this directory or the directories above)
8 */
9
10#pragma once
11
12#include <l4/re/error_helper>
13#include <l4/sys/cxx/ipc_epiface>
14
15#include <l4/l4virtio/server/virtio>
16#include <l4/l4virtio/server/l4virtio>
17#include <l4/l4virtio/l4virtio>
18
19#include <l4/re/error_helper>
20#include <l4/re/util/object_registry>
21#include <l4/re/util/br_manager>
22#include <l4/sys/cxx/ipc_epiface>
23
24#include <vector>
25#include <l4/cxx/pair>
26
27namespace L4virtio {
28namespace Svr {
29
30struct Spi_config
31{
32 l4_uint8_t cs_max_number;
33 l4_uint8_t cs_change_supported;
34 l4_uint8_t tx_nbits_supported;
35 l4_uint8_t rx_nbits_supported;
36 l4_uint32_t bits_per_word_mask;
37 l4_uint32_t mode_func_supported;
38 l4_uint32_t max_freq_hz;
39 l4_uint32_t max_word_delay_ns;
40 l4_uint32_t max_cs_setup_ns;
41 l4_uint32_t max_cs_hold_ns;
42 l4_uint32_t max_cs_inactive_ns;
43};
44
45enum Spi_transfer_result: l4_uint8_t
46{
47 Spi_trans_ok = 0,
48 Spi_param_err = 1,
49 Spi_trans_err = 2,
50};
51
52struct Spi_transfer_head
53{
54 l4_uint8_t chip_select_id;
55 l4_uint8_t bits_per_word;
56 l4_uint8_t cs_change;
57 l4_uint8_t tx_nbits;
58 l4_uint8_t rx_nbits;
59 l4_uint8_t reserved[3];
60 l4_uint32_t mode;
61 l4_uint32_t freq;
62 l4_uint32_t word_delay_ns;
63 l4_uint32_t cs_setup_ns;
64 l4_uint32_t cs_delay_hold_ns;
65 l4_uint32_t cs_change_delay_inactive_ns;
66};
67static_assert(sizeof(Spi_transfer_head) == 32,
68 "Spi_transfer_head contains padding bytes.");
69
70struct Spi_transfer_req
71{
72 struct Spi_transfer_head head;
73 l4_uint8_t *tx_buf = nullptr;
74 l4_uint8_t *rx_buf = nullptr;
75 Spi_transfer_result *result = nullptr;
76
77 unsigned tx_size = 0;
78 unsigned rx_size = 0;
79
80 void set_result(Spi_transfer_result res)
81 {
82 *result = res;
83 }
84};
85
99template <typename Spi_request_handler, typename Epiface = L4virtio::Device>
100class Virtio_spi
101: public L4virtio::Svr::Device,
102 public L4::Epiface_t<Virtio_spi<Spi_request_handler, Epiface>, Epiface>
103{
104private:
105 enum
106 {
107 Num_request_queues = 1,
108 Queue_size = 128,
109 };
110
111public:
117 class Host_irq : public L4::Irqep_t<Host_irq>
118 {
119 public:
120 explicit Host_irq(Virtio_spi *spi) : L4::Irqep_t<Host_irq>(), _spi(spi) {}
121
122 void handle_irq()
123 {
124 _spi->handle_queue();
125 }
126
127 private:
128 Virtio_spi *_spi;
129 };
130
134 class Request_processor : public L4virtio::Svr::Request_processor
135 {
136 public:
137
138 struct Data_buffer : public L4virtio::Svr::Data_buffer
139 {
140 Data_buffer()
141 {
142 pos = nullptr;
143 left = 0;
144 }
145 // This constructor is called from within the base start(), so make it
146 // available.
147 Data_buffer(L4virtio::Svr::Driver_mem_region const *r,
150 {
151 pos = static_cast<char *>(r->local(d.addr));
152 left = d.len;
153 }
154
155 };
156
157 Request_processor(L4virtio::Svr::Virtqueue *q, Spi_request_handler *hndlr,
158 Virtio_spi *spi)
159 : _q(q), _req_handler(hndlr), _spi(spi), _head(), _req()
160 {}
161
162 bool init_queue()
163 {
164 auto r = _q->next_avail();
165
166 if (L4_UNLIKELY(!r))
167 return false;
168
169 _head = start(_spi->mem_info(), r, &_req);
170
171 return true;
172 }
173
184 Spi_transfer_req get_request()
185 {
186 Spi_transfer_req request;
187 memcpy(&request.head, _req.pos, sizeof(Spi_transfer_head));
188
189 // Check for a TX and/or RX buffer
190 if (!next(_spi->mem_info(), &_req))
191 {
192 L4Re::throw_error(-L4_EIO, "Virtio SPI request too short");
193 }
194
195 if (current_flags().write())
196 {
197 // device-writable buffer
198 request.rx_buf = reinterpret_cast<l4_uint8_t *>(_req.pos);
199 request.rx_size = _req.left;
200 }
201 else
202 {
203 request.tx_buf = reinterpret_cast<l4_uint8_t *>(_req.pos);
204 request.tx_size = _req.left;
205 }
206
207 // Check for an RX buffer or request result buffer
208 if (!next(_spi->mem_info(), &_req))
209 {
210 L4Re::throw_error(-L4_EIO, "Virtio SPI request too short");
211 }
212
213 if (has_more())
214 {
215 // This must be the RX buffer
216 if (request.rx_buf || !current_flags().write())
217 {
218 L4Re::throw_error(-L4_EIO, "Bad Virtio SPI request");
219 }
220
221 request.rx_buf = reinterpret_cast<l4_uint8_t *>(_req.pos);
222 request.rx_size = _req.left;
223 next(_spi->mem_info(), &_req);
224 }
225
226 request.result = reinterpret_cast<Spi_transfer_result *>(_req.pos);
227
228 return request;
229 }
230
231 void handle_request()
232 {
233 if (!_head)
234 if (!init_queue())
235 return;
236
237 using Consumed_entry =
239 std::vector<Consumed_entry> consumed;
240
241 for (;;)
242 {
243 auto r = get_request();
244 Spi_transfer_result res;
245 if (r.tx_buf && r.rx_buf && (r.tx_size != r.rx_size))
246 res = Spi_param_err;
247 else
248 res =
249 _req_handler->handle_transfer(r.head, r.tx_buf, r.rx_buf,
250 r.tx_size ? r.tx_size : r.rx_size);
251 r.set_result(res);
252
253 l4_uint32_t written = sizeof(Spi_transfer_result);
254 if (res == Spi_trans_ok)
255 written += r.rx_size;
256
257 consumed.emplace_back(_head, written);
258 if (!init_queue())
259 break;
260 }
261
262 _q->finish(consumed.begin(), consumed.end(), _spi);
263
264 _head = Virtqueue::Head_desc();
265 }
266
267 private:
269 Spi_request_handler *_req_handler;
270 Virtio_spi *_spi;
272 Data_buffer _req;
273 };
274
275 Virtio_spi(Spi_request_handler *hndlr, L4Re::Util::Object_registry *registry)
276 : L4virtio::Svr::Device(&_dev_config),
277 _dev_config(L4VIRTIO_VENDOR_KK, L4VIRTIO_ID_SPI, Num_request_queues),
278 _req_handler(hndlr),
279 _host_irq(this),
280 _request_processor(&_q, hndlr, this)
281 {
282 init_mem_info(2);
283 reset_queue_config(0, Queue_size);
284 setup_queue(&_q, 0, Queue_size);
285 registry->register_irq_obj(&_host_irq);
286
287 Spi_config volatile *pc = _dev_config.priv_config();
288
289 pc->cs_max_number = hndlr->cs_max_number();
290 pc->cs_change_supported = 0;
291 pc->tx_nbits_supported = 0;
292 pc->rx_nbits_supported = 0;
293 pc->bits_per_word_mask = 0x80;
294 pc->mode_func_supported = hndlr->mode_func_supported();
295 pc->max_freq_hz = 0; // XXX: restrict wrt. controller
296 pc->max_word_delay_ns = 0;
297 pc->max_cs_setup_ns = 0;
298 pc->max_cs_hold_ns = 0;
299 pc->max_cs_inactive_ns = 0;
300
301 L4virtio::Svr::Dev_config::Features hf(0);
302 _dev_config.host_features(0) = hf.raw;
303 _dev_config.set_host_feature(L4VIRTIO_FEATURE_VERSION_1);
304 _dev_config.reset_hdr();
305 }
306
307 void notify_queue(L4virtio::Svr::Virtqueue *)
308 {
309 if (_q.no_notify_guest())
310 return;
311
312 _dev_config.add_irq_status(L4VIRTIO_IRQ_STATUS_VRING);
313 L4Re::chkipc(_notify_guest_irq->trigger(), "trigger guest irq");
314 }
315
316 void handle_queue()
317 {
318 _request_processor.handle_request();
319 }
320
321 void reset() override
322 {
323 }
324
325 bool check_queues() override
326 {
327 return true;
328 }
329
330 int reconfig_queue(unsigned idx) override
331 {
332 if (idx != 0)
333 return -L4_ERANGE;
334
335 setup_queue(&_q, 0, Queue_size);
336
337 return L4_EOK;
338 }
339
341 {
342 _dev_config.add_irq_status(L4VIRTIO_IRQ_STATUS_CONFIG);
343 _notify_guest_irq->trigger();
344 }
345
347 {
349 }
350
351 long op_set_status(L4virtio::Device::Rights r, unsigned status)
352 {
353 return L4virtio::Svr::Device::op_set_status(r, status);
354 }
355
356 long op_config_queue(L4virtio::Device::Rights r, unsigned queue)
357 {
358 return L4virtio::Svr::Device::op_config_queue(r, queue);
359 }
360
361 long op_device_config(L4virtio::Device::Rights r,
362 L4::Ipc::Cap<L4Re::Dataspace> &config_ds,
363 l4_addr_t &ds_offset)
364 {
365 return L4virtio::Svr::Device::op_device_config(r, config_ds, ds_offset);
366 }
367
369 {
370 return L4::cap_cast<L4::Irq>(_host_irq.obj_cap());
371 }
372
374 {
375 _notify_guest_irq =
376 L4Re::chkcap(server_iface()->template rcv_cap<L4::Irq>(0));
377
378 L4Re::chksys(server_iface()->realloc_rcv_cap(0));
379 }
380
381
382private:
383 L4virtio::Svr::Dev_config_t<Spi_config> _dev_config;
384 Spi_request_handler *_req_handler;
386 Host_irq _host_irq;
387 L4::Cap<L4::Irq> _notify_guest_irq;
388 Request_processor _request_processor;
389};
390
391} // namespace Svr
392} // 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
void reset_hdr(bool inc_generation=false) const
Reset the config header to the initial contents.
Definition l4virtio:318
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
bool has_more() const
Are there more chained descriptors?
Definition virtio:553
void start(DESC_MAN *dm, Virtqueue *ring, Virtqueue::Head_desc const &request, ARGS... args)
Start processing a new request.
Definition virtio:501
Spi_transfer_req get_request()
Linux prepares the SPI request in three or four data parts: 1st: transfer_head 2nd: TX buffer (not pr...
void register_single_driver_irq() override
callback for registering a single guest IRQ for all queues (old-style)
void trigger_driver_config_irq() override
callback for triggering configuration change notification IRQ
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 reset() override
reset callback, called for doing a device reset
L4::Cap< L4::Irq > device_notify_irq() const override
callback to gather the device notification IRQ (old-style)
VIRTIO request, essentially a descriptor from the available ring.
Definition virtio:94
Virtqueue implementation for the device.
Definition virtio:88
void finish(Head_desc &d, QUEUE_OBSERVER *o, l4_uint32_t len=0)
Add a descriptor to the used ring, and notify an observer.
Definition virtio:240
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
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
@ L4_ERANGE
Range error.
Definition err.h:48
@ L4_EIO
I/O error.
Definition err.h:35
@ L4_EOK
Ok.
Definition err.h:32
#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:104
@ L4VIRTIO_ID_SPI
SPI device.
Definition virtio.h:84
@ L4VIRTIO_IRQ_STATUS_VRING
VRING IRQ pending flag.
Definition virtio.h:115
@ L4VIRTIO_IRQ_STATUS_CONFIG
CONFIG IRQ pending flag.
Definition virtio.h:116
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
void throw_error(long err, char const *extra="")
Generate C++ exception.
Definition error_helper:37
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
Pair of two values.
Definition pair:28