L4Re Operating System Framework
Interface and Usage Documentation
Loading...
Searching...
No Matches
virtio-console
1// vi:ft=cpp
2/* SPDX-License-Identifier: MIT */
3/*
4 * Copyright (C) 2019-2024 Kernkonzept GmbH.
5 * Author(s): Sarah Hoffmann <sarah.hoffmann@kernkonzept.com>
6 * Phillip Raffeck <phillip.raffeck@kernkonzept.com>
7 * Steffen Liebergeld <steffen.liebergeld@kernkonzept.com>
8 * Jan Klötzke <jan.kloetzke@kernkonzept.com>
9 */
10#pragma once
11
12#include <l4/l4virtio/server/l4virtio>
13#include <l4/re/error_helper>
14
15namespace L4virtio { namespace Svr { namespace Console {
16
18struct Features : Dev_config::Features
19{
20 Features() = default;
21 explicit Features(l4_uint32_t raw) : Dev_config::Features(raw) {}
23 CXX_BITFIELD_MEMBER(0, 0, console_size, raw);
25 CXX_BITFIELD_MEMBER(1, 1, console_multiport, raw);
27 CXX_BITFIELD_MEMBER(2, 2, emerg_write, raw);
28};
29
31struct Control_message
32{
53
57
58 Control_message() {}
60 : id(i), event(e), value(v)
61 {}
62};
63
66{
72 Driver_mem_region *mem;
73};
74
109struct Port
110{
129
131 enum { Control_queue_size = 0x10 };
132
137 unsigned vq_max;
138
139 Port() : status(Port_disabled), vq_max(Control_queue_size) {}
140 Port(Port const &) = delete;
141 Port &operator = (Port const &) = delete;
142
143 virtual ~Port() = default;
144
146 bool is_open() const
147 { return status == Port_open; }
148
150 virtual void reset()
151 {
152 tx.disable();
153 rx.disable();
156 }
157
159 bool queues_ready() const
160 { return tx.ready() && rx.ready(); }
161
163 bool rx_ready() const
164 { return is_open() && rx.ready(); }
165
167 bool tx_ready() const
168 { return is_open() && tx.ready(); }
169
176
196 static constexpr Transition
198 {
199 /* reported current */
200
201 /* Port_disabled */ /* Port_disabled */ {{ -1, 0, Port_disabled },
202 /* Port_added */ { Control_message::Device_add, 0,
203 Port_added },
204 /* Port_ready */ { Control_message::Device_add, 0,
205 Port_ready },
206 /* Port_open */ { Control_message::Device_add, 0,
207 Port_ready },
208 /* Port_failed */ { Control_message::Device_add, 0,
209 Port_failed }},
210
211 /* Port_added */ /* Port_disabled */ {{ Control_message::Device_remove,
212 0, Port_disabled },
213 /* Port_added */ { -1, 0, Port_added },
214 /* Port_ready */ { -1, 0, Port_ready },
215 /* Port_open */ { Control_message::Port_open, 1,
216 Port_open },
217 /* Port_failed */ { -1, 0, Port_failed }},
218
219 /* Port_ready */ /* Port_disabled */ {{ Control_message::Device_remove,
220 0, Port_disabled },
221 /* Port_added */ { -1, 0, Port_added },
222 /* Port_ready */ { -1, 0, Port_ready },
223 /* Port_open */ { Control_message::Port_open, 1,
224 Port_open },
225 /* Port_failed */ { -1, 0, Port_failed }},
226
227 /* Port_open */ /* Port_disabled */ {{ Control_message::Port_open, 0,
228 Port_ready },
229 /* Port_added */ { Control_message::Port_open, 0,
230 Port_added },
231 /* Port_ready */ { Control_message::Port_open, 0,
232 Port_ready },
233 /* Port_open */ { -1, 0, Port_open },
234 /* Port_failed */ { Control_message::Port_open, 0,
235 Port_ready }},
236
237 /* Port_failed */ /* Port_disabled */ {{ Control_message::Device_remove,
238 0, Port_disabled },
239 /* Port_added */ { -1, 0, Port_added },
240 /* Port_ready */ { -1, 0, Port_ready },
241 /* Port_open */ { Control_message::Port_open, 1,
242 Port_open },
243 /* Port_failed */ { -1, 0, Port_failed }},
244 };
245};
246
269class Virtio_con : public L4virtio::Svr::Device
270{
271 enum Virtqueue_names
272 {
273 Ctrl_rx = 2,
274 Ctrl_tx = 3,
275 };
276
277 struct Serial_config_space
278 {
279 l4_uint16_t cols;
280 l4_uint16_t rows;
281 l4_uint32_t max_nr_ports;
282 l4_uint32_t emerg_wr;
283 } __attribute__((packed));
284
285public:
295 explicit Virtio_con(unsigned max_ports, bool enable_multiport)
296 : L4virtio::Svr::Device(&_dev_config),
297 _num_ports(enable_multiport ? max_ports : 1),
298 _dev_config(L4VIRTIO_VENDOR_KK, L4VIRTIO_ID_CONSOLE,
299 enable_multiport ? max_ports * 2 + 2 : 2)
300 {
301 if (_num_ports < 1)
302 L4Re::chksys(-L4_EINVAL, "At least one port is required.");
303
304 Features hf(0);
305
306 hf.console_multiport() = enable_multiport;
307
308 _dev_config.host_features(0) = hf.raw;
309
310 if (enable_multiport)
311 _dev_config.priv_config()->max_nr_ports = _num_ports;
312 _dev_config.reset_hdr();
313 }
314
315 void reset_queue_configs()
316 {
317 for (unsigned q = 0; q < _dev_config.num_queues(); ++q)
318 reset_queue_config(q, max_queue_size(q));
319 }
320
321 int reconfig_queue(unsigned index) override
322 {
323 if (index >= _dev_config.num_queues())
324 return -L4_ERANGE;
325
326 if (setup_queue(get_queue(index), index, max_queue_size(index)))
327 return 0;
328
329 return -L4_EINVAL;
330 }
331
336 bool multiport_enabled() const
337 {
338 return _negotiated_features.console_multiport()
339 && _dev_config.num_queues() > Ctrl_rx;
340 }
341
342 bool ctrl_queue_ready() const
343 { return _ctrl_port.is_open(); }
344
345 bool check_features(void) override
346 {
347 _negotiated_features = Features(_dev_config.negotiated_features(0));
348 return true;
349 }
350
351 bool check_queues() override
352 {
353 // NOTE
354 // The VIRTIO specification states:
355 // "The port 0 receive and transmit queues always exist"
356 // The linux driver however does not setup port 0 if the multiport feature
357 // is negotiated.
358 // We just go along with the linux driver and do not expect port 0 to be up,
359 // if the multiport feature is negotiated.
360
361 if (multiport_enabled())
362 // If MULTIPORT was negotiated, ctrl queues should be set up.
363 return _ctrl_port.queues_ready();
364
365 // If MULTIPORT was not negotiated, port 0 should be set up.
367 return port(0)->queues_ready();
368 }
369
381 int port_add(unsigned idx)
382 {
383 Port *p = port(idx);
384
385 if (p->status != Port::Port_disabled)
386 return -L4_EPERM;
387
389 port_report_status(idx);
390
391 return L4_EOK;
392 }
393
405 int port_remove(unsigned idx)
406 {
407 Port *p = port(idx);
408
409 if (p->status == Port::Port_disabled)
410 return -L4_EPERM;
411
413 port_report_status(idx);
414
415 return L4_EOK;
416 }
417
430 int port_open(unsigned idx, bool open)
431 {
432 Port *p = port(idx);
433
434 if ((open && p->status != Port::Port_ready)
435 || (!open && p->status != Port::Port_open))
436 return -L4_EPERM;
437
439 port_report_status(idx);
440
441 return L4_EOK;
442 }
443
457 int port_name(unsigned idx, char const *name)
458 {
459 Port *p = port(idx);
460
461 if (p->status == Port::Port_disabled)
462 return -L4_EPERM;
463
465 }
466
490 l4_uint16_t value = 0, const char *name = 0)
491 {
492 if (!ctrl_queue_ready())
493 return -L4_ENODEV;
494
495 Virtqueue *q = &_ctrl_port.rx;
496 if (!q->ready())
497 return -L4_ENODEV;
498
499 Virtqueue::Request r = q->next_avail();
500 if (!r)
501 return -L4_EBUSY;
502
504 Control_request req;
505 rp.start(this, r, &req);
506
507 if (req.len < sizeof(Control_message))
508 return -L4_ENOMEM;
509
510 Control_message msg(idx, event, value);
511
512 memcpy(req.msg, &msg, sizeof(msg));
513
514 if (event == Control_message::Port_name && name)
515 {
516 size_t name_len = cxx::min(req.len - sizeof(msg), strlen(name));
517 memcpy(reinterpret_cast<char*>(req.msg) + sizeof(msg), name, name_len);
518 q->finish(r, this, sizeof(msg) + name_len);
519 }
520 else
521 q->finish(r, this, sizeof(msg));
522
523 return L4_EOK;
524 }
525
539 {
540 // Report port state transitions if that failed in the past...
541 if (_report_port_state)
542 {
543 _report_port_state = false;
544
545 for (unsigned i = 0; i < _num_ports; ++i)
546 if (!port_report_status(i))
547 _report_port_state = true;
548 }
549
550 Virtqueue *q = &_ctrl_port.tx;
551 if (!q->ready())
552 return -L4_ENODEV;
553
554 int ret = L4_EOK;
555 Virtqueue::Request r;
556 while ((r = q->next_avail()))
557 {
559 Control_request req;
560
561 rp.start(this, r, &req);
562
563 Control_message msg;
564 if (req.len < sizeof(msg))
565 {
566 // Just ignore malformed input.
567 q->finish(r, this);
568 ret = -L4_EINVAL;
569 continue;
570 }
571
572 memcpy(&msg, req.msg, sizeof(msg));
573 q->finish(r, this);
574
575 if (_ctrl_port.status == Port::Port_disabled)
576 {
577 // When the control queue is disabled, only device ready is accepted.
579 {
580 if (msg.value)
581 _ctrl_port.status = Port::Port_open;
583 }
584 else
585 ret = -L4_EIO;
586
587 continue;
588 }
589
590 if (!ctrl_queue_ready())
591 continue;
592
593 // Ignore invalid port ids
594 if (msg.id >= max_ports())
595 continue;
596
597 switch (msg.event)
598 {
600 process_port_ready(msg.id, msg.value);
601 break;
603 process_port_open(msg.id, msg.value);
604 break;
605 default:
606 ret = -L4_EINVAL;
607 break;
608 }
609 }
610
611 return ret;
612 }
613
615 void load_desc(L4virtio::Virtqueue::Desc const &desc,
616 Request_processor const *proc,
617 L4virtio::Virtqueue::Desc const **table)
618 {
619 this->_mem_info.load_desc(desc, proc, table);
620 }
621
623 void load_desc(L4virtio::Virtqueue::Desc const &desc,
624 Request_processor const *proc,
625 Control_request *data)
626 {
627 auto *region = this->_mem_info.find(desc.addr.get(), desc.len);
628 if (L4_UNLIKELY(!region))
630
631 data->msg = reinterpret_cast<Control_message *>(region->local(desc.addr));
632 data->len = desc.len;
633 data->mem = region;
634 }
635
636 void reset() override
637 {
638 for (unsigned p = 0; p < _num_ports; ++p)
639 port(p)->reset();
640
641 _ctrl_port.reset();
642 reset_queue_configs();
643 _dev_config.reset_hdr();
644 _negotiated_features = Features(0);
645 _report_port_state = false;
646
647 reset_device();
648 }
649
656 virtual void reset_device() {}
657
667 virtual void notify_queue(Virtqueue *queue) = 0;
668
676 virtual Port *port(unsigned port) = 0;
677 virtual Port const *port(unsigned port) const = 0;
678
689 virtual void process_device_ready(l4_uint16_t value) = 0;
690
703 {
704 Port *p = port(id);
705
706 switch (p->status)
707 {
708 case Port::Port_added:
709 case Port::Port_ready:
711 break;
712 case Port::Port_open:
713 if (!value)
715 break;
716 default:
717 // invalid state for PORT_READY message
718 break;
719 }
720 }
721
732 virtual void process_port_open(l4_uint32_t id, l4_uint16_t value) = 0;
733
734 unsigned max_ports() const
735 { return _num_ports; }
736
737private:
738 bool is_control_queue(unsigned q) const
739 { return q == Ctrl_rx || q == Ctrl_tx; }
740
741 unsigned queue_to_port(unsigned q) const
742 { return (q == 0 || q == 1) ? 0 : (q / 2) - 1; }
743
752 unsigned max_queue_size(unsigned q) const
753 {
754 if (is_control_queue(q))
755 return _ctrl_port.vq_max;
756
757 return port(queue_to_port(q))->vq_max;
758 }
759
768 Virtqueue *get_queue(unsigned q)
769 {
770 Port *p;
771 if (is_control_queue(q))
772 p = &_ctrl_port;
773 else
774 p = port(queue_to_port(q));
775
776 if (q & 1)
777 return &p->tx;
778 else
779 return &p->rx;
780 }
781
792 bool port_report_status(unsigned idx)
793 {
794 Port *p = port(idx);
795 while (p->status != p->reported_status)
796 {
797 auto const &trans
798 = Port::state_transitions[p->reported_status][p->status];
799
800 if (trans.event >= 0
801 && send_control_message(idx, trans.event, trans.value) < 0)
802 {
803 _report_port_state = true;
804 return false;
805 }
806
807 p->reported_status = trans.next;
808 }
809
810 return true;
811 }
812
813 unsigned _num_ports;
814 bool _report_port_state = false;
815
816protected:
817 Dev_config_t<Serial_config_space> _dev_config;
818 Port _ctrl_port;
819 Features _negotiated_features{0};
820};
821
822}}} // name space
l4_uint64_t get() const
Definition virtqueue:68
Base class implementing a virtio console device with L4Re-based notification handling.
virtual void process_port_open(l4_uint32_t id, l4_uint16_t value)=0
Callback called on PORT_OPEN event.
bool check_queues() override
callback for checking if the queues at DRIVER_OK transition
virtual void reset_device()
Reset the state of the actual console device.
bool multiport_enabled() const
Return true if the multiport feature is enabled and control queues are available.
bool check_features(void) override
callback for checking the subset of accepted features
virtual void notify_queue(Virtqueue *queue)=0
Notify queue of available data.
void reset() override
reset callback, called for doing a device reset
int port_open(unsigned idx, bool open)
Send a PORT_OPEN message and update the internal state.
int port_name(unsigned idx, char const *name)
Send a PORT_NAME message to announce the port name.
virtual void process_port_ready(l4_uint32_t id, l4_uint16_t value)
Callback called on PORT_READY event.
int port_remove(unsigned idx)
Send a DEVICE_REMOVE message and update the internal state.
virtual void process_device_ready(l4_uint16_t value)=0
Callback called on DEVICE_READY event.
int port_add(unsigned idx)
Send a DEVICE_ADD message and update the internal state.
int reconfig_queue(unsigned index) override
callback for client queue-config request
Virtio_con(unsigned max_ports, bool enable_multiport)
Create a new multiport console device.
virtual Port * port(unsigned port)=0
Return the specified port.
int handle_control_message()
Handle control message received from the driver.
int send_control_message(l4_uint32_t idx, l4_uint16_t event, l4_uint16_t value=0, const char *name=0)
Send control message to driver.
l4_uint32_t num_queues() const
Return the number of queues currently usable.
Definition l4virtio:204
bool setup_queue(Virtqueue *q, unsigned qn, unsigned num_max)
Definition l4virtio:1091
void reset_queue_config(unsigned idx, unsigned num_max, bool inc_generation=false, unsigned device_notify_index=0)
Definition l4virtio:1044
Mem_region * find(l4_uint64_t base, l4_umword_t size) const
Find memory region containing the given driver address region.
Definition l4virtio:727
void load_desc(Virtqueue::Desc const &desc, Request_processor const *p, Virtqueue::Desc const **table) const
Default implementation for loading an indirect descriptor.
Definition l4virtio:741
Encapsulate the state for processing a VIRTIO request.
Definition virtio:473
void start(DESC_MAN *dm, Virtqueue *ring, Virtqueue::Head_desc const &request, ARGS... args)
Start processing a new request.
Definition virtio:503
Virtqueue implementation for the device.
Definition virtio:88
Request next_avail()
Get the next available descriptor from the available ring.
Definition virtio:136
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:93
l4_uint32_t len
Length of described buffer.
Definition virtqueue:115
Ptr< void > addr
Address stored in descriptor.
Definition virtqueue:114
bool ready() const
Test if this queue is in working state.
Definition virtqueue:419
Error helper.
signed short int l4_int16_t
Signed 16bit value.
Definition l4int.h:26
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_EINVAL
Invalid argument.
Definition err.h:47
@ L4_EBUSY
Object currently busy, try later.
Definition err.h:43
@ L4_ENODEV
No such thing.
Definition err.h:45
@ L4_EIO
I/O error.
Definition err.h:36
@ L4_EOK
Ok.
Definition err.h:33
@ L4_EPERM
No permission.
Definition err.h:34
@ L4_ENOMEM
No memory.
Definition err.h:40
#define L4_UNLIKELY(x)
Expression is unlikely to execute.
Definition compiler.h:295
@ L4VIRTIO_ID_CONSOLE
Simple device for data IO via ports.
Definition virtio.h:66
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-VIRTIO Transport C++ API.
Definition l4virtio:26
Exception used by Queue to indicate descriptor errors.
Definition virtio:398
@ Bad_address
Address cannot be translated.
Definition virtio:402
Virtio console control message.
@ Device_ready
Sent by driver at initialization.
@ Console_port
Sent by device to nominate port as console port.
@ Port_open
Sent by device and driver to indicate whether a port is open.
@ Port_name
Sent by device to tag a port.
@ Device_remove
Sent by device to remove added ports.
@ Device_add
Sent by device to create new ports.
@ Port_ready
Sent by driver as response to Device_add.
@ Resize
Sent by device to indicate a console size change.
l4_uint16_t value
Extra information.
l4_uint16_t event
Control event, see Events.
Specialised Virtqueue::Request providing access to control message payload.
Control_message * msg
Virtual address of the data block (in device space).
l4_uint32_t len
Length of datablock in bytes.
Driver_mem_region * mem
Pointer to driver memory region.
Virtio console specific feature bits.
constexpr emerg_write_bfm_t::Val emerg_write() const
Get the emerg_write bits (2 to 2) of raw.
constexpr console_multiport_bfm_t::Val console_multiport() const
Get the console_multiport bits (1 to 1) of raw.
constexpr console_size_bfm_t::Val console_size() const
Get the console_size bits (0 to 0) of raw.
State transition from last report state to current state.
l4_uint16_t value
Extra information.
Port_status next
Next Port_status state.
l4_int16_t event
Control_message::Events or <0 if no event is sent.
Representation of a Virtio console port.
Port_status status
State the port is in.
Virtqueue rx
Transmitq of the port.
static constexpr Transition state_transitions[Port_num_states][Port_num_states]
State transition table from last report state to current state.
unsigned vq_max
Maximum queue sizes for this port.
virtual void reset()
Reset the port to the initial state and disable its virtqueues.
Virtqueue tx
Receiveq of the port.
bool is_open() const
Check that the port is open.
bool rx_ready() const
Check that device implementation may write to receive queues.
bool tx_ready() const
Check that device implementation may read from transmit queues.
Port_status
Possible states of a virtio console port.
@ Port_added
Port has been added by device, waiting for ready message.
@ Port_ready
Port is ready but still closed.
@ Port_num_states
Number of port states. Must be last.
@ Port_open
Port is in a working state.
@ Port_disabled
Reset state, waiting for port to be added.
@ Port_failed
Device failure, port unusable.
bool queues_ready() const
Check that both virtqueues are set up correctly.
Port_status reported_status
State the port was last reported.
l4_uint32_t raw
The raw value of the features bitmap.
Definition virtio:68