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 last_request; // XXX non-standard! aka reserved[0]
60 l4_uint8_t reserved[2];
61 l4_uint32_t mode;
62 l4_uint32_t freq;
63 l4_uint32_t word_delay_ns;
64 l4_uint32_t cs_setup_ns;
65 l4_uint32_t cs_delay_hold_ns;
66 l4_uint32_t cs_change_delay_inactive_ns;
67};
68static_assert(sizeof(Spi_transfer_head) == 32,
69 "Spi_transfer_head contains padding bytes.");
70
71struct Spi_transfer_req
72{
73 struct Spi_transfer_head head;
74 l4_uint8_t *tx_buf = nullptr;
75 l4_uint8_t *rx_buf = nullptr;
76 Spi_transfer_result *result = nullptr;
77
78 unsigned tx_size = 0;
79 unsigned rx_size = 0;
80
81 void set_result(Spi_transfer_result res)
82 {
83 *result = res;
84 }
85};
86
100template <typename Spi_request_handler, typename Epiface = L4virtio::Device>
101class Virtio_spi
102: public L4virtio::Svr::Device,
103 public L4::Epiface_t<Virtio_spi<Spi_request_handler, Epiface>, Epiface>
104{
105private:
106 enum
107 {
108 Num_request_queues = 1,
109 Queue_size = 128,
110 };
111
112public:
118 class Host_irq : public L4::Irqep_t<Host_irq>
119 {
120 public:
121 explicit Host_irq(Virtio_spi *spi) : L4::Irqep_t<Host_irq>(), _spi(spi) {}
122
123 void handle_irq()
124 {
125 _spi->handle_queue();
126 }
127
128 private:
129 Virtio_spi *_spi;
130 };
131
135 class Request_processor : public L4virtio::Svr::Request_processor
136 {
137 public:
138
139 struct Data_buffer : public L4virtio::Svr::Data_buffer
140 {
141 Data_buffer()
142 {
143 pos = nullptr;
144 left = 0;
145 }
146 // This constructor is called from within the base start(), so make it
147 // available.
148 Data_buffer(L4virtio::Svr::Driver_mem_region const *r,
151 {
152 pos = static_cast<char *>(r->local(d.addr));
153 left = d.len;
154 }
155
156 };
157
158 Request_processor(L4virtio::Svr::Virtqueue *q, Spi_request_handler *hndlr,
159 Virtio_spi *spi)
160 : _q(q), _req_handler(hndlr), _spi(spi), _head(), _req()
161 {}
162
163 bool init_queue()
164 {
165 auto r = _q->next_avail();
166
167 if (L4_UNLIKELY(!r))
168 return false;
169
170 _head = start(_spi->mem_info(), r, &_req);
171
172 return true;
173 }
174
185 Spi_transfer_req get_request()
186 {
187 Spi_transfer_req request;
188 memcpy(&request.head, _req.pos, sizeof(Spi_transfer_head));
189
190 // Check for a TX and/or RX buffer
191 if (!next(_spi->mem_info(), &_req))
192 {
193 L4Re::throw_error(-L4_EIO, "Virtio SPI request too short");
194 }
195
196 if (current_flags().write())
197 {
198 // device-writable buffer
199 request.rx_buf = reinterpret_cast<l4_uint8_t *>(_req.pos);
200 request.rx_size = _req.left;
201 }
202 else
203 {
204 request.tx_buf = reinterpret_cast<l4_uint8_t *>(_req.pos);
205 request.tx_size = _req.left;
206 }
207
208 // Check for an RX buffer or request result buffer
209 if (!next(_spi->mem_info(), &_req))
210 {
211 L4Re::throw_error(-L4_EIO, "Virtio SPI request too short");
212 }
213
214 if (has_more())
215 {
216 // This must be the RX buffer
217 if (request.rx_buf || !current_flags().write())
218 {
219 L4Re::throw_error(-L4_EIO, "Bad Virtio SPI request");
220 }
221
222 request.rx_buf = reinterpret_cast<l4_uint8_t *>(_req.pos);
223 request.rx_size = _req.left;
224 next(_spi->mem_info(), &_req);
225 }
226
227 request.result = reinterpret_cast<Spi_transfer_result *>(_req.pos);
228
229 return request;
230 }
231
232 void handle_request()
233 {
234 if (!_head)
235 if (!init_queue())
236 return;
237
238 using Consumed_entry =
240 std::vector<Consumed_entry> consumed;
241
242 for (;;)
243 {
244 auto r = get_request();
245 Spi_transfer_result res;
246 if (r.tx_buf && r.rx_buf && (r.tx_size != r.rx_size))
247 res = Spi_param_err;
248 else
249 res =
250 _req_handler->handle_transfer(r.head, r.tx_buf, r.rx_buf,
251 r.tx_size ? r.tx_size : r.rx_size);
252 r.set_result(res);
253
254 l4_uint32_t written = sizeof(Spi_transfer_result);
255 if (res == Spi_trans_ok)
256 written += r.rx_size;
257
258 consumed.emplace_back(_head, written);
259 if (!init_queue())
260 break;
261 }
262
263 _q->finish(consumed.begin(), consumed.end(), _spi);
264
265 _head = Virtqueue::Head_desc();
266 }
267
268 private:
270 Spi_request_handler *_req_handler;
271 Virtio_spi *_spi;
273 Data_buffer _req;
274 };
275
276 Virtio_spi(Spi_request_handler *hndlr, L4Re::Util::Object_registry *registry)
277 : L4virtio::Svr::Device(&_dev_config),
278 _dev_config(L4VIRTIO_VENDOR_KK, L4VIRTIO_ID_SPI, Num_request_queues),
279 _req_handler(hndlr),
280 _host_irq(this),
281 _request_processor(&_q, hndlr, this)
282 {
283 init_mem_info(2);
284 reset_queue_config(0, Queue_size);
285 setup_queue(&_q, 0, Queue_size);
286 registry->register_irq_obj(&_host_irq);
287
288 Spi_config volatile *pc = _dev_config.priv_config();
289
290 pc->cs_max_number = hndlr->cs_max_number();
291 pc->cs_change_supported = 0;
292 pc->tx_nbits_supported = 0;
293 pc->rx_nbits_supported = 0;
294 pc->bits_per_word_mask = 0x80;
295 pc->mode_func_supported = hndlr->mode_func_supported();
296 pc->max_freq_hz = 0; // XXX: restrict wrt. controller
297 pc->max_word_delay_ns = 0;
298 pc->max_cs_setup_ns = 0;
299 pc->max_cs_hold_ns = 0;
300 pc->max_cs_inactive_ns = 0;
301
302 L4virtio::Svr::Dev_config::Features hf(0);
303 _dev_config.host_features(0) = hf.raw;
304 _dev_config.set_host_feature(L4VIRTIO_FEATURE_VERSION_1);
305 _dev_config.reset_hdr();
306 }
307
308 void notify_queue(L4virtio::Svr::Virtqueue *)
309 {
310 if (_q.no_notify_guest())
311 return;
312
313 _dev_config.add_irq_status(L4VIRTIO_IRQ_STATUS_VRING);
314 L4Re::chkipc(_notify_guest_irq->trigger(), "trigger guest irq");
315 }
316
317 void handle_queue()
318 {
319 _request_processor.handle_request();
320 }
321
322 void reset() override
323 {
324 }
325
326 bool check_queues() override
327 {
328 return true;
329 }
330
331 int reconfig_queue(unsigned idx) override
332 {
333 if (idx != 0)
334 return -L4_ERANGE;
335
336 setup_queue(&_q, 0, Queue_size);
337
338 return L4_EOK;
339 }
340
342 {
343 _dev_config.add_irq_status(L4VIRTIO_IRQ_STATUS_CONFIG);
344 _notify_guest_irq->trigger();
345 }
346
348 {
350 }
351
352 long op_set_status(L4virtio::Device::Rights r, unsigned status)
353 {
354 return L4virtio::Svr::Device::op_set_status(r, status);
355 }
356
357 long op_config_queue(L4virtio::Device::Rights r, unsigned queue)
358 {
359 return L4virtio::Svr::Device::op_config_queue(r, queue);
360 }
361
362 long op_device_config(L4virtio::Device::Rights r,
363 L4::Ipc::Cap<L4Re::Dataspace> &config_ds,
364 l4_addr_t &ds_offset)
365 {
366 return L4virtio::Svr::Device::op_device_config(r, config_ds, ds_offset);
367 }
368
370 {
371 return L4::cap_cast<L4::Irq>(_host_irq.obj_cap());
372 }
373
375 {
376 _notify_guest_irq =
377 L4Re::chkcap(server_iface()->template rcv_cap<L4::Irq>(0));
378
379 L4Re::chksys(server_iface()->realloc_rcv_cap(0));
380 }
381
382
383private:
384 L4virtio::Svr::Dev_config_t<Spi_config> _dev_config;
385 Spi_request_handler *_req_handler;
387 Host_irq _host_irq;
388 L4::Cap<L4::Irq> _notify_guest_irq;
389 Request_processor _request_processor;
390};
391
392} // namespace Svr
393} // 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:327
bool setup_queue(Virtqueue *q, unsigned qn, unsigned num_max)
Definition l4virtio:1066
void reset_queue_config(unsigned idx, unsigned num_max, bool inc_generation=false, unsigned device_notify_index=0)
Definition l4virtio:1019
T * local(Ptr< T > p) const
Get the local address for driver address p.
Definition l4virtio:629
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:49
@ L4_EIO
I/O error.
Definition err.h:36
@ L4_EOK
Ok.
Definition err.h:33
#define L4_UNLIKELY(x)
Expression is unlikely to execute.
Definition compiler.h:289
@ 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
T chkcap(T &&cap, char const *extra="", l4_ret_t err=-L4_ENOMEM)
Check for valid capability or raise C++ exception.
Definition error_helper:149
l4_ret_t chksys(l4_ret_t err, char const *extra="", l4_ret_t ret=0)
Generate C++ exception on error.
Definition error_helper:72
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