L4Re Operating System Framework
Interface and Usage Documentation
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
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
19{
20 Features() = default;
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
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 {
154 }
155
157 bool queues_ready() const
158 { return tx.ready() && rx.ready(); }
159
161 bool rx_ready() const
162 { return is_open() && rx.ready(); }
163
165 bool tx_ready() const
166 { return is_open() && tx.ready(); }
167
174
194 static constexpr Transition
196 {
197 /* reported current */
198
199 /* Port_disabled */ /* Port_disabled */ {{ -1, 0, Port_disabled },
200 /* Port_added */ { Control_message::Device_add, 0,
201 Port_added },
202 /* Port_ready */ { Control_message::Device_add, 0,
203 Port_ready },
204 /* Port_open */ { Control_message::Device_add, 0,
205 Port_ready },
206 /* Port_failed */ { Control_message::Device_add, 0,
207 Port_failed }},
208
209 /* Port_added */ /* Port_disabled */ {{ Control_message::Device_remove,
210 0, Port_disabled },
211 /* Port_added */ { -1, 0, Port_added },
212 /* Port_ready */ { -1, 0, Port_ready },
213 /* Port_open */ { Control_message::Port_open, 1,
214 Port_open },
215 /* Port_failed */ { -1, 0, Port_failed }},
216
217 /* Port_ready */ /* Port_disabled */ {{ Control_message::Device_remove,
218 0, Port_disabled },
219 /* Port_added */ { -1, 0, Port_added },
220 /* Port_ready */ { -1, 0, Port_ready },
221 /* Port_open */ { Control_message::Port_open, 1,
222 Port_open },
223 /* Port_failed */ { -1, 0, Port_failed }},
224
225 /* Port_open */ /* Port_disabled */ {{ Control_message::Port_open, 0,
226 Port_ready },
227 /* Port_added */ { Control_message::Port_open, 0,
228 Port_added },
229 /* Port_ready */ { Control_message::Port_open, 0,
230 Port_ready },
231 /* Port_open */ { -1, 0, Port_open },
232 /* Port_failed */ { Control_message::Port_open, 0,
233 Port_ready }},
234
235 /* Port_failed */ /* Port_disabled */ {{ Control_message::Device_remove,
236 0, Port_disabled },
237 /* Port_added */ { -1, 0, Port_added },
238 /* Port_ready */ { -1, 0, Port_ready },
239 /* Port_open */ { Control_message::Port_open, 1,
240 Port_open },
241 /* Port_failed */ { -1, 0, Port_failed }},
242 };
243};
244
268{
269 enum Virtqueue_names
270 {
271 Ctrl_rx = 2,
272 Ctrl_tx = 3,
273 };
274
275 struct Serial_config_space
276 {
277 l4_uint16_t cols;
278 l4_uint16_t rows;
279 l4_uint32_t max_nr_ports;
280 l4_uint32_t emerg_wr;
281 } __attribute__((packed));
282
283public:
293 explicit Virtio_con(unsigned max_ports, bool enable_multiport)
294 : L4virtio::Svr::Device(&_dev_config),
295 _num_ports(enable_multiport ? max_ports : 1),
296 _dev_config(L4VIRTIO_VENDOR_KK, L4VIRTIO_ID_CONSOLE,
297 enable_multiport ? max_ports * 2 + 2 : 2)
298 {
299 if (_num_ports < 1)
300 L4Re::chksys(-L4_EINVAL, "At least one port is required.");
301
302 Features hf(0);
303
304 hf.console_multiport() = enable_multiport;
305
306 _dev_config.host_features(0) = hf.raw;
307
308 if (enable_multiport)
309 _dev_config.priv_config()->max_nr_ports = _num_ports;
310 _dev_config.reset_hdr();
311 }
312
313 void reset_queue_configs()
314 {
315 for (unsigned q = 0; q < _dev_config.num_queues(); ++q)
316 reset_queue_config(q, max_queue_size(q));
317 }
318
319 int reconfig_queue(unsigned index) override
320 {
321 if (index >= _dev_config.num_queues())
322 return -L4_ERANGE;
323
324 if (setup_queue(get_queue(index), index, max_queue_size(index)))
325 return 0;
326
327 return -L4_EINVAL;
328 }
329
334 bool multiport_enabled() const
335 {
336 return _negotiated_features.console_multiport()
337 && _dev_config.num_queues() > Ctrl_rx;
338 }
339
340 bool ctrl_queue_ready() const
341 { return _ctrl_port.is_open(); }
342
343 bool check_features(void) override
344 {
345 _negotiated_features = Features(_dev_config.negotiated_features(0));
346 return true;
347 }
348
349 bool check_queues() override
350 {
351 // NOTE
352 // The VIRTIO specification states:
353 // "The port 0 receive and transmit queues always exist"
354 // The linux driver however does not setup port 0 if the multiport feature
355 // is negotiated.
356 // We just go along with the linux driver and do not expect port 0 to be up,
357 // if the multiport feature is negotiated.
358
359 if (multiport_enabled())
360 // If MULTIPORT was negotiated, ctrl queues should be set up.
361 return _ctrl_port.queues_ready();
362
363 // If MULTIPORT was not negotiated, port 0 should be set up.
365 return port(0)->queues_ready();
366 }
367
379 int port_add(unsigned idx)
380 {
381 Port *p = port(idx);
382
383 if (p->status != Port::Port_disabled)
384 return -L4_EPERM;
385
387 port_report_status(idx);
388
389 return L4_EOK;
390 }
391
403 int port_remove(unsigned idx)
404 {
405 Port *p = port(idx);
406
407 if (p->status == Port::Port_disabled)
408 return -L4_EPERM;
409
411 port_report_status(idx);
412
413 return L4_EOK;
414 }
415
428 int port_open(unsigned idx, bool open)
429 {
430 Port *p = port(idx);
431
432 if ((open && p->status != Port::Port_ready)
433 || (!open && p->status != Port::Port_open))
434 return -L4_EPERM;
435
437 port_report_status(idx);
438
439 return L4_EOK;
440 }
441
455 int port_name(unsigned idx, char const *name)
456 {
457 Port *p = port(idx);
458
459 if (p->status == Port::Port_disabled)
460 return -L4_EPERM;
461
463 }
464
488 l4_uint16_t value = 0, const char *name = 0)
489 {
490 if (!ctrl_queue_ready())
491 return -L4_ENODEV;
492
493 Virtqueue *q = &_ctrl_port.rx;
494 if (!q->ready())
495 return -L4_ENODEV;
496
497 Virtqueue::Request r = q->next_avail();
498 if (!r)
499 return -L4_EBUSY;
500
502 Control_request req;
503 rp.start(this, r, &req);
504
505 if (req.len < sizeof(Control_message))
506 return -L4_ENOMEM;
507
508 Control_message msg(idx, event, value);
509
510 memcpy(req.msg, &msg, sizeof(msg));
511
512 if (event == Control_message::Port_name && name)
513 {
514 size_t name_len = cxx::min(req.len - sizeof(msg), strlen(name));
515 memcpy(reinterpret_cast<char*>(req.msg) + sizeof(msg), name, name_len);
516 q->finish(r, this, sizeof(msg) + name_len);
517 }
518 else
519 q->finish(r, this, sizeof(msg));
520
521 return L4_EOK;
522 }
523
537 {
538 // Report port state transitions if that failed in the past...
539 if (_report_port_state)
540 {
541 _report_port_state = false;
542
543 for (unsigned i = 0; i < _num_ports; ++i)
544 if (!port_report_status(i))
545 _report_port_state = true;
546 }
547
548 Virtqueue *q = &_ctrl_port.tx;
549 if (!q->ready())
550 return -L4_ENODEV;
551
552 int ret = L4_EOK;
553 Virtqueue::Request r;
554 while ((r = q->next_avail()))
555 {
557 Control_request req;
558
559 rp.start(this, r, &req);
560
561 Control_message msg;
562 if (req.len < sizeof(msg))
563 {
564 // Just ignore malformed input.
565 q->finish(r, this);
566 ret = -L4_EINVAL;
567 continue;
568 }
569
570 memcpy(&msg, req.msg, sizeof(msg));
571 q->finish(r, this);
572
573 if (_ctrl_port.status == Port::Port_disabled)
574 {
575 // When the control queue is disabled, only device ready is accepted.
577 {
578 if (msg.value)
579 _ctrl_port.status = Port::Port_open;
580 }
581
583 continue;
584 }
585
586 if (!ctrl_queue_ready())
587 continue;
588
589 // Ignore invalid port ids
590 if (msg.id >= max_ports())
591 break;
592
593 switch (msg.event)
594 {
596 process_port_ready(msg.id, msg.value);
597 break;
599 process_port_open(msg.id, msg.value);
600 break;
601 default:
602 ret = -L4_EINVAL;
603 break;
604 }
605 }
606
607 return ret;
608 }
609
611 void load_desc(L4virtio::Virtqueue::Desc const &desc,
612 Request_processor const *proc,
613 L4virtio::Virtqueue::Desc const **table)
614 {
615 this->_mem_info.load_desc(desc, proc, table);
616 }
617
619 void load_desc(L4virtio::Virtqueue::Desc const &desc,
620 Request_processor const *proc,
621 Control_request *data)
622 {
623 auto *region = this->_mem_info.find(desc.addr.get(), desc.len);
624 if (L4_UNLIKELY(!region))
626
627 data->msg = reinterpret_cast<Control_message *>(region->local(desc.addr));
628 data->len = desc.len;
629 data->mem = region;
630 }
631
632 void reset() override
633 {
634 for (unsigned p = 0; p < _num_ports; ++p)
635 port(p)->reset();
636
637 _ctrl_port.reset();
638 reset_queue_configs();
639 _dev_config.reset_hdr();
640 _negotiated_features = Features(0);
641 _report_port_state = false;
642
643 reset_device();
644 }
645
652 virtual void reset_device() {}
653
663 virtual void notify_queue(Virtqueue *queue) = 0;
664
672 virtual Port *port(unsigned port) = 0;
673 virtual Port const *port(unsigned port) const = 0;
674
685 virtual void process_device_ready(l4_uint16_t value) = 0;
686
699 {
700 Port *p = port(id);
701
702 switch (p->status)
703 {
704 case Port::Port_added:
705 case Port::Port_ready:
707 break;
708 case Port::Port_open:
709 if (!value)
711 break;
712 default:
713 // invalid state for PORT_READY message
714 break;
715 }
716 }
717
728 virtual void process_port_open(l4_uint32_t id, l4_uint16_t value) = 0;
729
730 unsigned max_ports() const
731 { return _num_ports; }
732
733private:
734 bool is_control_queue(unsigned q) const
735 { return q == Ctrl_rx || q == Ctrl_tx; }
736
737 unsigned queue_to_port(unsigned q) const
738 { return (q == 0 || q == 1) ? 0 : (q / 2) - 1; }
739
748 unsigned max_queue_size(unsigned q) const
749 {
750 if (is_control_queue(q))
751 return _ctrl_port.vq_max;
752
753 return port(queue_to_port(q))->vq_max;
754 }
755
764 Virtqueue *get_queue(unsigned q)
765 {
766 Port *p;
767 if (is_control_queue(q))
768 p = &_ctrl_port;
769 else
770 p = port(queue_to_port(q));
771
772 if (q & 1)
773 return &p->tx;
774 else
775 return &p->rx;
776 }
777
788 bool port_report_status(unsigned idx)
789 {
790 Port *p = port(idx);
791 while (p->status != p->reported_status)
792 {
793 auto const &trans
794 = Port::state_transitions[p->reported_status][p->status];
795
796 if (trans.event >= 0
797 && send_control_message(idx, trans.event, trans.value) < 0)
798 {
799 _report_port_state = true;
800 return false;
801 }
802
803 p->reported_status = trans.next;
804 }
805
806 return true;
807 }
808
809 unsigned _num_ports;
810 bool _report_port_state = false;
811
812protected:
813 Dev_config_t<Serial_config_space> _dev_config;
814 Port _ctrl_port;
815 Features _negotiated_features{0};
816};
817
818}}} // name space
l4_uint64_t get() const
Definition virtqueue:62
Base class implementing a virtio console device with L4Re-based notification handling.
Base class implementing a virtio console functionality.
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.
Server-side L4-VIRTIO device stub.
Definition l4virtio:796
Mem_list _mem_info
Memory region list.
Definition l4virtio:801
bool setup_queue(Virtqueue *q, unsigned qn, unsigned num_max)
Enable/disable the specified queue.
Definition l4virtio:1041
void reset_queue_config(unsigned idx, unsigned num_max, bool inc_generation=false)
Trigger reset for the configuration space for queue idx.
Definition l4virtio:996
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:501
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:87
l4_uint32_t len
Length of described buffer.
Definition virtqueue:109
Ptr< void > addr
Address stored in descriptor.
Definition virtqueue:108
bool ready() const
Test if this queue is in working state.
Definition virtqueue:399
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:48
@ L4_EINVAL
Invalid argument.
Definition err.h:46
@ L4_EBUSY
Object currently busy, try later.
Definition err.h:42
@ L4_ENODEV
No such thing.
Definition err.h:44
@ L4_EOK
Ok.
Definition err.h:32
@ L4_EPERM
No permission.
Definition err.h:33
@ L4_ENOMEM
No memory.
Definition err.h:39
#define L4_UNLIKELY(x)
Expression is unlikely to execute.
Definition compiler.h:275
@ L4VIRTIO_ID_CONSOLE
Simple device for data IO via ports.
Definition virtio.h:65
long chksys(long err, char const *extra="", long 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.
Type for device feature bitmap.
Definition virtio:67
l4_uint32_t raw
The raw value of the features bitmap.
Definition virtio:68