diff --git a/repos/os/src/server/vmm/README b/repos/os/src/server/vmm/README index 49f36b5e2f..7bbe0d080c 100644 --- a/repos/os/src/server/vmm/README +++ b/repos/os/src/server/vmm/README @@ -71,10 +71,9 @@ For each virtio_device node the following attributes need to be set: :type: The Virtio type of device. One can decide in between "console", "net", "gpu", - and "block". The "console" type gets mapped to a Genode Terminal session, - "net" is mapped to a Nic session, "gpu" is mapped to a Gui session, - and "block" to a Block session. - + "input", and "block". The "console" type gets mapped to a Genode Terminal + session, "net" is mapped to a Nic session, "gpu" is mapped to a Gui session, + "input" to the event part of the Gui session, and "block" to a Block session. Additional devices ------------------ diff --git a/repos/os/src/server/vmm/config.h b/repos/os/src/server/vmm/config.h index f9bd1075f4..1db2cbfd9f 100644 --- a/repos/os/src/server/vmm/config.h +++ b/repos/os/src/server/vmm/config.h @@ -38,7 +38,7 @@ class Vmm::Config struct Virtio_device : List_model::Element { - enum Type { INVALID, CONSOLE, NET, BLOCK, GPU }; + enum Type { INVALID, CONSOLE, NET, BLOCK, GPU, INPUT }; enum { MMIO_SIZE = 0x200 }; @@ -95,6 +95,7 @@ class Vmm::Config if (type == "net") t = Virtio_device::NET; if (type == "block") t = Virtio_device::BLOCK; if (type == "gpu") t = Virtio_device::GPU; + if (type == "input") t = Virtio_device::INPUT; return t; } diff --git a/repos/os/src/server/vmm/virtio_gpu.h b/repos/os/src/server/vmm/virtio_gpu.h index 444b6e95ab..c85c8bf430 100644 --- a/repos/os/src/server/vmm/virtio_gpu.h +++ b/repos/os/src/server/vmm/virtio_gpu.h @@ -305,7 +305,7 @@ class Vmm::Virtio_gpu_device : public Virtio_device Env & _env; Heap & _heap; Attached_ram_dataspace & _ram_ds; - Gui::Connection _gui { _env }; + Gui::Connection & _gui; Cpu::Signal_handler _handler; Constructible _fb_ds { }; Framebuffer::Mode _fb_mode { _gui.mode() }; @@ -452,11 +452,12 @@ class Vmm::Virtio_gpu_device : public Virtio_device Ram & ram, Env & env, Heap & heap, - Attached_ram_dataspace & ram_ds) + Attached_ram_dataspace & ram_ds, + Gui::Connection & gui) : Virtio_device(name, addr, size, irq, cpu, bus, ram, GPU), - _env(env), _heap(heap), _ram_ds(ram_ds), + _env(env), _heap(heap), _ram_ds(ram_ds), _gui(gui), _handler(cpu, env.ep(), *this, &Virtio_gpu_device::_mode_change) { _gui.mode_sigh(_handler); diff --git a/repos/os/src/server/vmm/virtio_input.h b/repos/os/src/server/vmm/virtio_input.h new file mode 100644 index 0000000000..5cb7ab3857 --- /dev/null +++ b/repos/os/src/server/vmm/virtio_input.h @@ -0,0 +1,310 @@ +/* + * \brief Virtio Input implementation + * \author Stefan Kalkowski + * \date 2022-12-06 + */ + +/* + * Copyright (C) 2022 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU Affero General Public License version 3. + */ + +#ifndef _VIRTIO_INPUT_H_ +#define _VIRTIO_INPUT_H_ + +#include +#include +#include + +namespace Vmm { + class Virtio_input_device; + using namespace Genode; +} + + +namespace Linux_evdev +{ + enum Type { + EV_SYNC = 0x0, + EV_KEY = 0x1, + EV_REL = 0x2, + EV_ABS = 0x3, + EV_REP = 0x14 + }; + + enum Relative { + REL_WHEEL = 8, + EV_REL_FEATURES = 1U << REL_WHEEL + }; + + enum Absolute { + ABS_X = 0, + ABS_Y = 1, + EV_ABS_FEATURES = (1U << ABS_X) | (1U << ABS_Y) + }; +}; + + +class Vmm::Virtio_input_device : public Virtio_device +{ + private: + + Env & _env; + Heap & _heap; + Input::Session_client & _input; + Attached_dataspace _input_ds { _env.rm(), _input.dataspace() }; + Input::Event const * const _events { + _input_ds.local_addr() }; + + enum State { READY, IN_MOTION, SYNC }; + + State _state { READY }; + unsigned _num_events { 0U }; + unsigned _idx_events { 0U }; + int _motion_y { -1 }; + + Cpu::Signal_handler _handler; + + struct Virtio_input_event : Mmio + { + enum { SIZE = 8 }; + + struct Type : Register<0, 16> {}; + struct Code : Register<2, 16> {}; + struct Value : Register<4, 32> {}; + + using Mmio::Mmio; + }; + + struct Configuration_area : Mmio_register + { + Virtio_input_device & dev; + + struct Abs_info + { + enum { SIZE = 20 }; + + uint32_t min; + uint32_t max; + uint32_t fuzz; + uint32_t flat; + }; + + enum Offsets + { + SELECT = 0, + SUB_SELECT = 1, + SIZE = 2, + DATA = 8, + DATA_MAX = DATA + 128, + }; + + enum Select { + UNSET = 0x00, + ID_NAME = 0x01, + ID_SERIAL = 0x02, + ID_DEVIDS = 0x03, + PROP_BITS = 0x10, + EV_BITS = 0x11, + ABS_INFO = 0x12, + }; + + uint8_t _select { 0 }; + uint8_t _sub_select { 0 }; + + String<16> _name { "vinput0" }; + String<16> _serial { "serial0" }; + String<16> _dev_id { "0" }; + + uint8_t _size() + { + using namespace Linux_evdev; + + switch (_select) { + case ID_NAME: return _name.length() - 1; + case ID_SERIAL: return _serial.length() - 1; + case ID_DEVIDS: return _dev_id.length() - 1; + case PROP_BITS: return 0; /* Unsupported */ + case EV_BITS: + switch (_sub_select) { + case EV_KEY: return 36; + case EV_REL: return 2; + case EV_ABS: return 1; + case EV_REP: return 1; + default: return 0; /* Unsupported */ + }; + case ABS_INFO: return 20; + default: break; + }; + + error("Unknown size for ", _select, " ", _sub_select); + return 0; + } + + Register _data(addr_t off) + { + using namespace Linux_evdev; + + switch (_select) { + case ID_NAME: + return (off < (_name.length()-1)) ? _name.string()[off] : 0; + case ID_SERIAL: + return (off < (_serial.length()-1)) ? _serial.string()[off] + : 0; + case ID_DEVIDS: + return (off < (_dev_id.length()-1)) ? _dev_id.string()[off] + : 0; + case EV_BITS: + switch (_sub_select) { + case EV_ABS: return EV_ABS_FEATURES; + case EV_REL: return EV_REL_FEATURES; + case EV_KEY: return 0xffffffff; + default: return 0; + }; + case ABS_INFO: + switch (_sub_select) { + case ABS_X: return (off == 4) ? 1920 : 0; + case ABS_Y: return (off == 4) ? 1050 : 0; + default: return 0; + }; + default: break; + }; + + error("Invalid data offset for selectors ", + _select, " ", _sub_select); + return 0; + } + + Register read(Address_range & range, Cpu&) override + { + if (range.start == SIZE) + return _size(); + + if (range.start >= DATA && range.start < DATA_MAX) + return _data(range.start-DATA); + + error("Reading from virtio input config space ", + "at offset ", range.start, " is not allowed"); + return 0; + } + + void write(Address_range & range, Cpu&, Register v) override + { + switch (range.start) { + case SELECT: _select = v; return; + case SUB_SELECT: _sub_select = v; return; + default: + error("Writing to virtio input config space ", + "at offset ", range.start, " is not allowed"); + } + } + + Configuration_area(Virtio_input_device & device) + : Mmio_register("Input config area", + Mmio_register::RO, 0x100, 0xa4), + dev(device) { device.add(*this); } + } _config_area{ *this }; + + void _handle_input() + { + if (!_queue[0].constructed()) + return; + + bool irq = _queue[0]->notify([&] (addr_t addr, size_t size) { + if (size < Virtio_input_event::SIZE) { + warning("wrong virtioqueue packet size for input ", size); + return 0UL; + } + + Virtio_input_event vie(addr); + + if (_state == IN_MOTION) { + vie.write(Linux_evdev::EV_ABS); + vie.write(Linux_evdev::ABS_Y); + vie.write(_motion_y); + _state = SYNC; + return size; + } + + if (_state == SYNC) { + vie.write(Linux_evdev::EV_SYNC); + vie.write(0); + vie.write(0); + _state = READY; + return size; + } + + if (_num_events == _idx_events) { + _num_events = _input.flush(); + _idx_events = 0; + } + + while (_idx_events < _num_events && + !_events[_idx_events].valid()) + _idx_events++; + + if (_num_events == _idx_events) + return 0UL; + + Input::Event const event = _events[_idx_events++]; + + auto press = [&] (Input::Keycode key, bool press) { + vie.write(Linux_evdev::EV_KEY); + vie.write(key); + vie.write(press); + _state = SYNC; + }; + event.handle_press([&] (Input::Keycode key, Genode::Codepoint) { + press(key, true); }); + event.handle_release([&] (Input::Keycode key) { + press(key, false); }); + event.handle_absolute_motion([&] (int x, int y) + { + vie.write(Linux_evdev::EV_ABS); + vie.write(Linux_evdev::ABS_X); + vie.write(x); + _motion_y = y; + _state = IN_MOTION; + }); + return size; + }); + + if (irq) _assert_irq(); + } + + void _notify(unsigned idx) override + { + if (idx) { + error("VirtIO input queue for status event not implemented"); + return; + } + _handle_input(); + } + + enum Device_id { INPUT = 18 }; + + public: + + Virtio_input_device(const char * const name, + const uint64_t addr, + const uint64_t size, + unsigned irq, + Cpu & cpu, + Mmio_bus & bus, + Ram & ram, + Env & env, + Heap & heap, + Input::Session_client & input) + : + Virtio_device(name, addr, size, + irq, cpu, bus, ram, INPUT), + _env(env), _heap(heap), _input(input), + _handler(cpu, env.ep(), *this, &Virtio_input_device::_handle_input) + { + _input.sigh(_handler); + } +}; + +#endif /* _VIRTIO_INPUT_H_ */ diff --git a/repos/os/src/server/vmm/vm.cc b/repos/os/src/server/vmm/vm.cc index 5b8ebe9650..163ee9101f 100644 --- a/repos/os/src/server/vmm/vm.cc +++ b/repos/os/src/server/vmm/vm.cc @@ -17,6 +17,7 @@ #include #include #include +#include using Vmm::Vm; @@ -124,10 +125,18 @@ Vm::Vm(Genode::Env & env, Heap & heap, Config & config) _bus, _ram, env, heap)); return; case Config::Virtio_device::GPU: + if (!_gui.constructed()) _gui.construct(env); _device_list.insert(new (_heap) Virtio_gpu_device(dev.name.string(), (uint64_t)dev.mmio_start, dev.mmio_size, dev.irq, boot_cpu(), - _bus, _ram, env, heap, _vm_ram)); + _bus, _ram, env, heap, _vm_ram, *_gui)); + return; + case Config::Virtio_device::INPUT: + if (!_gui.constructed()) _gui.construct(env); + _device_list.insert(new (_heap) + Virtio_input_device(dev.name.string(), (uint64_t)dev.mmio_start, + dev.mmio_size, dev.irq, boot_cpu(), + _bus, _ram, env, heap, *_gui->input())); default: return; }; diff --git a/repos/os/src/server/vmm/vm.h b/repos/os/src/server/vmm/vm.h index 28b4a4368e..8c19b155a7 100644 --- a/repos/os/src/server/vmm/vm.h +++ b/repos/os/src/server/vmm/vm.h @@ -25,6 +25,7 @@ #include #include +#include #include namespace Vmm { @@ -64,6 +65,8 @@ class Vmm::Vm List _device_list; Pl011 _uart; + Constructible _gui {}; + addr_t _initrd_offset() const; addr_t _dtb_offset() const;