vmm: implement VirtIO input model

Fix genodelabs/genode#4698
This commit is contained in:
Stefan Kalkowski 2022-12-09 12:00:41 +01:00 committed by Christian Helmuth
parent 85c8bd7d7e
commit 80687e702c
6 changed files with 332 additions and 9 deletions

View File

@ -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
------------------

View File

@ -38,7 +38,7 @@ class Vmm::Config
struct Virtio_device : List_model<Virtio_device>::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;
}

View File

@ -305,7 +305,7 @@ class Vmm::Virtio_gpu_device : public Virtio_device<Virtio_gpu_queue, 2>
Env & _env;
Heap & _heap;
Attached_ram_dataspace & _ram_ds;
Gui::Connection _gui { _env };
Gui::Connection & _gui;
Cpu::Signal_handler<Virtio_gpu_device> _handler;
Constructible<Attached_dataspace> _fb_ds { };
Framebuffer::Mode _fb_mode { _gui.mode() };
@ -452,11 +452,12 @@ class Vmm::Virtio_gpu_device : public Virtio_device<Virtio_gpu_queue, 2>
Ram & ram,
Env & env,
Heap & heap,
Attached_ram_dataspace & ram_ds)
Attached_ram_dataspace & ram_ds,
Gui::Connection & gui)
:
Virtio_device<Virtio_gpu_queue, 2>(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);

View File

@ -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 <input/event.h>
#include <input_session/client.h>
#include <virtio_device.h>
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<Virtio_split_queue, 2>
{
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<Input::Event>() };
enum State { READY, IN_MOTION, SYNC };
State _state { READY };
unsigned _num_events { 0U };
unsigned _idx_events { 0U };
int _motion_y { -1 };
Cpu::Signal_handler<Virtio_input_device> _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<Virtio_input_event::Type>(Linux_evdev::EV_ABS);
vie.write<Virtio_input_event::Code>(Linux_evdev::ABS_Y);
vie.write<Virtio_input_event::Value>(_motion_y);
_state = SYNC;
return size;
}
if (_state == SYNC) {
vie.write<Virtio_input_event::Type>(Linux_evdev::EV_SYNC);
vie.write<Virtio_input_event::Code>(0);
vie.write<Virtio_input_event::Value>(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<Virtio_input_event::Type>(Linux_evdev::EV_KEY);
vie.write<Virtio_input_event::Code>(key);
vie.write<Virtio_input_event::Value>(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<Virtio_input_event::Type>(Linux_evdev::EV_ABS);
vie.write<Virtio_input_event::Code>(Linux_evdev::ABS_X);
vie.write<Virtio_input_event::Value>(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<Virtio_split_queue, 2>(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_ */

View File

@ -17,6 +17,7 @@
#include <virtio_net.h>
#include <virtio_block.h>
#include <virtio_gpu.h>
#include <virtio_input.h>
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;
};

View File

@ -25,6 +25,7 @@
#include <base/attached_ram_dataspace.h>
#include <base/attached_rom_dataspace.h>
#include <gui_session/connection.h>
#include <vm_session/connection.h>
namespace Vmm {
@ -64,6 +65,8 @@ class Vmm::Vm
List<Virtio_device_base> _device_list;
Pl011 _uart;
Constructible<Gui::Connection> _gui {};
addr_t _initrd_offset() const;
addr_t _dtb_offset() const;