L4Re Operating System Framework
Interface and Usage Documentation
Loading...
Searching...
No Matches
virtio-i2c-device
1// vi:set ft=cpp: -*- Mode: C++ -*-
2/*
3 * Copyright (C) 2024-2025 Kernkonzept GmbH.
4 * Author(s): Martin Kuettler <martin.kuettler@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
23#include <vector>
24#include <optional>
25#include <l4/cxx/pair>
26
27namespace L4virtio {
28namespace Svr {
29
30enum class I2c_transfer_result : l4_uint8_t
31{
32 I2c_msg_ok = 0,
33 I2c_msg_err = 1
34};
35
36struct I2c_request_flags
37{
38 l4_uint32_t raw;
39
40 CXX_BITFIELD_MEMBER(0, 0, fail_next, raw);
41 CXX_BITFIELD_MEMBER(1, 1, m_rd, raw);
42};
43static_assert(sizeof(I2c_request_flags) == 4,
44 "I2c_request_flags contains padding bytes.");
45
46struct I2c_out_hdr
47{
48 l4_uint16_t addr;
49 l4_uint16_t padding;
50 I2c_request_flags flags;
51};
52static_assert(sizeof(I2c_out_hdr) == 8, "I2c_out_hdr contains padding bytes.");
53
54struct I2c_in_hdr
55{
56 l4_uint8_t status;
57};
58static_assert(sizeof(I2c_in_hdr) == 1, "I2c_in_hdr contains padding bytes.");
59
60struct I2c_req
61{
62 struct I2c_out_hdr out_hdr;
63 unsigned buf_len;
64 l4_uint8_t* buf;
65 struct I2c_in_hdr *in_hdr;
66 L4virtio::Svr::Virtqueue::Head_desc head;
67
68 unsigned write_size;
69
70 void set_status(I2c_transfer_result status)
71 {
72 in_hdr->status = static_cast<l4_uint8_t>(status);
73 }
74};
75
87template <typename Request_handler,
88 typename Epiface = L4virtio::Device>
89class Virtio_i2c : public L4virtio::Svr::Device,
90 public L4::Epiface_t<Virtio_i2c<Request_handler,
91 Epiface>,
92 Epiface>
93{
94private:
95 enum
96 {
97 Num_request_queues = 1,
98 queue_size = 128,
99 };
100
101public:
102 using I2c_request_handler = Request_handler;
103
109 class Host_irq : public L4::Irqep_t<Host_irq>
110 {
111 public:
112 explicit Host_irq(Virtio_i2c *i2c) : L4::Irqep_t<Host_irq>(), _i2c(i2c) {}
113
114 void handle_irq()
115 {
116 _i2c->handle_queue();
117 }
118
119 private:
120 Virtio_i2c *_i2c;
121 };
122
126 class Request_processor : public L4virtio::Svr::Request_processor
127 {
128 public:
129
130 struct Data_buffer : public L4virtio::Svr::Data_buffer
131 {
132 Data_buffer()
133 {
134 pos = nullptr;
135 left = 0;
136 }
137 // This constructor is called from within start, so make it available.
138 Data_buffer(L4virtio::Svr::Driver_mem_region const *r,
141 {
142 pos = static_cast<char *>(r->local(d.addr));
143 left = d.len;
144 }
145
146 };
147
148 Request_processor(L4virtio::Svr::Virtqueue *q, I2c_request_handler *hndlr,
149 Virtio_i2c *i2c)
150 : _q(q), _req_handler(hndlr), _i2c(i2c), _fail_next(false)
151 {}
152
162 I2c_req get_request()
163 {
164 I2c_req request;
165 if (_pending_req.has_value())
166 {
167 request = _pending_req.value();
168 _pending_req.reset();
169 return request;
170 }
171
172 auto r = _q->next_avail();
173 if (L4_UNLIKELY(!r))
174 {
175 request.head = Virtqueue::Head_desc();
176 return request;
177 }
178
179 Data_buffer req;
180 request.head = start(_i2c->mem_info(), r, &req);
181 memcpy(&request.out_hdr, req.pos, sizeof(I2c_out_hdr));
182
183 // number of bytes to be written in the answer.
184 request.write_size = sizeof(I2c_in_hdr);
185 request.buf_len = 0;
186
187 // 2nd part: either the optional buffer or the in_hdr
188 if (next(_i2c->mem_info(), &req))
189 {
190
191 request.buf_len += req.left;
192 request.buf = reinterpret_cast<l4_uint8_t *>(req.pos);
193 }
194
195 // 3rd part: in_hdr
196 if (next(_i2c->mem_info(), &req))
197 {
198 // 2nd part was indeed a buffer
199 if (request.out_hdr.flags.m_rd())
200 request.write_size += request.buf_len;
201
202 // actual 3rd part
203 request.in_hdr = reinterpret_cast<I2c_in_hdr *>(req.pos);
204 }
205 else
206 {
207 // no 3rd part, 2nd part is in_hdr;
208 request.in_hdr = reinterpret_cast<I2c_in_hdr *>(request.buf);
209 request.buf = nullptr;
210 request.buf_len = 0;
211 }
212
213 return request;
214 }
215
216 void handle_requests()
217 {
218 using Consumed_entry =
220 std::vector<Consumed_entry> consumed;
221
222 for (;;)
223 {
224 auto r = get_request();
225 if (!r.head)
226 break;
227
228 if (_fail_next)
229 {
230 r.set_status(I2c_transfer_result::I2c_msg_err);
231 _fail_next = r.out_hdr.flags.fail_next();
232 }
233 else
234 {
235 std::optional<bool> ok;
236 l4_uint16_t i2c_addr = r.out_hdr.addr >> 1;
237 if (r.out_hdr.flags.m_rd())
238 ok = _req_handler->handle_read(i2c_addr, r.buf, r.buf_len);
239 else
240 ok = _req_handler->handle_write(i2c_addr, r.buf, r.buf_len);
241 if (ok.has_value())
242 {
243 if (ok.value())
244 {
245 r.set_status(I2c_transfer_result::I2c_msg_ok);
246 _fail_next = false;
247 }
248 else
249 {
250 r.set_status(I2c_transfer_result::I2c_msg_err);
251 _fail_next = r.out_hdr.flags.fail_next();
252 }
253 }
254 else
255 {
256 _pending_req = r;
257 break;
258 }
259 }
260 consumed.emplace_back(r.head, r.write_size);
261 }
262
263 if (!consumed.empty())
264 _q->finish(consumed.begin(), consumed.end(), _i2c);
265 }
266
267 private:
268 L4virtio::Svr::Virtqueue *_q;
269 I2c_request_handler *_req_handler;
270 Virtio_i2c *_i2c;
271 bool _fail_next;
272 std::optional<I2c_req> _pending_req;
273 };
274
275 struct Features : public L4virtio::Svr::Dev_config::Features
276 {
277 Features() = default;
278 Features(l4_uint32_t raw) : L4virtio::Svr::Dev_config::Features(raw) {}
279
280 // This feature is mandatory. The driver is requested to abort communication
281 // if this is not offered.
282 CXX_BITFIELD_MEMBER(0, 0, zero_length_request, raw);
283 };
284
285 Virtio_i2c(I2c_request_handler *hndlr, L4Re::Util::Object_registry *registry)
286 : L4virtio::Svr::Device(&_dev_config),
287 _dev_config(L4VIRTIO_VENDOR_KK, L4VIRTIO_ID_I2C, Num_request_queues),
288 _req_handler(hndlr),
289 _host_irq(this),
290 _request_processor(&_q, hndlr, this)
291 {
292 init_mem_info(2);
293 reset_queue_config(0, queue_size);
294 setup_queue(&_q, 0, queue_size);
295 registry->register_irq_obj(&_host_irq);
296
297 Features hf(0);
298 hf.ring_indirect_desc() = true;
299 hf.zero_length_request() = true;
300 _dev_config.host_features(0) = hf.raw;
301 _dev_config.set_host_feature(L4VIRTIO_FEATURE_VERSION_1);
302 _dev_config.reset_hdr();
303 }
304
305 void notify_queue(L4virtio::Svr::Virtqueue *)
306 {
307 if (_q.no_notify_guest())
308 return;
309
310 _dev_config.add_irq_status(L4VIRTIO_IRQ_STATUS_VRING);
311 L4Re::chkipc(_notify_guest_irq->trigger(), "trigger guest irq");
312 }
313
314 void handle_queue()
315 {
316 _request_processor.handle_requests();
317 }
318
319 void reset() override
320 {
321 }
322
323 bool check_queues() override
324 {
325 return true;
326 }
327
328 int reconfig_queue(unsigned idx) override
329 {
330 if (idx != 0)
331 return -L4_ERANGE;
332
333 setup_queue(&_q, 0, queue_size);
334
335 return L4_EOK;
336 }
337
339 {
340 _dev_config.add_irq_status(L4VIRTIO_IRQ_STATUS_CONFIG);
341 _notify_guest_irq->trigger();
342 }
343
345 {
347 }
348
349 long op_set_status(L4virtio::Device::Rights r, unsigned status)
350 {
351 return L4virtio::Svr::Device::op_set_status(r, status);
352 }
353
354 long op_config_queue(L4virtio::Device::Rights r, unsigned queue)
355 {
356 return L4virtio::Svr::Device::op_config_queue(r, queue);
357 }
358
359 long op_device_config(L4virtio::Device::Rights r,
360 L4::Ipc::Cap<L4Re::Dataspace> &config_ds,
361 l4_addr_t &ds_offset)
362 {
363 return L4virtio::Svr::Device::op_device_config(r, config_ds, ds_offset);
364 }
365
367 {
368 return L4::cap_cast<L4::Irq>(_host_irq.obj_cap());
369 }
370
372 {
373 _notify_guest_irq = L4Re::chkcap
374 (server_iface()->template rcv_cap<L4::Irq>(0));
375
376 L4Re::chksys(server_iface()->realloc_rcv_cap(0));
377 }
378
379
380private:
381 L4virtio::Svr::Dev_config_t<L4virtio::Svr::No_custom_data>_dev_config;
382 I2c_request_handler *_req_handler;
384 Host_irq _host_irq;
385 L4::Cap<L4::Irq> _notify_guest_irq;
386 Request_processor _request_processor;
387};
388
389} // namespace Svr
390} // namespace L4virtio
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:38
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
void start(DESC_MAN *dm, Virtqueue *ring, Virtqueue::Head_desc const &request, ARGS... args)
Start processing a new request.
Definition virtio:501
I2c_req get_request()
Linux prepares the I2C request in three data parts: 1st: out_hdr 2nd: buffer (optional) 3rd: in_hdr.
void trigger_driver_config_irq() override
callback for triggering configuration change notification IRQ
void register_single_driver_irq() override
callback for registering a single guest IRQ for all queues (old-style)
void reset() override
reset callback, called for doing a device reset
bool check_queues() override
callback for checking if the queues at DRIVER_OK transition
L4::Cap< L4::Irq > device_notify_irq() const override
callback to gather the device notification IRQ (old-style)
int reconfig_queue(unsigned idx) override
callback for client queue-config request
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:93
l4_uint32_t len
Length of described buffer.
Definition virtqueue:115
Ptr< void > addr
Address stored in descriptor.
Definition virtqueue:114
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:49
@ L4_EOK
Ok.
Definition err.h:33
#define L4_UNLIKELY(x)
Expression is unlikely to execute.
Definition compiler.h:295
@ L4VIRTIO_FEATURE_VERSION_1
Virtio protocol version 1 supported. Must be 1 for L4virtio.
Definition virtio.h:104
@ L4VIRTIO_ID_I2C
I2C device.
Definition virtio.h:80
@ 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
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:659
Server_iface * server_iface() const
Get pointer to server interface at which the object is currently registered.
Definition ipc_epiface:325
Epiface implementation for interrupt handlers.
Definition ipc_epiface:395
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
l4_uint32_t raw
The raw value of the features bitmap.
Definition virtio:68
Pair of two values.
Definition pair:28